DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Uncategorized»Test des adaptateurs de référentiel avec une architecture hexagonale
    Uncategorized

    Test des adaptateurs de référentiel avec une architecture hexagonale

    mars 7, 2023
    Test des adaptateurs de référentiel avec une architecture hexagonale
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    Lors de l’application d’une architecture hexagonale (ports et adaptateurs), l’accès aux éléments d’infrastructure tels que les bases de données se fait au moyen d’adaptateurs, qui ne sont que des implémentations d’interfaces (ports) définies par le domaine. Dans cet article, nous allons fournir deux implémentations du même port de référentiel, une en mémoire et une autre basée sur JPA, en nous concentrant sur la façon de tester les deux implémentations avec le même ensemble de tests.

    Contexte

    De nombreuses solutions logicielles généralement développées dans le contexte de l’entreprise ont un état qui doit être conservé dans un magasin durable pour un accès ultérieur. Selon les exigences fonctionnelles et non fonctionnelles spécifiques, la sélection de la bonne solution de persistance peut être difficile à faire et nécessite très probablement un dossier de décision d’architecture (ADR) où la justification de la sélection, y compris les alternatives et les compromis, est détaillée. Pour conserver l’état de votre application, vous examinerez très probablement le théorème CAP pour prendre la décision la plus adéquate.

    Ce processus de décision ne doit pas retarder la conception et le développement du modèle de domaine de votre application. Les équipes d’ingénierie doivent se concentrer sur la création de valeur (commerciale), et non sur la maintenance d’un tas de scripts DDL et l’évolution d’un schéma de base de données très changeant, pour quelques semaines (ou mois) plus tard, réaliser qu’il aurait été préférable d’utiliser une base de données de documents au lieu d’un base de données relationnelle.

    De plus, se concentrer sur la valeur du domaine de livraison empêche l’équipe de prendre une décision liée au domaine basée sur les contraintes d’une décision technique et/ou liée à l’infrastructure prise trop tôt (c’est-à-dire la technologie de base de données dans ce cas). Comme oncle Bob l’a dit dans ce tweeterl’architecture doit permettre de différer les décisions d’encadrement (et d’infrastructure).

    Différer les décisions liées aux infrastructures

    Pour en revenir à l’exemple de la technologie de base de données, un moyen de différer la décision d’infrastructure concernant la technologie de base de données à utiliser serait de commencer par une simple implémentation en mémoire de votre référentiel où les entités de domaine peuvent être stockées dans une liste en mémoire. Cette approche accélère la découverte, la conception et la mise en œuvre des fonctionnalités et des cas d’utilisation de domaine, permettant des cycles de rétroaction rapides avec vos parties prenantes sur ce qui compte : Valeur du domaine.

    Maintenant, vous pensez peut-être, « mais ensuite, je ne propose pas de fonctionnalité de travail e2e », ou « comment vérifier la fonctionnalité avec un adaptateur en mémoire de mon référentiel ? » Ici, des modèles d’architecture comme Hexagonal Architecture (également appelés ports et adaptateurs) et des méthodologies comme DDD (non obligatoire pour avoir une architecture propre et finalement un code propre) entrent en action.

    Architecture hexagonale

    De nombreuses applications sont conçues selon l’architecture classique à trois couches :

    1. Présentation/contrôleur
    2. Service (logique métier)
    3. Couches de persistance

    Cette architecture a tendance à mélanger la définition de domaine (par exemple, des entités de domaine et des objets de valeur) avec des tables (par exemple, des entités ORM), généralement représentées comme de simples objets de transfert de données. Ceci est illustré ci-dessous :

    DTO

    Au contraire, avec une architecture hexagonale, les classes liées à la persistance réelle sont toutes définies sur la base du modèle de domaine.

    Modèle de domaine

    En utilisant le port (interface) du référentiel (qui est défini dans le cadre du modèle de domaine), il est possible de définir des définitions de test d’intégration indépendantes de la technologie sous-jacente, qui vérifient les attentes du domaine vis-à-vis du référentiel. Voyons à quoi cela ressemble dans le code dans un modèle de domaine simple pour la gestion des étudiants.

    Montrez-moi le code

    Alors, à quoi ressemble ce port de référentiel dans le cadre du domaine ? Il définit essentiellement les attentes du domaine envers le référentiel, ayant toutes les méthodes définies en termes de langage ubiquitaire du domaine :

    public interface StudentRepository {
    
        Student save(Student student);
        Optional<Student> retrieveStudentWithEmail(ContactInfo contactInfo);
        Publisher<Student> saveReactive(Student student);
    
    }

    Sur la base de la spécification du port du référentiel, il est possible de créer la définition de test d’intégration, qui dépend uniquement du port et est indépendante de toute décision technologique sous-jacente prise pour conserver l’état du domaine. Cette classe de test aura une propriété en tant qu’instance de l’interface de référentiel (port) sur laquelle les attentes sont vérifiées. L’image suivante montre à quoi ressemblent ces tests :

    public class StudentRepositoryTest {
    
        StudentRepository studentRepository;
    
        @Test
        public void shouldCreateStudent() {
            Student expected = randomNewStudent();
            Student actual = studentRepository.save(expected);
    
            assertAll("Create Student",
                    () -> assertEquals(0L, actual.getVersion()),
                    () -> assertEquals(expected.getStudentName(), actual.getStudentName()),
                    () -> assertNotNull(actual.getStudentId())
            );
        }
    
        @Test
        public void shouldUpdateExistingStudent() {
            Student expected = randomExistingStudent();
            Student actual = studentRepository.save(expected);
            assertAll("Update Student",
                    () -> assertEquals(expected.getVersion()+1, actual.getVersion()),
                    () -> assertEquals(expected.getStudentName(), actual.getStudentName()),
                    () -> assertEquals(expected.getStudentId(), actual.getStudentId())
            );
        }
    }

    Une fois la définition du test du référentiel terminée, nous pouvons créer un runtime de test (test d’intégration) pour le référentiel en mémoire :

    public class StudentRepositoryInMemoryIT extends StudentRepositoryTest {
    
        @BeforeEach
        public void setup() {
            super.studentRepository = new StudentRepositoryInMemory();
        }
    
    }

    Ou un test d’intégration un peu plus élaboré pour JPA avec Postgres :

    @Testcontainers
    @ContextConfiguration(classes = {PersistenceConfig.class})
    @DataJpaTest
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    public class StudentRepositoryJpaIT extends StudentRepositoryTest{
    
        @Autowired
        public StudentRepository studentRepository;
    
        @Container
        public static PostgreSQLContainer container = new PostgreSQLContainer("postgres:latest")
                .withDatabaseName("students_db")
                .withUsername("sa")
                .withPassword("sa");
    
    
        @DynamicPropertySource
        public static void overrideProperties(DynamicPropertyRegistry registry){
            registry.add("spring.datasource.url", container::getJdbcUrl);
            registry.add("spring.datasource.username", container::getUsername);
            registry.add("spring.datasource.password", container::getPassword);
            registry.add("spring.datasource.driver-class-name", container::getDriverClassName);
        }
    
        @BeforeEach
        public void setup() {
            super.studentRepository = studentRepository;
        }
    }

    Les deux exécutions de test étendent la même définition de test, nous pouvons donc être sûrs que, lors du passage de l’adaptateur en mémoire à la persistance complète JPA finale, aucun test ne sera affecté car il suffit de configurer l’exécution de test correspondante .

    Cette approche nous permettra de définir les tests du portage du référentiel sans aucune dépendance aux frameworks et de réutiliser ces tests une fois que le domaine sera mieux défini, étant plus stable, et que l’équipe décidera d’aller de l’avant avec la technologie de base de données qui répond mieux à la qualité de la solution les attributs.

    La structure globale du projet est illustrée dans l’image suivante :

    Image

    Où:

    • student-domain: Module avec la définition du domaine, y compris les entités, les objets de valeur, les événements de domaine, les ports, etc. Ce module n’a aucune dépendance avec les frameworks, étant aussi pur Java que possible.
    • student-application: Actuellement, ce module n’a pas de code car il était hors de portée de l’article. Suivant une architecture hexagonale, ce module orchestre les invocations au modèle de domaine, étant le point d’entrée des cas d’utilisation du domaine. Les prochains articles entreront plus de détails.
    • student-repository-test: Ce module contient les définitions de test du référentiel, sans dépendances sur les frameworks, et vérifie uniquement l’attente du port de référentiel fourni.
    • student-repository-inmemory: Implémentation en mémoire du port du référentiel défini par le domaine. Il contient également le test d’intégration, qui fournit l’adaptateur en mémoire du port à la définition de test du student-repository-test.
    • student-repository-jpa: Implémentation JPA du port du référentiel défini par le domaine. Il contient également le test d’intégration, qui fournit l’adaptateur en mémoire du port à la définition de test du student-repository-test. Cette configuration de test d’intégration est un peu plus complexe car elle crée un contexte Spring de base avec un conteneur Postgres.
    • student-shared-kernel: Ce module est hors de portée de l’article ; il fournit des classes utilitaires et des interfaces pour concevoir le reste du projet.

    Conclusion

    L’utilisation de ce style architectural pour vos projets favorise une bonne séparation entre le modèle de domaine et les éléments d’infrastructure qui l’entourent, garantissant que ce dernier n’influencera pas le premier tout en favorisant une bonne qualité de code (code propre) et une maintenabilité élevée.

    Le code de cet article se trouve dans mon perso Référentiel GitHub.

    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.