dani03
dani03 686 XP
a posé

La clean Architecture avec Laravel... où renvoyer les messages d'erreurs et où effectuer des conditions ?

Bonjour à tous j'essaie de comprendre (la clean architecture en me basant sur l'article de Mathieu Garcia sur Laravel France. Je vais vous présenter ici les couches que j'ai ajouté c'est un exemple qui suis l'article en question, sauf que je ne sais pas où renvoyer une erreur, ou encore ou est ce que je dois effectuer une quelconque condition si besoin étant donné que chaque couche me parait avoir un role strict et dois être independante. Je vais faire mon max pour être très clair peut être je me trompe sur certains points j'essaie justement de comprendre au mieux.

Dans la Clean Architecture (C.A) nous avons 4 couches :

  • la couche présentation
  • la couche application
  • la couche domain
  • la couche infrastructure

Je vous renvoie vers l'article pour connaitre le role précis de chacune d'elles.

dans la couche présentation :

1function create(Request $request)
2 {
3 $output = $this->createOrderAction->handle(
4 new Input(
5 $request->input('product_id'),
6 $request->input('quantity'),
7 $request->input('discount', null)
8 )
9 );
10
11 return $this->createOrderPresenter->present($output);
12 }
1function create(Request $request)
2 {
3 $output = $this->createOrderAction->handle(
4 new Input(
5 $request->input('product_id'),
6 $request->input('quantity'),
7 $request->input('discount', null)
8 )
9 );
10
11 return $this->createOrderPresenter->present($output);
12 }
1mon presenter :
1mon presenter :
1
2public function present(Output $output): JsonResponse
3{
4 $message = 'Order created successfully with a total amount of '.$output->totalPrice;
5
6 return response()->json([$message], 201);
7}
1
2public function present(Output $output): JsonResponse
3{
4 $message = 'Order created successfully with a total amount of '.$output->totalPrice;
5
6 return response()->json([$message], 201);
7}
1dans la couche application :
1dans la couche application :
1class CreateOrderAction
2{
3 /**
4 * Create a new class instance.
5 */
6 public function __construct(private readonly OrderRepository $repository)
7 {
8 }
9
10 public function handle(Input $input): Output
11 {
12 $order = $this->repository->initiateOrder($input->productId);
13
14 $order->defineQuantity($input->quantity);
15
16 $order->calculateTotalPrice();
17
18 $order->applyCustomerDiscount($input->discount);
19
20 $order->addApplicableTaxes();
21
22 $this->repository->commitOrder($order);
23
24 return new Output($order->totalPrice);
25 }
26}
1class CreateOrderAction
2{
3 /**
4 * Create a new class instance.
5 */
6 public function __construct(private readonly OrderRepository $repository)
7 {
8 }
9
10 public function handle(Input $input): Output
11 {
12 $order = $this->repository->initiateOrder($input->productId);
13
14 $order->defineQuantity($input->quantity);
15
16 $order->calculateTotalPrice();
17
18 $order->applyCustomerDiscount($input->discount);
19
20 $order->addApplicableTaxes();
21
22 $this->repository->commitOrder($order);
23
24 return new Output($order->totalPrice);
25 }
26}

dans la couche domain :

1class OrderDomain
2{
3 private bool $taxesApplied = false;
4
5 public function __construct(
6 public int $price,
7 public int $productId,
8 public int $quantity,
9 public int $totalPrice,
10 ) {}
11
12 public function defineQuantity(int $quantity): void
13 {
14 $this->quantity = $quantity;
15 }
16
17 public function calculateTotalPrice(): void
18 {
19 $this->totalPrice = $this->price * $this->quantity;
20 }
21
22 public function applyCustomerDiscount(float $percentage): void
23 {
24 $this->totalPrice -= $this->totalPrice * ($percentage / 100);
25 }
26
27 public function addApplicableTaxes(): void
28 {
29 if ($this->taxesApplied) {
30 throw new LogicException("Taxes have already been applied.");
31 }
32
33 $this->totalPrice *= 1.2;
34
35 $this->taxesApplied = true;
36 }
37}
1class OrderDomain
2{
3 private bool $taxesApplied = false;
4
5 public function __construct(
6 public int $price,
7 public int $productId,
8 public int $quantity,
9 public int $totalPrice,
10 ) {}
11
12 public function defineQuantity(int $quantity): void
13 {
14 $this->quantity = $quantity;
15 }
16
17 public function calculateTotalPrice(): void
18 {
19 $this->totalPrice = $this->price * $this->quantity;
20 }
21
22 public function applyCustomerDiscount(float $percentage): void
23 {
24 $this->totalPrice -= $this->totalPrice * ($percentage / 100);
25 }
26
27 public function addApplicableTaxes(): void
28 {
29 if ($this->taxesApplied) {
30 throw new LogicException("Taxes have already been applied.");
31 }
32
33 $this->totalPrice *= 1.2;
34
35 $this->taxesApplied = true;
36 }
37}

dans la couche infastructure :

1class OrderRepositoryInDatabase implements OrderRepository
2{
3 public function __construct(
4 private readonly Database $database,
5 ) {
6 }
7
8 public function initiateOrder(int $id): Order
9 {
10 $product = $this->database->table('products')->find($id);
11
12 throw_unless($product, ProductNotFoundException::class);
13
14 return new Order(
15 productId: $product->productId,
16 price: $product->price,
17 );
18 }
19
20 public function commitOrder(OrderDomain $order): void
21 {
22 $this->database->table('orders')->insert([
23 'product_id' => $order->productId,
24 'quantity' => $order->quantity,
25 'total_price' => $order->totalPrice,
26 ]);
27 }
28}
1class OrderRepositoryInDatabase implements OrderRepository
2{
3 public function __construct(
4 private readonly Database $database,
5 ) {
6 }
7
8 public function initiateOrder(int $id): Order
9 {
10 $product = $this->database->table('products')->find($id);
11
12 throw_unless($product, ProductNotFoundException::class);
13
14 return new Order(
15 productId: $product->productId,
16 price: $product->price,
17 );
18 }
19
20 public function commitOrder(OrderDomain $order): void
21 {
22 $this->database->table('orders')->insert([
23 'product_id' => $order->productId,
24 'quantity' => $order->quantity,
25 'total_price' => $order->totalPrice,
26 ]);
27 }
28}

et entre autre mon interface OrderRepository :

1interface OrderRepository
2{
3
4 public function commitOrder(OrderDomain $order);
5
6 public function initiateOrder(int $id);
7
8
9}
1interface OrderRepository
2{
3
4 public function commitOrder(OrderDomain $order);
5
6 public function initiateOrder(int $id);
7
8
9}

mes DTOs Input et Output :

1class Input
2{
3 /**
4 * Create a new class instance.
5 */
6 public function __construct(
7 public $productId,
8 public $quantity,
9 public $discount
10 )
11 {
12 //
13 }
14}
1class Input
2{
3 /**
4 * Create a new class instance.
5 */
6 public function __construct(
7 public $productId,
8 public $quantity,
9 public $discount
10 )
11 {
12 //
13 }
14}
1class Output
2{
3 public int $totalPrice = 200;
4
5 /**
6 * Create a new class instance.
7 */
8 public function __construct()
9 {
10 //
11 }
12
13 public function price(): int
14 {
15 return 20000;
16 }
17}
1class Output
2{
3 public int $totalPrice = 200;
4
5 /**
6 * Create a new class instance.
7 */
8 public function __construct()
9 {
10 //
11 }
12
13 public function price(): int
14 {
15 return 20000;
16 }
17}

