SpazioCodice

Ce conseil fournit un code de démarrage minimaliste pour configurer un module utilisant Spring Boot, GraphQL, PostgreSQL et TestContainers.

Spring Boot, GraphQL, TestContainers, PostgreSQL : Configuration Minimale

Le but de ce conseil rapide est de fournir un code de démarrage minimaliste pour configurer un module utilisant :

  • Spring Boot
  • GraphQL
  • PostgreSQL
  • TestContainers

Le code fonctionne, et le test est validé, mais, comme vous pouvez l’imaginer, la logique est très triviale car il s’agit uniquement de fournir un point de départ.

Le code est disponible ici sur GitHub. Je ferai référence à ce dépôt dans ce qui suit.

Définition du projet Maven

Le fichier pom.xml inclut les dépendances nécessaires. Notez les points suivants, que je trouve souvent très utiles :

  • Les versions des dépendances sont déclarées en tant que propriétés. Cela permet de les gérer à un point unique et centralisé
 
  • Le plugin release déclenche, via un profil dédié, la construction et la publication de l’image Docker du module. À la fin de la release, il y aura l’artifact dans le dépôt Maven et l’image Docker dans le registre de conteneurs, tous deux versionnés. Notez que cette fonctionnalité est commentée car le code n’inclut pas les identifiants pour lire/écrire dans un registre de conteneurs
 
  • Le profil build-and-publish peut également être déclenché de manière autonome : ainsi, Maven publiera une image Docker en version SNAPSHOT
				
					        <profile>
            <id>build-and-publish-images</id>
            <build>
                <plugins>
                    ...
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>exec-maven-plugin</artifactId>
                        <version>3.0.0</version>
                        <executions>
                            <execution>
                                <id>docker-build</id>
                                ...
                            </execution>
                            <execution>
                                <id>docker-login</id>
                                ...
                            </execution>
                            <execution>
                                <id>docker-push</id>
                                ...
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
				
			

GraphQL Schema

Le schéma GraphQL se trouve dans /src/main/resources/graphql/schema.graphqls. Spring Boot le lit automatiquement. Si votre domaine est vaste, le schéma peut être divisé en plusieurs fichiers. Cependant, ce n’est pas nécessaire ici, car nous avons uniquement un type et un champ de requête.

Entité JPA et Référentiel Spring Data

Le modèle de domaine se compose d’une seule entité simple, un Bass. En plus de l’entité, le référentiel Spring Data correspondant fournit automatiquement les modèles d’accès aux données courants.

Notez l’utilisation de Lombok, un excellent outil pour réduire le code répétitif.

Contrôleur

Le point d’entrée de l’API est un Contrôleur Spring, qui mappe les champs de requête du schéma et fournit l’implémentation correspondante.
				
					@Controller
public class ShopController {
    ...
}
				
			

Test : ApplicationInitializer

L’ApplicationInitializer est un rappel Spring au début de la suite de tests. Le module utilise TestContainers pour déployer le middleware requis dans les tests, en l’occurrence PostgreSQL.

				
					public class MiddlewareSetup implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    public static DockerComposeContainer SUBSYSTEMS;

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        if (SUBSYSTEMS == null) {
            var dockerCompose = new File(System.getProperty("user.dir"), "src/test/resources/docker/docker-compose.yml");
            SUBSYSTEMS =
                    new DockerComposeContainer(dockerCompose)
                            .withExposedService(
                                    "postgres_1",
                                    5432,
                                    Wait.forListeningPort().withStartupTimeout(Duration.ofMinutes(5)));
            SUBSYSTEMS.start();
        }
   }
}
				
			

Test : Docker compose

Notez la définition du volume : elle utilise un script pour initialiser et pré-remplir la base de données de test.

				
					version: '3.3'

services:
  postgres:
    image: library/postgres:14.5
    environment:
      - POSTGRES_USER=shop_owner
      - POSTGRES_PASSWORD=029348hdhj
      - POSTGRES_DB=bass_shop
    command: postgres -c max_connections=300 -c log_min_messages=LOG
    volumes:
      - ./populate_shop.sql:/docker-entrypoint-initdb.d/populate_shop.sql
    ports:
      - "5432:5432"
    tty: true
				
			

Test : Superclass

L’IntegrationTestCaseSuperLayer inclut les annotations et les composants “autowired” dont nous avons besoin dans tous les cas de test.

				
					@SpringBootTest(
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        classes = ElectricBassShop.class)
@ContextConfiguration(initializers = MiddlewareSetup.class)
@AutoConfigureMockMvc
@AutoConfigureGraphQlTester
@ActiveProfiles("it")
public abstract class IntegrationTestCaseSuperLayer {

    @Autowired
    protected HttpGraphQlTester tester;

    @Autowired
    protected MockMvc mvc;
}
				
			

Cas de Test

Et enfin, voici notre cas de test trivial.

				
					public class ShopTestCase extends IntegrationTestCaseSuperLayer {
    @Test
    void findSomeBasses() {
        String query =
                "query {" +
                "  instruments {" +
                "      id" +
                "      brand" +
                "      model" +
                "      fretless" +
                "      strings" +
                "  }" +
                "}";
        var basses =
                tester.document(query)
                        .execute()
                        .path("data.instruments[*]")
                        .entityList(Bass.class)
                        .get();
        assertThat(basses).asList().hasSize(3);

        // Not so robust/complete as test...
        var fenderJazz =
                of(basses.iterator())
                        .map(Iterator::next)
                        .map(Bass.class::cast)
                        .orElseThrow();
        assertThat(fenderJazz.getStrings()).isEqualTo(4);
        assertThat(fenderJazz.getModel()).isEqualTo("Jazz Vintage 62");
        assertThat(fenderJazz.getBrand()).isEqualTo("Fender");
        assertThat(fenderJazz.isFretless()).isFalse();
    }
}
				
			

Share this post

Laisser un commentaire

En savoir plus sur SpazioCodice

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Poursuivre la lecture