Pod > Developer guide¶
This guide presents the coding conventions of the SUMARiS Pod (Java, Spring Boot, JPA/Hibernate, GraphQL), for a developer joining the project.
It is written from the AI agent rules of the sumaris-pod repository (.agents/rules/), which are the reference:
each section gives its source rule, on GitLab and as a synchronized copy on this site.
To update these copies and find the sections to review: npm run site:sync-rules.
See also: environment setup, Pod API, App conventions (TypeScript entities, client-side GraphQL services).
Common rules¶
- Use the helpers of the
sumaris-sharedmodule (net.sumaris.core.util) rather than ad hoc code:- dates: only
Dates(Dates.addDays(),Dates.safeParseDate()...), neverCalendarnorSimpleDateFormat; - collections and strings:
CollectionUtils.isEmpty(),ArrayUtils.isEmpty(),StringUtils.isBlank(); - streams:
Beans.getStream(collection), which accepts a null collection.
- dates: only
- Logs with SLF4J, through the Lombok class annotation
@Slf4j. - Configuration options read once (see Configuration), never in a loop.
- Messages shown to the user are always translated (see Internationalization).
JPA entities, VOs and repositories¶
Each entity of the model comes with several classes:
| Class | Conventions |
|---|---|
| JPA entity | Lombok @FieldNameConstants, @Getter, @Setter, @EqualsAndHashCode(onlyExplicitlyIncluded = true); implements IEntity<ID>; snake_case table, *_seq sequence, *_fk foreign keys, nullable = false columns by default |
Value Object (*VO) |
Lombok @Data, @FieldNameConstants; implements IValueObject<ID> (at least id and updateDate); for each relation, the related VO and its id (otherEntity + otherEntityId); arrays for simple types, List<> for VOs |
*Specifications |
extends IEntitySpecifications<ID, E>; joins with Daos.composePath() / composeJoin(); fields always through Entity.Fields.NAME |
*Repository |
extends SumarisJpaRepository<E, ID, V> and the Specifications interface |
*RepositoryImpl |
extends SumarisJpaRepositoryImpl<E, ID, V>; toVO() and toEntity() conversions, using the repositories of the related entities |
protected MyEntityRepositoryImpl(EntityManager entityManager) {
super(MyEntity.class, MyEntityVO.class, entityManager);
}
Source:
jpa-entities-and-vos.md(copy)
Business services¶
*FilterVO(Lombok@Data,@Builder,@FieldNameConstants, implementsIDataFilter) and*FetchOptions(implementsIFetchOptions) finely control the returned data.-
*Serviceinterface annotated with@Transactional, with the usual methods:List<E> findByFilter(F filter, Page page, FO fetchOptions); Optional<E> findById(ID id, FO fetchOptions); E save(E source); void delete(ID id); -
Implementation
@Service("myEntityService"), delegating to the repository.
Source:
service.md(copy)
GraphQL and REST APIs¶
- GraphQL: a
*GraphQLServiceclass (@GraphQLApi) delegating to the business service:- read:
@GraphQLQuery(name, description)+@Transactional(readOnly = true); - write:
@GraphQLMutation(name, description)+@Transactional; - each parameter annotated with
@GraphQLArgument(name = "..."); lists filtered byFilterVOandFetchOptions.
- read:
- REST: paths centralized in
RestPaths, explicitproduces/consumes, JSON through dedicated POJOs (Lombok,@JsonInclude(NON_NULL),@JsonPropertyif needed), security throughAuthService.isLogin()/hasUserProfile(). - To use these APIs from a client, see Pod API.
Source:
graphql-rest-api.md(copy)
Security¶
-
Method access control with the meta-annotations of the
net.sumaris.server.http.securitypackage, rather than@Secureddirectly:Annotation Allowed profiles @IsAdminadministrators @IsSupervisorsupervisors and administrators @IsUserany authenticated user (except guests) @IsGuestany authenticated user, guests included -
Fine-grained control (programs, locations, other users' data) with
DataAccessControlService:checkCanRead(),checkCanWrite(),getAuthorizedProgramIds()... (NO_ACCESS_FAKE_IDSfor "no access"). - ED25519 public keys (
issuer,sender):SYSTEMfor the system itself,SystemRecipientEnumrather than hard-coded strings, displayed withCryptoUtils.minifyPubkey().
Source:
security-and-crypto.md(copy)
Configuration options¶
- Client-side options in
SumarisConfigurationOption, server-side options inSumarisServerConfigurationOption. -
Never read an option in a business method: read it once and keep it in a field, refreshed when the configuration changes:
@PostConstruct @EventListener({ConfigurationReadyEvent.class, ConfigurationUpdatedEvent.class}) public void onConfigurationReady() { this.enabled = configuration.isMyFeatureEnabled(); }
Source:
configuration-option.md(copy)
Cache¶
- Spring Cache with JCache (Ehcache); one
*CacheConfigurationclass per module, with cache names as constants of an innerNamesinterface. - Caches created with
Caches.createHeapCache()(and variants),CacheTTLdurations;@Cacheableon costly reads,@CacheEvicton updates.
Database (Liquibase)¶
- Changeset author: the developer's e-mail address; ids
<timestamp_ms>-NNN. - Preconditions everywhere (e.g.
tableExistswithonFail="MARK_RAN"). snake_casetables,*_seqsequences,*_fkforeign key columns,*_fkcconstraints.- One changelog per database (HSQLDB, PostgreSQL, Oracle), with the same ids when possible.
Source:
database-liquibase.md(copy)
Internationalization¶
I18n.t("sumaris.module.category.message", args...)(static importorg.nuiton.i18n.I18n.t), parameters{0},{1}...- Keys in
<module>/src/main/resources/i18n/, files*_en_GB.propertiesand*_fr_FR.properties.
Tests¶
- JUnit 4 and DBUnit, on an HSQLDB database managed by
DatabaseResource. - XML datasets in
sumaris-test-shared/src/main/resources; the test database is generated once byInitTests(fromsumaris-core) and must be regenerated when these files change. - One
TestConfigurationper module (DaoTestConfiguration,ServiceTestConfiguration), kept aligned withApplication.java. - To test a feature flag: one test with the default configuration, another one with a dedicated properties file
(
@TestPropertySource).
Source:
unit-testing.md(copy)
Specific topics¶
| Topic | Rule |
|---|---|
Elasticsearch (indices, analyzers, ElasticsearchSpecification) |
elasticsearch.md (copy) |
Parquet files (@ParquetColumn, repositories) |
parquet-entities.md (copy) |
| FishBase / SeaLifeBase data | fishbase.md (copy) |
| GitLab usage (issues, branches, MRs) | gitlab.md (copy) |