Skip to content

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-shared module (net.sumaris.core.util) rather than ad hoc code:
    • dates: only Dates (Dates.addDays(), Dates.safeParseDate()...), never Calendar nor SimpleDateFormat;
    • collections and strings: CollectionUtils.isEmpty(), ArrayUtils.isEmpty(), StringUtils.isBlank();
    • streams: Beans.getStream(collection), which accepts a null collection.
  • 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).

Source: common.md (copy)

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, implements IDataFilter) and *FetchOptions (implements IFetchOptions) finely control the returned data.
  • *Service interface 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 *GraphQLService class (@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 by FilterVO and FetchOptions.
  • REST: paths centralized in RestPaths, explicit produces / consumes, JSON through dedicated POJOs (Lombok, @JsonInclude(NON_NULL), @JsonProperty if needed), security through AuthService.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.security package, rather than @Secured directly:

    Annotation Allowed profiles
    @IsAdmin administrators
    @IsSupervisor supervisors and administrators
    @IsUser any authenticated user (except guests)
    @IsGuest any authenticated user, guests included
  • Fine-grained control (programs, locations, other users' data) with DataAccessControlService: checkCanRead(), checkCanWrite(), getAuthorizedProgramIds()... (NO_ACCESS_FAKE_IDS for "no access").

  • ED25519 public keys (issuer, sender): SYSTEM for the system itself, SystemRecipientEnum rather than hard-coded strings, displayed with CryptoUtils.minifyPubkey().

Source: security-and-crypto.md (copy)

Configuration options

  • Client-side options in SumarisConfigurationOption, server-side options in SumarisServerConfigurationOption.
  • 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 *CacheConfiguration class per module, with cache names as constants of an inner Names interface.
  • Caches created with Caches.createHeapCache() (and variants), CacheTTL durations; @Cacheable on costly reads, @CacheEvict on updates.

Source: cache.md (copy)

Database (Liquibase)

  • Changeset author: the developer's e-mail address; ids <timestamp_ms>-NNN.
  • Preconditions everywhere (e.g. tableExists with onFail="MARK_RAN").
  • snake_case tables, *_seq sequences, *_fk foreign key columns, *_fkc constraints.
  • 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 import org.nuiton.i18n.I18n.t), parameters {0}, {1}...
  • Keys in <module>/src/main/resources/i18n/, files *_en_GB.properties and *_fr_FR.properties.

Source: i18n.md (copy)

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 by InitTests (from sumaris-core) and must be regenerated when these files change.
  • One TestConfiguration per module (DaoTestConfiguration, ServiceTestConfiguration), kept aligned with Application.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)