Commit 0d963723 authored by Julien SADAOUI's avatar Julien SADAOUI
Browse files

feat: spring-reactor wording

parent 736d1fd3
# spring-reactor
This is a sample demonstrating Spring 5 Webflux framework. It uses Spring Data Mongo’s support for reactive libraries to provide an end to end reactive flow.
# Coding Dojo : Reactor et Spring WebFlux
Enoncé : Spring 5 & Reactor
===========================
Bienvenue dans ce **Coding Dojo** ciblé sur l'usage de la programmation réactive, en utilisant le projet Reactor et le module Spring WebFlux.
L'application que nous développons sert à gérer des listes de comptes
Le but de cet exercice est d'être capable de construire une application avec un flux réactif de bout en bout, c'est-à-dire en partant par l'entrée de l'API REST jusqu'à la base de données.
Le framework Spring 5 permet de fournir des flux réactifs de bout en bout avec les nouveaux modules Spring WebFlux, Spring Data Mongo Reactive et Spring Security.
- Il y a plusieurs utilisateurs
- Chaque utilisateur peut avoir plusieurs comptes
- Ces comptes peuvent être partagées entre plusieurs utilisateurs
- Chaque compte contient plusieurs transactions
L'application est déjà en partie codée:
## 1. Cas d'utilisation
L'application que nous développons dans ce **Coding Dojo** permet de gérer des comptes d'un établissement bancaire. La modélisation de l'application est très simpliste, nous nous concentrons sur l'utilisation de la programmation réactive avec Reactor et Spring WebFLux.
Depuis le début, nous avons fait uniquement des traitements avec des appels synchrones. En mode Servlet, un thread
est monopolisé pendant tout le traitement d'une requête HTTP qu'il soit en traitement ou en attente d'entrées/sorties
(accès en base de données, lecture de fichier, appel réseau, etc.)
- **User** : représente un utilisateur connecté à l'application.
- **Account** : représente un compte bancaire associé à un client particulier
- **Transaction** : représente une opération de débit ou crédit sur un compte particulier. Cette opération affecte le montant du compte bancaire.
Maintenant, nous allons utiliser les technologies Spring WebFlux et Reactor pour ajouter et afficher des Todos contenus
dans ces listes avec des traitements aynchrones.
Notre application recherchera des données dans une base de données MongoDB pour renseigner notre compte et renvoyer ces informations. Les URI suivants devront fonctionner:
- GET (app IP)/account/ : affiche chaque compte de la base de données
- GET (app IP)/account/{number} : affiche les informations d'un compte particulier en base de données
- POST (app IP)/account : crée un nouveau compte dans la base de données
- DELETE (app IP)/account : supprime un compte particulier de la base de données
- GET (app IP)/account/{number}/transaction : affiche chaque transaction d'un compte particulier en base de données
- GET (app IP)/account/{number}/transaction/credit : crédite un compte particulier en base de données
- GET (app IP)/account/{number}/transaction/debit : débite un compte particulier en base de données
I. Configuration de la sécurité HTTP
------------------------------------
La base MongoDB est embarquée dans l'application et déjà opérationnelle. Vous n'aurez pas besoin de ces informations car le pilote Mongo est déjà implémenté et n'est pas l'objet de ce **Coding Dojo**.
1. Dans la classe "SecurityConfiguration", activez la sécurité pour les applications réactives dans Spring
## 2. Frameworks
2. Toujours dans la classe "SecurityConfiguration", ajoutez la gestion des annotations de sécurité
Les frameworks utilisés pour ce coding dojo sont nombreux. Ci-dessous une liste des principaux :
3. Afin de récupérer les informations de l'utilisateur, ajoutez un Bean UserDetailsService.
- Utilisez l'implémentation MongoReactiveUserDetailsService permettant de récupérer l'utilisateur dans la base MongoDB
* Java 11
* JUnit 5
* Lombok 1.18.4
* Reactor 3.2.3
* Spring Boot 2.1.1
* Spring WebFlux 5.1.3
* Spring Security 5.1.2
* Netty 4.1.31
* MongoDB
* Mongobee
* Etc.
## 3. Enoncé
Passons maintenant au développement de ce **Coding Dojo**, vous trouverez dessous les instructions pour compléter cet exercice. L'application est déjà en partie codée, l'idée est de se concentrer sur les parties utilisant la programmation réactive avec le projet **Reactor** et le module **Spring WebFlux**.
### A. Configuration de la sécurité HTTP
- Dans la classe **SecurityConfiguration**, activez la sécurité pour les applications réactives dans Spring
- Toujours dans la classe **SecurityConfiguration**, ajoutez la gestion des annotations de sécurité
- Afin de récupérer les informations de l'utilisateur, ajoutez un Bean UserDetailsService.
- Utilisez l'implémentation **MongoReactiveUserDetailsService** permettant de récupérer l'utilisateur dans la base MongoDB
Vous devez maintenant être capables de vous authentifier dans l'application en mode réactif avec l'un des utilisateurs
créés par le Bean InitialSetupMigration (par exemple: philip@ippon.fr / Pa$$word1)
créés par le Bean InitialSetupMigration (par exemple: customer@email.org / Pa$$word1)
### B. Développement de la gestion des comptes
Nous allons coder la couche service de l'application. Elle permet de gérer les différents comptes bancaires et les opérations associées à ces comptes.
Partie II : Développement de la liste des comptes
-------------------------------------------------
- Implémentez la méthode **getCurrentUser** de la classe **UserServiceImp** pour retourner l'utilisateur connecté
- La classe helper **ReactiveSecurityContextHolder** fournit le nom de l'utilisateur connecté
Nous allons coder les couches Controller et Service permettant d'ajouter, supprimer et lister la liste des Comptes
pour l'utilisateur en cours.
- Implémentez les méthodes accessibles par un employé de la banque dans la classe **AccountServiceImpl**
- Ouverture d'un compte bancaire associé à un client particulier
- Fermeture d'un compte compte bancaire
- Lister l'ensemble des comptes de la banque
- Afficher les informations d'un compte particulier
1. Implémentez la méthode "getCurrentUser" de la classe "UserServiceImpl" pour retourner l'utilisateur en cours:
- La classe helper ReactiveSecurityContextHolder fournit le nom de l'utilisateur connecté
- Implémentez les méthodes accessibles par un client de la banque dans la classe **AccountServiceImpl**
- Création d'une opération de type débit sur un compte particulier
- Création d'une opération de type débit sur un compte particulier
- Lister les transactions d'un compte particulier
### C. Développement l'API REST
2. Implémentez les méthodes de la classe "AccountServiceImpl"
- Ajoutez un nouveau Compte et partagez-la à un utilisateur
- Ajoutez une nouvelle Transaction à un Compte.
- Récupérez la liste des Transactions associées à un Compte
- Implémentez l'API REST permettant de gérer un compte dans la classe **AccountResource**
| HTTP method | URI | Description | HTTP codes |
| ------------- | ----------------- | ------------------------------------ | ----------- |
| POST | /account/ | Ouverture d'un compte | 201 |
| DELETE | /account/{number} | Fermeture d'un compte | 204 |
| GET | /account/ | Affiche l'ensemble des comptes | 200 |
| GET | /account/{number} | Affiche les informations d'un compte | 200, 204 |
3. Complétez les méthodes de la couche Controller
- Implémentez l'API REST permettant de gérer les transactions d'un compte dans la classe **TransactionResource**
- Note: Ces ressources sont accessibles par un client, l'utilisateur connecté
| HTTP method | URI | Description | HTTP codes |
| ------------- | ------------------------------------ | -------------------------------------- | ---------- |
| POST | /account/{number}/transaction/credit | Crédite un compte | 201 |
| POST | /account/{number}/transaction/debit | Débite un compte | 201 |
| GET | /account/{number}/transaction | Affiche chaque transaction d'un compte | 200 |
Partie III : Création d'un test d'intégration
---------------------------------------------
### D. Création des tests d'intégration
Nous allons maintenant réaliser les tests d'intégration sur le controllers AccountController et TransactionController.
On exploite l'utilitaire WebTestClient pour tester le contrôleur ; l'API permet d'effectuer des traitements en mode asynchrone.
Nous allons maintenant réaliser les tests d'intégration sur les classes **AccountResource** et **TransactionResource**.
On exploite l'utilitaire **WebTestClient** pour tester les ressources REST ; l'API permet d'effectuer des traitements en mode asynchrone.
Elle offre en outre toute une panoplie d'outils permettant de vérifier les réponses obtenues.
- Configurez l'utilitaire WebTestClient
- Utilisez WebTestClient pour créer un nouveau Transaction
- Utilisez WebTestClient pour récupérer la liste des Transaction et vérifiez le flux de données avec la classe StepVerifier
\ No newline at end of file
- Configurez l'utilitaire **WebTestClient**
- Utilisez l'utilitaire **WebTestClient** pour réaliser des opérations asynchrones sur les ressources **REST**
- Utilisez l'utilitaire **StepVerifier** pour vérifier les flux de données.
\ No newline at end of file
......@@ -11,21 +11,20 @@ import org.springframework.data.mongodb.core.MongoTemplate;
@ChangeLog(order = "001")
public class InitialSetupMigration {
@ChangeSet(order = "01", author = "jsadaoui-demo", id = "01-addCustomers")
@ChangeSet(order = "01", author = "ippon", id = "01-addUsers")
public void addCustomers(MongoTemplate mongoTemplate) {
mongoTemplate.save(User.builder()
.email("customer@email.org")
.email("customer@bank.com")
.password("Pa$$word1")
.firstName("customer_first-name")
.lastName("customer_last-name")
.role("CUSTOMER")
.build());
mongoTemplate.save(User.builder()
.email("employee@email.org")
.email("employee@bank.com")
.password("Pa$$word1")
.firstName("employee_first-name")
.lastName("employee_last-name")
.role("USER")
.role("EMPLOYEE")
.build());
}
......
......@@ -80,7 +80,7 @@ public class AccountServiceImpl implements AccountService {
}
@Override
@PreAuthorize("hasAnyRole('EMPLOYEE','CUSTOMER')")
@PreAuthorize("hasAnyRole('CUSTOMER')")
public Flux<Transaction> getTransactions(String customerId, String accountNumber) {
return accountRepository.findByCustomerIdAndNumber(customerId, accountNumber)
.flatMapMany(account -> Flux.fromIterable(account.getTransactions()));
......
......@@ -43,7 +43,7 @@ public class AccountResource {
log.debug("REST request to get Account : {}", accountNumber);
return accountService.getAccount(accountNumber)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
.defaultIfEmpty(ResponseEntity.noContent().build());
}
@PostMapping
......
......@@ -21,6 +21,7 @@ import static org.springframework.security.test.web.reactive.server.SecurityMock
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@WithMockUser(username = "employee@bank.com", roles = {"EMPLOYEE"})
public class AccountResourceTest {
@Autowired
......@@ -43,17 +44,15 @@ public class AccountResourceTest {
}
@Test
@WithMockUser(username = "employee@email.org", roles = {"EMPLOYEE"})
@DisplayName("Should open account for given user")
@DisplayName("Should open new account")
public void should_open_new_account() {
createAccount("customer@email.org", "Checking Account", BigDecimal.valueOf(2000L));
createAccount("customer@bank.com", "Checking Account", BigDecimal.valueOf(2000L));
}
@Test
@WithMockUser(username = "employee@email.org", roles = {"EMPLOYEE"})
@DisplayName("Should close account for given user")
@DisplayName("Should close account")
public void should_close_account() {
Account account = createAccount("customer@email.org", "Checking Account", BigDecimal.TEN);
Account account = createAccount("customer@bank.com", "Checking Account", BigDecimal.TEN);
this.client
.mutateWith(csrf())
......@@ -67,16 +66,15 @@ public class AccountResourceTest {
.get()
.uri("/account/{number}", account.getNumber())
.exchange()
.expectStatus().isNotFound();
.expectStatus().isNoContent();
}
@Test
@WithMockUser(value = "employee@email.org", roles = {"EMPLOYEE"})
@DisplayName("Should get all accounts")
public void should_get_all_accounts_for_current_user() {
Account account1 = createAccount("customer@email.org", "Checking Account 1", BigDecimal.valueOf(1000L));
Account account2 = createAccount("customer@email.org", "Checking Account 2", BigDecimal.valueOf(2500L));
Account account3 = createAccount("customer@email.org", "Checking Account 3", BigDecimal.valueOf(500L));
Account account1 = createAccount("customer@bank.com", "Checking Account 1", BigDecimal.valueOf(1000L));
Account account2 = createAccount("customer@bank.com", "Checking Account 2", BigDecimal.valueOf(2500L));
Account account3 = createAccount("customer@bank.com", "Checking Account 3", BigDecimal.valueOf(500L));
FluxExchangeResult<Account> result = this.client
.mutateWith(csrf())
......@@ -93,10 +91,9 @@ public class AccountResourceTest {
}
@Test
@WithMockUser(value = "employee@email.org", roles = {"EMPLOYEE"})
@DisplayName("Should get given account")
public void should_get_account_for_current_user() {
Account account = createAccount("customer@email.org", "Checking Account", BigDecimal.valueOf(1000L));
Account account = createAccount("customer@bank.com", "Checking Account", BigDecimal.valueOf(1000L));
FluxExchangeResult<Account> result = this.client
//.mutateWith(csrf())
......
......@@ -24,6 +24,7 @@ import static org.springframework.security.test.web.reactive.server.SecurityMock
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@WithMockUser(value = "customer@bank.com", roles = {"CUSTOMER"})
public class TransactionResourceTest {
@Autowired
......@@ -61,22 +62,19 @@ public class TransactionResourceTest {
}
@Test
@WithMockUser(value = "customer@email.org", roles = {"CUSTOMER"})
@DisplayName("Should create credit transaction with current user")
@DisplayName("Should create credit transaction")
public void should_create_credit_transaction() {
createTransaction(Operation.CREDIT, BigDecimal.valueOf(210L));
}
@Test
@WithMockUser(value = "customer@email.org", roles = {"CUSTOMER"})
@DisplayName("Should create new debit transaction with current user")
@DisplayName("Should create debit transaction")
public void should_create_debit_transaction() {
createTransaction(Operation.DEBIT, BigDecimal.valueOf(150L));
}
@Test
@WithMockUser(value = "customer@email.org", roles = {"CUSTOMER"})
@DisplayName("Should get all transactions for current user")
@DisplayName("Should get all transactions")
public void should_get_all_transactions() {
Transaction transaction1 = createTransaction(Operation.CREDIT, BigDecimal.valueOf(20L));
Transaction transaction2 = createTransaction(Operation.DEBIT, BigDecimal.valueOf(30L));
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment