réponses
741 vues
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 );1011 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 );1011 return $this->createOrderPresenter->present($output);12 }
1mon presenter :1mon presenter :
12public function present(Output $output): JsonResponse3{4 $message = 'Order created successfully with a total amount of '.$output->totalPrice;56 return response()->json([$message], 201);7}12public function present(Output $output): JsonResponse3{4 $message = 'Order created successfully with a total amount of '.$output->totalPrice;56 return response()->json([$message], 201);7}
1dans la couche application :1dans la couche application :
1class CreateOrderAction2{3 /**4 * Create a new class instance.5 */6 public function __construct(private readonly OrderRepository $repository)7 {8 }910 public function handle(Input $input): Output11 {12 $order = $this->repository->initiateOrder($input->productId);1314 $order->defineQuantity($input->quantity);1516 $order->calculateTotalPrice();1718 $order->applyCustomerDiscount($input->discount);1920 $order->addApplicableTaxes();2122 $this->repository->commitOrder($order);2324 return new Output($order->totalPrice);25 }26}1class CreateOrderAction2{3 /**4 * Create a new class instance.5 */6 public function __construct(private readonly OrderRepository $repository)7 {8 }910 public function handle(Input $input): Output11 {12 $order = $this->repository->initiateOrder($input->productId);1314 $order->defineQuantity($input->quantity);1516 $order->calculateTotalPrice();1718 $order->applyCustomerDiscount($input->discount);1920 $order->addApplicableTaxes();2122 $this->repository->commitOrder($order);2324 return new Output($order->totalPrice);25 }26}
dans la couche domain :
1class OrderDomain2{3 private bool $taxesApplied = false;45 public function __construct(6 public int $price,7 public int $productId,8 public int $quantity,9 public int $totalPrice,10 ) {}1112 public function defineQuantity(int $quantity): void13 {14 $this->quantity = $quantity;15 }1617 public function calculateTotalPrice(): void18 {19 $this->totalPrice = $this->price * $this->quantity;20 }2122 public function applyCustomerDiscount(float $percentage): void23 {24 $this->totalPrice -= $this->totalPrice * ($percentage / 100);25 }2627 public function addApplicableTaxes(): void28 {29 if ($this->taxesApplied) {30 throw new LogicException("Taxes have already been applied.");31 }3233 $this->totalPrice *= 1.2;3435 $this->taxesApplied = true;36 }37}1class OrderDomain2{3 private bool $taxesApplied = false;45 public function __construct(6 public int $price,7 public int $productId,8 public int $quantity,9 public int $totalPrice,10 ) {}1112 public function defineQuantity(int $quantity): void13 {14 $this->quantity = $quantity;15 }1617 public function calculateTotalPrice(): void18 {19 $this->totalPrice = $this->price * $this->quantity;20 }2122 public function applyCustomerDiscount(float $percentage): void23 {24 $this->totalPrice -= $this->totalPrice * ($percentage / 100);25 }2627 public function addApplicableTaxes(): void28 {29 if ($this->taxesApplied) {30 throw new LogicException("Taxes have already been applied.");31 }3233 $this->totalPrice *= 1.2;3435 $this->taxesApplied = true;36 }37}
dans la couche infastructure :
1class OrderRepositoryInDatabase implements OrderRepository2{3 public function __construct(4 private readonly Database $database,5 ) {6 }78 public function initiateOrder(int $id): Order9 {10 $product = $this->database->table('products')->find($id);1112 throw_unless($product, ProductNotFoundException::class);1314 return new Order(15 productId: $product->productId,16 price: $product->price,17 );18 }1920 public function commitOrder(OrderDomain $order): void21 {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 OrderRepository2{3 public function __construct(4 private readonly Database $database,5 ) {6 }78 public function initiateOrder(int $id): Order9 {10 $product = $this->database->table('products')->find($id);1112 throw_unless($product, ProductNotFoundException::class);1314 return new Order(15 productId: $product->productId,16 price: $product->price,17 );18 }1920 public function commitOrder(OrderDomain $order): void21 {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 OrderRepository2{34 public function commitOrder(OrderDomain $order);56 public function initiateOrder(int $id);789}1interface OrderRepository2{34 public function commitOrder(OrderDomain $order);56 public function initiateOrder(int $id);789}
mes DTOs Input et Output :
1class Input2{3 /**4 * Create a new class instance.5 */6 public function __construct(7 public $productId,8 public $quantity,9 public $discount10 )11 {12 //13 }14}1class Input2{3 /**4 * Create a new class instance.5 */6 public function __construct(7 public $productId,8 public $quantity,9 public $discount10 )11 {12 //13 }14}
1class Output2{3 public int $totalPrice = 200;45 /**6 * Create a new class instance.7 */8 public function __construct()9 {10 //11 }1213 public function price(): int14 {15 return 20000;16 }17}1class Output2{3 public int $totalPrice = 200;45 /**6 * Create a new class instance.7 */8 public function __construct()9 {10 //11 }1213 public function price(): int14 {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 😅
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): Output2 {3 try {4 $order = $this->repository->initiateOrder($input->productId);56 $order->defineQuantity($input->quantity);78 $order->calculateTotalPrice();910 $order->applyCustomerDiscount($input->discount);1112 $order->addApplicableTaxes();1314 $this->repository->commitOrder($order);1516 return new Output($order->totalPrice);1718 } catch (ProductNotFoundException $e) {19 return new Output(null, success: false, error: 'Produit non trouvé');20 }1public function handle(Input $input): Output2 {3 try {4 $order = $this->repository->initiateOrder($input->productId);56 $order->defineQuantity($input->quantity);78 $order->calculateTotalPrice();910 $order->applyCustomerDiscount($input->discount);1112 $order->addApplicableTaxes();1314 $this->repository->commitOrder($order);1516 return new Output($order->totalPrice);1718 } catch (ProductNotFoundException $e) {19 return new Output(null, success: false, error: 'Produit non trouvé');20 }
1puis modifier ton DTO Ouput1puis modifier ton DTO Ouput
12class Output3{4 public function __construct(5 public ?int $totalPrice = null,6 public bool $success = true,7 public ?string $error = null8 ) {}9}12class Output3{4 public function __construct(5 public ?int $totalPrice = null,6 public bool $success = true,7 public ?string $error = null8 ) {}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): JsonResponse2{3 if (!$output->success) {4 return response()->json(['error' => $output->error], 404);5 }67 $message = 'Order created successfully with a total amount of ' . $output->totalPrice;8 return response()->json([$message], 201);9}1public function present(Output $output): JsonResponse2{3 if (!$output->success) {4 return response()->json(['error' => $output->error], 404);5 }67 $message = 'Order created successfully with a total amount of ' . $output->totalPrice;8 return response()->json([$message], 201);9}
Il faut Se connecter ou Créer un compte pour participer à cette conversation.