Implémentation d'une architecture multi-tiers avec Spring
KX
Messages postés
16752
Date d'inscription
samedi 31 mai 2008
Statut
Modérateur
Dernière intervention
31 août 2024
-
21 mai 2022 à 12:05
Cet article a pour but d’expliquer le fonctionnement d'une application serveur JEE avec une architecture multi-tiers en utilisant le framework Spring.
NB. Le projet est configuré avec Maven.
Interface utilisateur de l'application :
Le code complet et son mode d'emploi est disponible sur CodeS-SourceS :
https://codes-sources.commentcamarche.net/source/103029-implementation-d-une-architecture-multi-tiers-en-jee-avec-spring
Remarques :
Ci-dessous un schéma représentant les interactions entre les classes, notamment les conversions d'objets d'un service à l'autre (en pointillés).
Tout d'abord, s'agissant d'un projet Spring-Boot, il est nécessaire que le pom parent hérite de spring-boot-starter-parent :
Le nom de l'artifactId n'a en soit que peu d'importance pour le pom parent, en revanche le groupId sera partagé par tous les autres modules.
Pour fonctionner avec Spring-Boot il est nécessaire que la compilation et l'exécution se fasse au minimum en Java 8.
Ensuite il est nécessaire de déclarer les différents sous-modules (voir le schéma d'architecture plus haut).
Pour éviter de gérer les versions dans les sous-modules, il est utile de les déclarer dans le pom parent.
Etape facultative mais plutôt pratique, j'ajoute une dépendance sur Lombok (applicable à tous les modules) afin de générer automatiquement une partie du code.
Afin de gérer plusieurs profils applicatifs (local, dev, prod) on valorise la variable spring.profiles.active
Pour générer un jar exécutable, on déclare spring-boot-maven-plugin ainsi que la classe principale pour le manifest.
Quant aux dépendances, nous aurons besoin de spring-boot-starter ainsi que des web-services à déployer (cf. schéma d'architecture).
Selon sa valeur le fichier de propriétés correspondant sera sélectionné.
Exemple avec le profil "prod", le fichier application-prod.properties est sélectionné.
Notez cependant qu'il est exposé par le module "server" et fonctionne grâce à des ressources REST déclarées dans le module "web-service-api" et implémentées dans le module "web-services-impl".
Ici on précise le nom de la table auquel l'objet est lié.
Et ici on décrit chaque champs de la table pour l'associer à un attribut de la classe.
Pour le fonctionnement de la base de données, il est également nécessaire d'intégrer H2 (au runtime uniquement), ainsi que Liquibase pour la construire.
Sans rentrer dans le détail du fonctionnement de Liquibase, notez qu'il existe une table "databasechangelog" qui va référencer tous les "changeset" joués sur la base de données.
Lors du démarrage de l'application, si un nouveau "changeset" est référencé dans le code, alors il sera automatiquement joué.
Remarque : l'annotation @Data (de Lombok) permet de générer automatiquement les getter/setter, ainsi que les méthodes hashCode, equals et toString.
Exemple: http://api-adresse.data.gouv.fr/search?q=5+Avenue+Anatole+France+75007+Paris
La transcription en Java donne ainsi :
Il requiert également l'accès au framework Spring.
Il requiert également la partie web de Spring Boot.
Remarque : il est possible de créer un fichier application-test.properties mais dans cet exemple les valeurs par défaut suffiront.
Stack technique
Pour le programme d'exemple j'ai utilisé les technologies Jakarta EE (anciennement Java EE) et Spring suivantes :- Java 8
- Spring-Boot 2.2.6
- Spring-Framework 5.2.5
- Jakarta Persistence (JPA) 2.2.3
- H2 1.4.200
- Liquibase 3.8.8
- Lombok 1.18.12
NB. Le projet est configuré avec Maven.
Table des matières
- Présentation de l'exemple
- Architecture
- Explication de code
Présentation de l'exemple
Le programme détaillé ci-dessous permet de faire 3 actions :- Chercher une adresse en s'appuyant sur un service tiers : https://adresse.data.gouv.fr
- Enregistrer un utilisateur (avec son adresse) en base de données
- Lister tous les utilisateurs précédemment enregistrés
Interface utilisateur de l'application :
Le code complet et son mode d'emploi est disponible sur CodeS-SourceS :
https://codes-sources.commentcamarche.net/source/103029-implementation-d-une-architecture-multi-tiers-en-jee-avec-spring
Architecture
Ci-dessous un schéma représentant l'architecture globale de l'application avec les différentes briques logicielles et leurs interactions.Remarques :
- Tous les modules héritent du même pom parent, non représenté sur ce schéma.
- À la compilation seuls les modules "api" sont visibles (scope provided) alors que ce sont les modules "impl" qui sont exécutés (scope runtime).
Ci-dessous un schéma représentant les interactions entre les classes, notamment les conversions d'objets d'un service à l'autre (en pointillés).
Explication de code
Pom parent
pom.xml
Le fichier pom.xml à la racine du projet permet de déclarer un certain nombre de propriétés communes à tous les modules qui en héritent.Tout d'abord, s'agissant d'un projet Spring-Boot, il est nécessaire que le pom parent hérite de spring-boot-starter-parent :
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> </parent>
Le nom de l'artifactId n'a en soit que peu d'importance pour le pom parent, en revanche le groupId sera partagé par tous les autres modules.
<groupId>ccm.kx.users</groupId> <artifactId>pom-parent</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>pom</packaging>
Pour fonctionner avec Spring-Boot il est nécessaire que la compilation et l'exécution se fasse au minimum en Java 8.
<properties> <java.version>1.8</java.version> </properties>
Ensuite il est nécessaire de déclarer les différents sous-modules (voir le schéma d'architecture plus haut).
<modules> <module>web-clients-api</module> <module>web-clients-impl</module> <module>datas-api</module> <module>datas-impl</module> <module>services-api</module> <module>services-impl</module> <module>web-services-api</module> <module>web-services-impl</module> <module>server</module> </modules>
Pour éviter de gérer les versions dans les sous-modules, il est utile de les déclarer dans le pom parent.
<dependencyManagement> <dependencies> <dependency> <groupId>ccm.kx.users</groupId> <artifactId>web-clients-api</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>ccm.kx.users</groupId> <artifactId>web-clients-impl</artifactId> <version>${project.version}</version> </dependency> ... </dependencies> </dependencyManagement>
Etape facultative mais plutôt pratique, j'ajoute une dépendance sur Lombok (applicable à tous les modules) afin de générer automatiquement une partie du code.
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies>
Module "server"
UsersServerApplication.java
Le point d'entrée de l'application est définie par la classe UsersServerApplication, c'est le seul code du module "server".package ccm.kx.users; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class UsersServerApplication { public static void main(String[] args) { SpringApplication.run(UsersServerApplication.class, args); } }
pom.xml
Au niveau de la configuration dans le pom.xml on déclarera bien sûr que le module hérite du pom parent.<modelVersion>4.0.0</modelVersion> <parent> <groupId>ccm.kx.users</groupId> <artifactId>pom-parent</artifactId> <version>0.0.1-SNAPSHOT</version> </parent>
Afin de gérer plusieurs profils applicatifs (local, dev, prod) on valorise la variable spring.profiles.active
<profiles> <profile> <id>local</id> <activation> <activeByDefault>true</activeByDefault> </activation> <properties> <spring.profiles.active>local</spring.profiles.active> </properties> </profile> <profile> <id>dev</id> <properties> <spring.profiles.active>dev</spring.profiles.active> </properties> </profile> <profile> <id>prod</id> <properties> <spring.profiles.active>prod</spring.profiles.active> </properties> </profile> </profiles>
Pour générer un jar exécutable, on déclare spring-boot-maven-plugin ainsi que la classe principale pour le manifest.
<build> <finalName>${artifactId}-${version}-${spring.profiles.active}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>ccm.kx.users.UsersServerApplication</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build>
Quant aux dépendances, nous aurons besoin de spring-boot-starter ainsi que des web-services à déployer (cf. schéma d'architecture).
<dependencies> <dependency> <groupId>ccm.kx.users</groupId> <artifactId>web-services-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>ccm.kx.users</groupId> <artifactId>web-services-impl</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies>
application.properties
Le fichier application.properties ne contient qu'une seule ligne, pour déclarer le profil applicatif en cours.spring.profiles.active=@spring.profiles.active@
Selon sa valeur le fichier de propriétés correspondant sera sélectionné.
Exemple avec le profil "prod", le fichier application-prod.properties est sélectionné.
server.port=8082 logging.level.web=WARN spring.datasource.url=jdbc:h2:file:~/.h2/prod/ccm.kx.users/users-prod spring.datasource.username=prod spring.datasource.password=H3ll0W0rld!
index.html
Je ne vais pas rentrer dans le détail de ce fichier HTML qui dépasse le cadre de cet article concernant Java.Notez cependant qu'il est exposé par le module "server" et fonctionne grâce à des ressources REST déclarées dans le module "web-service-api" et implémentées dans le module "web-services-impl".
Module "datas-api"
pom.xml
Ce module requiert une dépendance sur les spécifications de JPA.<dependencies> <dependency> <groupId>jakarta.persistence</groupId> <artifactId>jakarta.persistence-api</artifactId> <scope>provided</scope> </dependency> </dependencies>
UserEntity.java
UserEntity est une représentation Objet d'une ligne de la table "user" en base de données.Ici on précise le nom de la table auquel l'objet est lié.
@Entity @Table(name = "user") public class UserEntity {
Et ici on décrit chaque champs de la table pour l'associer à un attribut de la classe.
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @Column(name = "first_name") private String firstName; ...
UsersDao.java
UsersDao est une interface (qui sera implémentée dans "datas-impl") qui fournit des méthodes accessibles aux autres modules, en l’occurrence pour enregistrer un utilisateur et les lister tous.public interface UsersDao { public UserEntity save(UserEntity entity); public List<UserEntity> findAll(); }
Module "datas-impl"
pom.xml
Ce module requiert le module datas-api, ainsi que spring-boot-starter-data-jpa qui fournit une implémentation de JPA (basé sur Hibernate).<dependency> <groupId>ccm.kx.users</groupId> <artifactId>datas-api</artifactId> </dependency> <!-- spring-boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
Pour le fonctionnement de la base de données, il est également nécessaire d'intégrer H2 (au runtime uniquement), ainsi que Liquibase pour la construire.
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.liquibase</groupId> <artifactId>liquibase-core</artifactId> <scope>runtime</scope> </dependency>
db.changelog-master.yaml
Pour construire la base de données, nous utilisons Liquibase qui fonctionne grâce à des "changeset".databaseChangeLog: - include: file: db/changelog/db.changelog-1.0.yaml
Sans rentrer dans le détail du fonctionnement de Liquibase, notez qu'il existe une table "databasechangelog" qui va référencer tous les "changeset" joués sur la base de données.
Lors du démarrage de l'application, si un nouveau "changeset" est référencé dans le code, alors il sera automatiquement joué.
db.changelog-1.0.yaml
Dans notre exemple, il n'y a qu'un seul changeset : la création de la table "user".databaseChangeLog: - changeSet: id: createTable-user author: KX changes: - createTable: tableName: user columns: - column: name: id type: BIGINT autoIncrement: true constraints: primaryKey: true - column: name: first_name type: VARCHAR(255) constraints: nullable: false ...
UserRepository.java
Cette interface sera instanciée par Spring, elle fournit des méthodes de base pour manipuler la table "user" au travers de la classe UserEntity.public interface UserRepository extends JpaRepository<UserEntity, Long> { }
UsersDaoImpl.java
L'implémentation de UserDao (tel que définit dans le module "datas-api") se base sur les méthodes de UserRepository.@Service public class UsersDaoImpl implements UsersDao { @Autowired private UserRepository userRepository; @Override public UserEntity save(UserEntity entity) { return userRepository.save(entity); } @Override public List<UserEntity> findAll(){ return userRepository.findAll(); } }
Module "web-clients-api"
pom.xml
Ce module n'a aucune dépendance (excepté Lombok, hérité du pom parent).SearchedAddressDto.java
On créé ici un DTO (Data Transfer Object) pour manipuler une adresse.@Data public class SearchedAddressDto { private String street; private String postcode; private String city; private Double score; }
Remarque : l'annotation @Data (de Lombok) permet de générer automatiquement les getter/setter, ainsi que les méthodes hashCode, equals et toString.
AddressWebClient.java
AddressWebClient est une interface (qui sera implémentée dans "web-clients-impl") qui fournit des méthodes accessibles aux autres modules, en l’occurrence pour chercher une adresse.public interface AddressWebClient { public List<SearchedAddressDto> searchAddress(String address); }
Module "web-clients-impl"
pom.xml
Ce module utilise "web-clients-api" dont il implémente les méthodes en se basant sur "spring-boot-starter-web".<dependencies> <dependency> <groupId>ccm.kx.users</groupId> <artifactId>web-clients-api</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
ResultDto.java, FeatureDto.java, PropertiesDto.java
Les classes ResultDto, FeatureDto et PropertiesDto représentent en Java le JSON consommé par le web-service.Exemple: http://api-adresse.data.gouv.fr/search?q=5+Avenue+Anatole+France+75007+Paris
{ "features": [ { "properties": { "score": 0.9566517682452214, "label": "5 Avenue Anatole France 75007 Paris", "name": "5 Avenue Anatole France", "postcode": "75007", "city": "Paris" } } ] }
La transcription en Java donne ainsi :
@Data @JsonIgnoreProperties(ignoreUnknown = true) public class ResultDto { private List<FeatureDto> features; } @Data @JsonIgnoreProperties(ignoreUnknown = true) public class FeatureDto { private PropertiesDto properties; } @Data @JsonIgnoreProperties(ignoreUnknown = true) public class PropertiesDto { private Double score; private String label; private String name; private String postcode; private String city; }
SearchedAddressDtoMapper.java
Cette classe permet de transformer le ResultDto (et ses composants FeatureDto, PropertiesDto) vers les SearchedAddressDto de "webclients-api".@Component public class SearchedAddressDtoMapper { public List<SearchedAddressDto> toDto(ResultDto result) { if (result == null) return null; ... } }
AddressWebClientImpl.java
L'implémentation de AddressWebClient (tel que définit dans le module "web-client-api") se base sur les RestTemplateBuilder de Spring.@Service public class AddressWebClientImpl implements AddressWebClient { private static final String SEARCH_URL = "https://api-adresse.data.gouv.fr/search/?q=%s"; @Autowired private RestTemplateBuilder restTemplateBuilder; @Autowired private SearchedAddressDtoMapper addressMapper; @Override public List<SearchedAddressDto> searchAddress(String query) { String url = String.format(SEARCH_URL, query); ResultDto dto = restTemplateBuilder.build().getForObject(url, ResultDto.class); return addressMapper.toDto(dto); } }
Module "services-api"
pom.xml
Ce module n'a aucune dépendance (excepté Lombok, hérité du pom parent).AddressBean.java
Cette classe, très proche de UserEntity, ne comporte aucune donnée technique liée à la base de données.@Data public class UserBean { private Long id; private String firstName; private String lastName; private String street; private String postCode; private String city; }
UserService.java
UserService est une interface (qui sera implémentée dans "services-impl") qui fournit des méthodes accessibles aux autres modules, en l’occurrence pour enregistrer un utilisateur et les lister tous.public interface UserService { List<UserBean> getAllUsers(); UserBean save(UserBean user); }
AddressBean.java
Cette classe, identique à SearchedAddressDto dans cet exemple, pourrait présenter des différences dans une application plus complexe.@Data public class AddressBean { private String street; private String postcode; private String city; private Double score; }
AddressService.java
AddressService est une interface (qui sera implémentée dans "services-impl") qui fournit des méthodes accessibles aux autres modules, en l’occurrence pour chercher une adresse.public interface AddressService { List<AddressBean> searchAddress(String address); }
Module "services-impl"
pom.xml
Ce module utilise "services-api" dont il implémente les méthodes en se basant sur "datas-api" (via "datas-impl") et "web-clients-api" (via "web-clients-impl").Il requiert également l'accès au framework Spring.
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency>
UserBeanMapper.java
Cette classe fournit des méthodes de conversion entre UserEntity et UserBean. Elle utilise la classe BeanUtils de Spring.@Component public class UserBeanMapper { public UserEntity fromBean(UserBean bean) { if (bean == null) return null; UserEntity entity = new UserEntity(); BeanUtils.copyProperties(bean, entity); return entity; } public UserBean toBean(UserEntity entity) { if (entity == null) return null; UserBean bean = new UserBean(); BeanUtils.copyProperties(entity, bean); return bean; } }
UserServiceImpl.java
L'implémentation de UserService (tel que définit dans le module "services-api") se base sur UsersDao de "datas-api".@Service public class UserServiceImpl implements UserService { @Autowired private UsersDao usersDao; @Autowired private UserBeanMapper userMapper; @Override public List<UserBean> getAllUsers(){ List<UserEntity> usersEntities = usersDao.findAll(); List<UserBean> usersBeans = usersEntities.stream().map(userMapper::toBean).collect(Collectors.toList()); return usersBeans; } @Override public UserBean save(UserBean user) { UserEntity userEntity = userMapper.fromBean(user); userEntity = usersDao.save(userEntity); return userMapper.toBean(userEntity); } }
AddressBeanMapper.java
Cette classe fournit une méthode de conversion entre SearchedAddressDto et AddressBean. Elle utilise la classe BeanUtils de Spring.@Component public class AddressBeanMapper { public AddressBean toBean(SearchedAddressDto dto) { if (dto == null) return null; AddressBean bean = new AddressBean(); BeanUtils.copyProperties(dto, bean); return bean; } }
AddressServiceImpl.java
L'implémentation de AddressService (tel que définit dans le module "services-api") se base sur AddressWebClient de "web-clients-api".@Service public class AddressServiceImpl implements AddressService { @Autowired private AddressWebClient addressWebClient; @Autowired private AddressBeanMapper addressMapper; @Override public List<AddressBean> searchAddress(String address) { List<SearchedAddressDto> addresses = addressWebClient.searchAddress(address); if (addresses == null) return Collections.emptyList(); return addresses.stream().map(addressMapper::toBean).filter(Objects::nonNull).collect(Collectors.toList()); } }
Module "web-services-api"
pom.xml
Ce module requiert spring-web pour la déclaration des web-services.<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <scope>provided</scope> </dependency> </dependencies>
UserDto.java
La classe Java décrivant le format des données JSON qui vont transiter via les services REST de manipulation des utilisateurs.@Data public class UserDto { private Long id; private String firstName; private String lastName; private String street; private String postCode; private String city; }
UserWebService.java
UserWebService est une interface (qui sera implémentée dans "web-services-impl") qui décrit les services REST utilisables pour manipuler des utilisateurs.@RestController public interface UserWebService { @GetMapping("/users/all") public List<UserDto> getAllUsers(); @PostMapping("/users/new") public UserDto postUser(@RequestBody UserDto user); }
AddressDto.java
La classe Java décrivant le format des données JSON qui vont transiter via le web-service de recherche d'adresses.@Data public class AddressDto { private String street; private String postcode; private String city; private Double score; }
AddressWebService.java
AddressWebService est une interface (qui sera implémentée dans "web-services-impl") qui décrit le service REST utilisables pour chercher une adresse.@RestController public interface AddressWebService { @GetMapping("/address/search") public List<AddressDto> searchAddress(@RequestParam String address); }
Module "web-services-impl"
pom.xml
Ce module utilise "web-services-api" dont il implémente les méthodes en se basant sur "services-api" (via "services-impl").Il requiert également la partie web de Spring Boot.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
UserDtoMapper.java
Cette classe fournit des méthodes de conversion entre UserBean et UserDto. Elle utilise la classe BeanUtils de Spring.@Component public class UserDtoMapper { public UserDto toDto(UserBean bean) { if (bean == null) return null; UserDto dto = new UserDto(); BeanUtils.copyProperties(bean, dto); return dto; } public UserBean fromDto(UserDto dto) { if (dto == null) return null; UserBean bean = new UserBean(); BeanUtils.copyProperties(dto, bean); return bean; } }
UserWebServiceImpl.java
L'implémentation de UserWebService (tel que définit dans le module "web-services-api") se base sur UserService de "services-api".@Service public class UserWebServiceImpl implements UserWebService { @Autowired private UserService userService; @Autowired private UserDtoMapper userMapper; @Override public List<UserDto> getAllUsers(){ List<UserBean> allUsersBeans = userService.getAllUsers(); List<UserDto> allUsersDtos = allUsersBeans.stream().map(userMapper::toDto).collect(Collectors.toList()); return allUsersDtos; } @Override public UserDto postUser(UserDto dto) { UserBean bean = userMapper.fromDto(dto); bean = userService.save(bean); dto = userMapper.toDto(bean); return dto; } }
AddressDtoMapper.java
Cette classe fournit une méthode de conversion entre AddressBean et AddressDto. Elle utilise la classe BeanUtils de Spring.@Component public class AddressDtoMapper { public AddressDto toDto(AddressBean bean) { if (bean == null) return null; AddressDto dto = new AddressDto(); BeanUtils.copyProperties(bean, dto); return dto; } }
AddressWebServiceImpl.java
L'implémentation de AddressWebServiceImpl (tel que définit dans le module "web-services-api") se base sur AddressService de "services-api".@Service public class AddressWebServiceImpl implements AddressWebService { @Autowired private AddressService addressService; @Autowired private AddressDtoMapper addressMapper; @Override public List<AddressDto> searchAddress(String address) { List<AddressBean> bean = addressService.searchAddress(address); List<AddressDto> dtos = bean.stream().map(addressMapper::toDto).collect(Collectors.toList()); return dtos; } }
Module "server" (tests de l'application)
pom.xml
Pour effectuer des tests automatiques avec Spring-Boot, il faut ajouter la dépendance suivante au module "server"<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
application.properties
Pour ne pas que les tests s'effectuent sur les bases de données déjà configurée (local, dev, ou prod) il faut utiliser un nouveau profil : testspring.profiles.active=test
Remarque : il est possible de créer un fichier application-test.properties mais dans cet exemple les valeurs par défaut suffiront.
UsersServerApplicationTest.java
Le code de test couvre la quasi totalité de l'application, en particulier en appelant à l'api-adresse et en stockant un utilisateur dans une base de données temporaire.@SpringBootTest public class UsersServerApplicationTest { @Autowired private AddressWebService addressWebService; @Autowired private UserWebService userWebService; @Test public void testAddressWebService(){ List<AddressDto> addresses = addressWebService.searchAddress("5 Avenue Anatole France 75007 Paris"); AddressDto firstAddress = addresses.get(0); Assertions.assertEquals("5 Avenue Anatole France", firstAddress.getStreet()); Assertions.assertEquals("75007", firstAddress.getPostcode()); Assertions.assertEquals("Paris", firstAddress.getCity()); Assertions.assertTrue(firstAddress.getScore() > 0.9); } @Test public void testUserWebService(){ Assertions.assertEquals(Collections.emptyList(), userWebService.getAllUsers()); UserDto user = new UserDto(); user.setFirstName("Gustave"); user.setLastName("Eiffel"); user.setStreet("5 Avenue Anatole France"); user.setPostCode("75007"); user.setCity("Paris"); UserDto createdUser = userWebService.postUser(user); user.setId(1L); Assertions.assertEquals(user, createdUser); List<UserDto> users = userWebService.getAllUsers(); Assertions.assertEquals(1, users.size()); Assertions.assertEquals(user, users.get(0)); } }