Aller au contenu

Pod > Guide de développement

Ce guide présente les conventions de code du Pod SUMARiS (Java, Spring Boot, JPA/Hibernate, GraphQL), pour un développeur qui rejoint le projet.

Il est rédigé à partir des règles des agents IA du dépôt sumaris-pod (.agents/rules/), qui font foi : chaque section indique sa règle source, sur GitLab et dans sa copie synchronisée sur ce site. Pour mettre à jour ces copies et savoir quelles sections relire : npm run site:sync-rules.

Voir aussi : installation de l'environnement, API du Pod, conventions de l'App (entités TypeScript, services GraphQL côté client).

Règles communes

  • Utiliser les utilitaires du module sumaris-shared (net.sumaris.core.util) plutôt que du code ad hoc :
    • dates : uniquement Dates (Dates.addDays(), Dates.safeParseDate()...), jamais Calendar ni SimpleDateFormat ;
    • collections et chaînes : CollectionUtils.isEmpty(), ArrayUtils.isEmpty(), StringUtils.isBlank() ;
    • streams : Beans.getStream(collection), qui accepte une collection nulle.
  • Logs avec SLF4J, via l'annotation de classe Lombok @Slf4j.
  • Options de configuration lues une seule fois (voir Configuration), jamais dans une boucle.
  • Messages destinés à l'utilisateur toujours traduits (voir Internationalisation).

Source : common.md (copie)

Entités JPA, VO et repositories

Chaque entité du modèle se décline en plusieurs classes :

Classe Conventions
Entité JPA Lombok @FieldNameConstants, @Getter, @Setter, @EqualsAndHashCode(onlyExplicitlyIncluded = true) ; implémente IEntity<ID> ; table en snake_case, séquence *_seq, clés étrangères *_fk, colonnes nullable = false par défaut
Value Object (*VO) Lombok @Data, @FieldNameConstants ; implémente IValueObject<ID> (au minimum id et updateDate) ; pour chaque relation, le VO lié et son identifiant (otherEntity + otherEntityId) ; tableaux pour les types simples, List<> pour les VO
*Specifications étend IEntitySpecifications<ID, E> ; jointures avec Daos.composePath() / composeJoin() ; champs toujours via Entity.Fields.NOM
*Repository étend SumarisJpaRepository<E, ID, V> et l'interface Specifications
*RepositoryImpl étend SumarisJpaRepositoryImpl<E, ID, V> ; conversions toVO() et toEntity(), en s'appuyant sur les repositories des entités liées
protected MyEntityRepositoryImpl(EntityManager entityManager) {
  super(MyEntity.class, MyEntityVO.class, entityManager);
}

Source : jpa-entities-and-vos.md (copie)

Services métier

  • *FilterVO (Lombok @Data, @Builder, @FieldNameConstants, implémente IDataFilter) et *FetchOptions (implémente IFetchOptions) contrôlent finement les données renvoyées.
  • Interface *Service annotée @Transactional, avec les méthodes usuelles :

    List<E> findByFilter(F filter, Page page, FO fetchOptions);
    Optional<E> findById(ID id, FO fetchOptions);
    E save(E source);
    void delete(ID id);
    
  • Implémentation @Service("myEntityService"), qui délègue au repository.

Source : service.md (copie)

API GraphQL et REST

  • GraphQL : une classe *GraphQLService (@GraphQLApi) qui délègue au service métier :
    • lecture : @GraphQLQuery(name, description) + @Transactional(readOnly = true) ;
    • écriture : @GraphQLMutation(name, description) + @Transactional ;
    • chaque paramètre annoté @GraphQLArgument(name = "...") ; listes filtrées par FilterVO et FetchOptions.
  • REST : chemins centralisés dans RestPaths, produces / consumes explicites, JSON par des POJO dédiés (Lombok, @JsonInclude(NON_NULL), @JsonProperty si besoin), sécurité via AuthService.isLogin() / hasUserProfile().
  • Pour l'utilisation de ces API par un client, voir API du Pod.

Source : graphql-rest-api.md (copie)

Sécurité

  • Contrôle d'accès des méthodes par les méta-annotations du paquet net.sumaris.server.http.security, plutôt que @Secured directement :

    Annotation Profils autorisés
    @IsAdmin administrateurs
    @IsSupervisor superviseurs et administrateurs
    @IsUser tout utilisateur authentifié (hors invité)
    @IsGuest tout utilisateur authentifié, invités inclus
  • Contrôle fin (programmes, lieux, données d'autres utilisateurs) avec DataAccessControlService : checkCanRead(), checkCanWrite(), getAuthorizedProgramIds()... (NO_ACCESS_FAKE_IDS pour « aucun accès »).

  • Clés publiques ED25519 (issuer, sender) : SYSTEM pour le système, SystemRecipientEnum plutôt que des chaînes en dur, affichage par CryptoUtils.minifyPubkey().

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

Options de configuration

  • Options côté client dans SumarisConfigurationOption, côté serveur dans SumarisServerConfigurationOption.
  • Ne jamais lire une option dans une méthode métier : la lire une fois et la garder dans un champ, rafraîchi quand la configuration change :

    @PostConstruct
    @EventListener({ConfigurationReadyEvent.class, ConfigurationUpdatedEvent.class})
    public void onConfigurationReady() {
        this.enabled = configuration.isMyFeatureEnabled();
    }
    

Source : configuration-option.md (copie)

Cache

  • Spring Cache avec JCache (Ehcache) ; une classe *CacheConfiguration par module, avec les noms de cache en constantes d'une interface interne Names.
  • Caches créés avec Caches.createHeapCache() (et variantes), durées CacheTTL ; @Cacheable sur les lectures coûteuses, @CacheEvict sur les mises à jour.

Source : cache.md (copie)

Base de données (Liquibase)

  • Auteur d'un changeset : l'adresse e-mail du développeur ; identifiants <timestamp_ms>-NNN.
  • Préconditions systématiques (par exemple tableExists en onFail="MARK_RAN").
  • Tables en snake_case, séquences *_seq, colonnes de clé étrangère *_fk, contraintes *_fkc.
  • Un changelog par base (HSQLDB, PostgreSQL, Oracle), avec les mêmes identifiants si possible.

Source : database-liquibase.md (copie)

Internationalisation

  • I18n.t("sumaris.module.categorie.message", args...) (import statique org.nuiton.i18n.I18n.t), paramètres {0}, {1}...
  • Clés dans <module>/src/main/resources/i18n/, fichiers *_en_GB.properties et *_fr_FR.properties.

Source : i18n.md (copie)

Tests

  • JUnit 4 et DBUnit, sur une base HSQLDB gérée par DatabaseResource.
  • Jeux de données en XML dans sumaris-test-shared/src/main/resources ; la base de test est générée une fois par InitTests (depuis sumaris-core) et doit être régénérée quand ces fichiers changent.
  • Une TestConfiguration par module (DaoTestConfiguration, ServiceTestConfiguration), à garder alignée sur Application.java.
  • Pour tester une fonctionnalité activable : un test avec la configuration par défaut, un autre avec un fichier de propriétés dédié (@TestPropertySource).

Source : unit-testing.md (copie)

Sujets spécifiques

Sujet Règle
Elasticsearch (index, analyseurs, ElasticsearchSpecification) elasticsearch.md (copie)
Fichiers Parquet (@ParquetColumn, repositories) parquet-entities.md (copie)
Données FishBase / SeaLifeBase fishbase.md (copie)
Usage de GitLab (tickets, branches, MR) gitlab.md (copie)