Maintenant moi ma question est par exemple je ne trouve pas l'id du produit dans ma couche infrastructure et que je ne veux pas forcement renvoyer une exception, mais un message ou un not found sur mon api, dans quelle couche je vérifie ça ? initialement je l'aurais fait dans le presenter.. mais le presenter ne doit pas contenir de logique métier, de ce fait est qu'un swtich un if etc.. peut y aller? donc je ne sais pas peut être je suis meme à l'ouest raison pour laquelle je vous demande .. prenez moi comme un petit frère si ma question est bête 😅

stevymarlino
a répondu

Salut @dani03,

Ta question n'est pas bête du tout ! C'est une question interressante, En règle général tu peux structure ta logique d'exception comme suite

Couche Infrastructure : Lance tes exceptions techniques Couche Application : Attrape et transforme en résultats métier Couche Présentation : Transforme les résultats en réponses HTTP

pour ton cas au lieu de capturer ton exception dans ton repository, tu devrais le faire dans ton action handle de ta class CreateOrderAction en encapsulant le tout dans un try ans catch

1public function handle(Input $input): Output
2 {
3 try {
4 $order = $this->repository->initiateOrder($input->productId);
5 
6 $order->defineQuantity($input->quantity);
7 
8 $order->calculateTotalPrice();
9 
10 $order->applyCustomerDiscount($input->discount);
11 
12 $order->addApplicableTaxes();
13 
14 $this->repository->commitOrder($order);
15 
16 return new Output($order->totalPrice);
17 
18 } catch (ProductNotFoundException $e) {
19 return new Output(null, success: false, error: 'Produit non trouvé');
20 }
1public function handle(Input $input): Output
2 {
3 try {
4 $order = $this->repository->initiateOrder($input->productId);
5 
6 $order->defineQuantity($input->quantity);
7 
8 $order->calculateTotalPrice();
9 
10 $order->applyCustomerDiscount($input->discount);
11 
12 $order->addApplicableTaxes();
13 
14 $this->repository->commitOrder($order);
15 
16 return new Output($order->totalPrice);
17 
18 } catch (ProductNotFoundException $e) {
19 return new Output(null, success: false, error: 'Produit non trouvé');
20 }
1puis modifier ton DTO Ouput
1puis modifier ton DTO Ouput
1 
2class Output
3{
4 public function __construct(
5 public ?int $totalPrice = null,
6 public bool $success = true,
7 public ?string $error = null
8 ) {}
9}
1 
2class Output
3{
4 public function __construct(
5 public ?int $totalPrice = null,
6 public bool $success = true,
7 public ?string $error = null
8 ) {}
9}

et dans ta fonction present un simple if pour tester si ton ouput est success ou fail ira, puisque justement son rôle est de formater les réponses reçus.

1public function present(Output $output): JsonResponse
2{
3 if (!$output->success) {
4 return response()->json(['error' => $output->error], 404);
5 }
6 
7 $message = 'Order created successfully with a total amount of ' . $output->totalPrice;
8 return response()->json([$message], 201);
9}
1public function present(Output $output): JsonResponse
2{
3 if (!$output->success) {
4 return response()->json(['error' => $output->error], 404);
5 }
6 
7 $message = 'Order created successfully with a total amount of ' . $output->totalPrice;
8 return response()->json([$message], 201);
9}
Confirmer la suppression

Êtes-vous sûr de vouloir supprimer cette réponse ? Cette action est irréversible.

dani03
dani03 686 XP
a répondu

okay super merci beaucoup @stevymarlino ça va beaucoup m'aider...

Confirmer la suppression

Êtes-vous sûr de vouloir supprimer cette réponse ? Cette action est irréversible.

stevymarlino
a répondu

Super, Bien vouloir marquer ma réponse comme résolu merci @dani03

Confirmer la suppression

Êtes-vous sûr de vouloir supprimer cette réponse ? Cette action est irréversible.

Il faut Se connecter ou Créer un compte pour participer à cette conversation.

Confirmer la suppression

Êtes-vous sûr de vouloir supprimer ce sujet ? Cette action est irréversible.