7 - TP 7-Web Service Multi Connecteur
7 - TP 7-Web Service Multi Connecteur
I. Objectif du TP ................................................................................................................................................... 2
2. Le fichier [Link]..............................................................................................................................21
5. Le contrôleur ..........................................................................................................................................24
Conclusion ..............................................................................................................................................................28
II. Prérequis
- IntelliJ IDEA ;
- JDK version 17 ;
- Une connexion Internet pour permettre à Maven de télécharger les librairies.
- Le TP relatif à GraphQL.
NB : Ce TP a été réalisé avec IntelliJ IDEA 2023.2.3 (Ultimate Edition).
b. Clone de l’application
- Ouvrir un terminal dans Intellig.
- Se positionner par exemple dans le dossier c:\workspace\tp7 et lancer la commande suivante :
git clone [Link]
NB : Pour plus de détails par rapport au développement du service web avec GraphQL, veuillez se référer à
l’atelier n°4 dont le code source est disponible sur GITHUB : [Link]
[Link].
Explications :
- Pour la validation des données envoyées dans la requête. Par défaut, Spring utilise
comme implémentation de l’API Bean Validation le Framework Hibernate
Validator. L’api propose plusieurs annotations comme par exemple : @NotNull,
@NotEmpty, @Max, ….
- Avec l’api Bean Validation, vous pouvez implémenter vos propres règles de gestion.
import [Link];
import [Link];
import [Link];
@Getter
@Setter
public class ErrorResponse {
private String message;
private List<String> details;
Explications :
- Cette classe sera utilisée pour l’envoi des messages d’erreurs dans la réponse Rest.
- Au niveau de la couche Service, nous allons vérifier les règles de gestion métier si elles ont été
bien respectées, le cas échéant nous allons lever l’exception BusinessException. Cette même
exception sera interceptée par la suite par la classe ExceptionHandlerController
La classe CustomerRestController :
package [Link];
import [Link];
import [Link].*;
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
@RestController
@RequestMapping("/api/rest/customer")
@CrossOrigin("[Link]
Service web avec plusieurs API : Rest, SOAP, GRAPHQL et gRPC 6
public class CustomerRestController {
private final ICustomerService customerService;
@GetMapping("/all")
List<CustomerDto> customers() {
return [Link]();
}
@GetMapping
CustomerDto customerByIdentity(@RequestParam(value = "identity") String identity) {
return [Link](identity);
}
@PostMapping("/create")
public ResponseEntity<AddCustomerResponse> createCustomer(@RequestBody @Valid AddCustomerRequest dto) {
return new ResponseEntity<>([Link](dto), [Link]);
}
@PutMapping("/update/{identityRef}")
public ResponseEntity<UpdateCustomerResponse> updateCustomer(@PathVariable String identityRef, @RequestBody
@Valid UpdateCustomerRequest dto) {
return new ResponseEntity<>([Link](identityRef, dto), [Link]);
}
@DeleteMapping("/delete/{identityRef}")
public ResponseEntity<String> deleteCustomer(@PathVariable String identityRef) {
[Link](identityRef);
return new ResponseEntity<>([Link]("Customer with identity %s is removed", identityRef), [Link]);
}
}
Explications :
- Remarquer que nous avons annoté la classe par @CrossOrigin("[Link] . En
fait, l’objectif est de permettre à l’application Javascript (que nous allons développer dans un
autre atelier en utilisant React) de consommer le service web Rest de notre couche Back-end.
La classe BankAccountRestController :
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
@RestController
@RequestMapping("/api/rest/bank")
@CrossOrigin("[Link]
public class BankAccountRestController {
private final IBankAccountService bankAccountService;
@GetMapping("/all")
List<BankAccountDto> bankAccounts() {
return [Link]();
}
@GetMapping
BankAccountDto bankAccountByRib(@RequestParam(value = "rib") String rib) {
return [Link](rib);
}
@PostMapping("/create")
public ResponseEntity<AddBankAccountResponse> addBankAccount(@Valid @RequestBody AddBankAccountRequest
dto) {
return new ResponseEntity<>([Link](dto), [Link]);
}
}
La classe TransactionRestController :
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
@AllArgsConstructor
@RestController
@RequestMapping("/api/rest/transaction")
@CrossOrigin("[Link]
public class TransactionRestController {
@PostMapping("/create")
public ResponseEntity<AddWirerTransferResponse> addWirerTransfer(@Valid @RequestBody
AddWirerTransferRequest dto) {
return new ResponseEntity<>([Link](dto), [Link]);
}
@GetMapping
public List<TransactionDto> getTransactions(GetTransactionListRequest dto) {
return [Link](dto);
}
}
La classe ExceptionHandlerController :
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@ControllerAdvice
public class ExceptionHandlerController extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatusCode status, WebRequest request) {
List<String> details = new ArrayList<>();
for (ObjectError error : [Link]().getAllErrors()) {
[Link]([Link]());
}
ErrorResponse error = new ErrorResponse("Validation Failed", details);
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(value = [Link])
public final ResponseEntity<Object> handleBusinessException(BusinessException ex, WebRequest request) {
List<String> details = new ArrayList<>();
[Link]([Link]());
ErrorResponse error = new ErrorResponse("Functional errors", details);
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
Explications :
- Avec cette classe, nous avons implémenté le Design Pattern AOP (Aspect Oriented
Programming). En effet, nous avons externalisé le code ayant pour vocation le traitement des
exceptions de notre couche métier. Remarquer que cette classe intercepte et traite 03 types
d’exceptions :
Les exceptions levées par l’api Bean Validation (il s’agit de la classe exception :
MethodArgumentNotValidException) qui sont traitées par la méthode
handleMethodArgumentNotValid. Remarquer que la classe
ExceptionHandlerController hérite de la classe ResponseEntityExceptionHandler de
Spring et ceci afin que la JVM exécute la méthode redéfinie au lieu d’exécuter la
méthode de la classe ResponseEntityExceptionHandler (le principe de
Polymorphisme).
Les exceptions de type BusinessException levées par la couche Service.
Les autres exceptions (par exemple : panne de réseau, …).
Explications :
- La clé [Link] permet de personnaliser le lien pour accéder à l’interface
d’OpenAPI.
- Vous pouvez tester par exemple l’EndPoint /api/rest/customer/all en cliquant tout simplement sur
l’Endpoint comme montré ci-après :
<dependency>
<groupId>[Link]</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>4.0.3</version>
</dependency>
Explications :
- La dépendance jaxws-ri est l’implémentation de référence de l’api JAX WS (Java Architecture based
XML for Web Service), il s’agit du projet Metro de la communauté Jakarta (le site officiel de Metro est
[Link]
2. Le contrôleur SOAP
- Créer le package [Link]
- Créer ensuite la classe BankSoapController suivante :
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Component
@WebService(serviceName = "BankWS")
@SOAPBinding
@AllArgsConstructor
public class BankSoapController {
@WebMethod
/**
*
@WebResult was user in order to replace return balise
by Customer balise in SOAP Response.
*/
@WebResult(name = "Customer")
@WebMethod
/**
* @WebResult was user in order to replace return balise
* by Customer balise in SOAP Response.
*/
@WebResult(name = "Customer")
public CustomerDto customerByIdentity(@WebParam(name = "identity") String identity) {
return [Link](identity);
}
@WebMethod
/**
* @WebResult was user in order to replace return balise
* by Customer balise in SOAP Response.
*/
@WebResult(name = "Customer")
public AddCustomerResponse createCustomer(@WebParam(name = "Customer") AddCustomerRequest dto) {
return [Link](dto);
}
/**
* @WebResult was user in order to replace return balise
* by BankAccount balise in SOAP Response.
*/
@WebResult(name = "BankAccount")
@WebMethod
public List<BankAccountDto> bankAccounts() {
return [Link]();
}
@WebMethod
/**
* @WebResult was user in order to replace return balise
* by BankAccount balise in SOAP Response.
*/
@WebResult(name = "BankAccount")
public BankAccountDto bankAccountByRib(@WebParam(name = "rib") String rib) {
return [Link](rib);
}
/**
* @WebResult was user in order to replace return balise
* by BankAccount balise in SOAP Response.
*/
@WebResult(name = "BankAccount")
@WebMethod
public AddBankAccountResponse createBankAccount(@WebParam(name = "bankAccountRequest")
AddBankAccountRequest dto) {
return [Link](dto);
}
/**
* @WebResult was user in order to replace return balise
* by Transaction balise in SOAP Response.
*/
@WebResult(name = "Transaction")
@WebMethod
public List<TransactionDto> getTransactions(@WebParam(name = "dto") GetTransactionListRequest dto) {
return [Link](dto);
}
@WebResult(name = "Customer")
@WebMethod
public UpdateCustomerResponse changeCustomer(@WebParam(name = "identityRef") String identityRef,
@WebParam(name = "dto") UpdateCustomerRequest dto) {
return [Link](identityRef, dto);
}
@WebMethod
public String deleteCustomer(@WebParam(name = "identityRef") String identityRef) {
return [Link](identityRef);
}
}
3. Le serveur SOAP
- Créer la classe CxfConfig suivante :
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Configuration
@AllArgsConstructor
@Bean
public EndpointImpl bankWSEndpoint() {
Explications :
- La méthode bankWSEndpoint() ci-dessus permet d’enregistrer le Servlet de CXF au niveau du
conteneur. Le SW SOAP nommé « /bankService » est accessible via lien
[Link]
- Entrer l’URL du WS, le nom de votre projet de test et cliquer sur le bouton OK. Vérifier que tous les
Endpoints de votre WS sont listés :
Remarque :
Remarquez que grâce au fichier WSDL, vous avez pu connaître la liste des Endpoint fournis par le SW et
également comment les consommer : vous venez de développer une API SOAP.
<dependency>
<groupId>[Link]</groupId>
<artifactId>protobuf-java</artifactId>
<version>${[Link]}</version>
</dependency>
<dependency>
<!-- Java 9+ compatibility - Do NOT update to 2.0.0 -->
<groupId>[Link]</groupId>
<artifactId>[Link]-api</artifactId>
<version>2.1.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>${[Link]}</version>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>grpc-server-spring-boot-autoconfigure</artifactId>
<version>${[Link]}</version>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>[Link]-api</artifactId>
<version>1.3.2</version>
</dependency>
2. Le fichier [Link]
Créer le fichier src/resources/[Link] suivant :
syntax = "proto3";
option java_package = "[Link]";
service BankService{
rpc customers(CustomersRequest) returns (CustomersResponse);
rpc customerByIdentity(CustomerByIdentityRequest) returns (CustomerByIdentityResponse);
rpc createCustomer(CreateCustomerRequest) returns (CreateCustomerResponse);
rpc updateCustomer(UpdateCustomerRequest) returns (UpdateCustomerResponse);
rpc deleteCustomer(DeleteCustomerRequest) returns (DeleteCustomerResponse);
message CustomersRequest {
}
message CustomersResponse {
repeated CustomerDTO customers = 1;
}
message CustomerDTO {
int64 id = 1;
string username = 2;
string identityRef = 3;
string firstname = 4;
string lastname = 5;
}
message CustomerByIdentityRequest {
string identityRef = 1;
}
message CustomerByIdentityResponse {
CustomerDTO customer = 1;
}
message CreateCustomerRequest {
string username = 1;
string identityRef = 2;
string firstname = 3;
string lastname = 4;
message CreateCustomerResponse {
string message = 1;
CustomerDTO customer = 2;
message UpdateCustomerRequest {
string identityRef = 1;
UpdatedCustomerDTO updatedCustomer = 2;
}
message UpdatedCustomerDTO {
string username = 2;
string firstname = 4;
string lastname = 5;
}
message UpdateCustomerResponse {
string message = 1;
CustomerDTO customer = 2;
}
message DeleteCustomerRequest {
string identityRef = 1;
}
message DeleteCustomerResponse {
string message = 1;
}
message BankAccountDto {
int64 id = 1;
string rib = 2;
double amount = 3;
string createdAt = 4;
string accountStatus = 5;
CustomerDTO customer = 6;
}
message BankAccountsRequest {
message BankAccountsResponse {
repeated BankAccountDto bankAccount = 1;
}
message BankAccountByRibRequest {
string rib = 1;
}
message BankAccountByRibResponse {
BankAccountDto bankAccount = 1;
message AddBankAccountRequest {
string rib = 1;
double amount = 2;
string customerIdentityRef = 3;
}
message AddBankAccountResponse {
string message = 1;
BankAccountDto bankAccount = 2;
}
message AddWirerTransferRequest {
string ribFrom = 1;
string ribTo = 2;
double amount = 3;
string username = 4;
}
message AddWirerTransferResponse {
string message = 1;
TransactionDto transactionFrom = 2;
TransactionDto transactionTo = 3;
}
message GetTransactionsRequest {
string rib = 1;
string dateTo = 2;
string dateFrom = 3;
}
message GetTransactionsResponse {
repeated TransactionDto transaction = 1;
}
message UserDto {
string username = 1;
string firstname = 2;
string lastname = 3;
}
message TransactionDto {
string createdAt = 1;
string transactionType = 2;
double amount = 3;
BankAccountDto bankAccount = 4;
UserDto user = 5;
}
- Lancer la commande clean install de Maven pour que le plugin gRPC puisse générer le stub. Vérifier
que les classes du stub ont été bien générées dans le package
[Link].
5. Le contrôleur
- Créer le package [Link] et ensuite créer la classe
BankGrpcController suivante :
package [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link].*;
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Component
@AllArgsConstructor
@GrpcService
public class BankGrpcController extends [Link] {
private ICustomerService customerService;
private IBankAccountService bankAccountService;
private ModelMapper modelMapper;
@Override
public void customers([Link] request, StreamObserver<[Link]>
responseObserver) {
List<CustomerDto> customers = [Link]();
[Link] response = [Link]().
addAllCustomers(
[Link]().map(customerDto ->
[Link](customerDto, [Link]).build()).
collect([Link]())).
build();
[Link](response);
[Link]();
}
@Override
public void customerByIdentity([Link] request,
StreamObserver<[Link]> responseObserver) {
CustomerDto customerDto = [Link]([Link]());
[Link] response = [Link]().
setCustomer([Link](customerDto, [Link]).build()).
build();
[Link](response);
[Link]();
}
@Override
public void createCustomer([Link] request, StreamObserver<[Link]>
responseObserver) {
AddCustomerResponse addCustomerResponse = [Link]([Link](request,
[Link]));
[Link] response = [Link]().
setMessage([Link]()).
setCustomer([Link](addCustomerResponse, [Link]).build()).
build();
[Link](response);
[Link]();
}
@Override
public void updateCustomer([Link] request, StreamObserver<[Link]>
responseObserver) {
@Override
public void deleteCustomer([Link] request, StreamObserver<[Link]>
responseObserver) {
@Override
public void bankAccounts([Link] request, StreamObserver<[Link]>
responseObserver) {
List<BankAccountDto> bankAccounts = [Link]();
[Link] response = [Link]().
addAllBankAccount(
[Link]().
map(bankAccount -> [Link](bankAccount, [Link]).build()).
collect([Link]())).
build();
[Link](response);
[Link]();
}
@Override
public void bankAccountByRib([Link] request,
StreamObserver<[Link]> responseObserver) {
BankAccountDto bankAccount = [Link]([Link]());
@Override
public void addBankAccount([Link] request, StreamObserver<[Link]>
responseObserver) {
AddBankAccountResponse addBankAccountResponse = [Link](
[Link](request, [Link]));
[Link] response = [Link]().
setMessage([Link]()).
setBankAccount([Link](addBankAccountResponse, [Link]).build()).
build();
[Link](response);
[Link]();
}
@Override
public void addWirerTransfer([Link] request,
StreamObserver<[Link]> responseObserver) {
AddWirerTransferResponse addWirerTransferResponse =
[Link]([Link](request, [Link]));
[Link] response = [Link]().
setMessage([Link]()).
setTransactionFrom([Link]([Link](),
[Link]).build()).
@Override
public void getTransactions([Link] request, StreamObserver<[Link]>
responseObserver) {
List<TransactionDto> transactions = [Link]([Link](request,
[Link]));
[Link] response = [Link]().
addAllTransaction([Link]().
map(transactionDto -> [Link](transactionDto, [Link]).build()).
collect([Link]())).
build();
[Link](response);
[Link]();
}
}
- Lancer BloomRPC, configurer l’URL du serveur ([Link]:4444), ajouter le fichier [Link] et vérifier
que les méthodes fournies par le service gRPC sont bien listées au niveau De l’interface de BloomRPC :
Remarque :
Remarquez que grâce au fichier PROTO, vous avez pu connaître la liste des Endpoint fournis par le SW et
également comment les consommer : vous venez de développer une API gRPC.
Conclusion
Le code source de cet atelier est disponible sur GITHUB :
[Link]