質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

設計相談

システムの設計についての相談や質問を投稿する際にご使用ください。

ドメイン駆動設計

ドメイン駆動設計(Domain-driven design, DDD)とは、ソフトウェアの設計手法、および設計思想や哲学のことです。ドメインモデル構築の際に、設計上の判断を決定する枠組みとドメイン設計に関して議論するボキャブラリを提供するものです。

意見交換

クローズ

3回答

662閲覧

注文とクーポンのドメインモデリングについて

rashild

総合スコア27

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

設計相談

システムの設計についての相談や質問を投稿する際にご使用ください。

ドメイン駆動設計

ドメイン駆動設計(Domain-driven design, DDD)とは、ソフトウェアの設計手法、および設計思想や哲学のことです。ドメインモデル構築の際に、設計上の判断を決定する枠組みとドメイン設計に関して議論するボキャブラリを提供するものです。

0グッド

0クリップ

投稿2023/05/14 08:36

編集2023/05/15 09:06

0

0

様々な条件があるクーポンのドメインモデリング

注文情報・顧客情報・期間限定セールなどに応じて
・注文全体への割引
・特定の商品への割引
・特定のカテゴリ(ブランド)への割引
・割引ではなく、固定金額になる。(クーポンを使うと200円など)
・送料無料
・回数制限
など

ドメインを跨いで影響する様々な仕様を持つクーポンと注文クラスについてのドメインモデリングについて

他の方がどのようにクラスを実装するか、お聞きしたいです。

状況

特に既存のシステムがあるわけではなく、注文と注文に対して使用できるクーポンがある場合にどのようにモデリングしたらいいだろうという意見交換です。

概要

・OrderクラスはCouponの配列を持ったImmutableなクラス。
・OrderDetailクラスは商品情報を持った注文詳細クラス(各注文商品の金額を持つ)
・CouponクラスはCouponInterfaceを使用することで、様々な仕様のクーポン機能が可能

Orderクラス

php

1<?php 2namespace App\Beer; 3 4use App\Beer\Interface\CouponInterface; 5 6class Order { 7 8 private ?int $id; 9 10 private int $price; 11 12 private Customer $customer; 13 14 private OrderDetails $orderDetails; 15 16 private Coupons $coupons; 17 18 private DeliveryFee $deliveryFee; 19 20 public function __construct( 21 ?int $id, 22 int $price, 23 Customer $customer, 24 OrderDetails $orderDetails, 25 Coupons $coupons, 26 DeliveryFee $deliveryFee, 27 ) { 28 $this->id = $id; 29 $this->price = $price; 30 $this->customer = $customer; 31 $this->orderDetails = $orderDetails; 32 $this->coupons = $coupons; 33 $this->deliveryFee = $deliveryFee; 34 } 35 36 public function getId(): ?int { 37 return $this->id; 38 } 39 40 public function getPrice():int { 41 return $this->price; 42 } 43 44 public function getTotalPrice():int { 45 return $this->price + $this->getDeliveryFee()->getValue(); 46 } 47 48 public function getCustomer():Customer { 49 return $this->customer; 50 } 51 52 public function getOrderDetails(): OrderDetails { 53 return $this->orderDetails; 54 } 55 56 public function getCoupons():Coupons { 57 return $this->coupons; 58 } 59 60 public function getDeliveryFee():DeliveryFee { 61 return $this->deliveryFee; 62 } 63 64 public function addCoupon(CouponInterface $coupon) { 65 $this->coupons = $this->coupons->addCoupon($coupon); 66 } 67 68 public function addOrderDetail(OrderDetail $orderDetail) { 69 $this->orderDetails->addOrderDetail($orderDetail); 70 } 71 72 public function hasProduct(Product $product):bool { 73 return $this->orderDetails->hasProduct($product); 74 } 75}

OrderDomainService

php

1<?php 2namespace App\Beer\DomainService; 3 4use App\Beer\Coupons; 5use App\Beer\Order; 6use App\Beer\Interface\CouponInterface; 7 8class OrderDomainService { 9 10 public function __construct( 11 12 ) { 13 14 } 15 16 public function couponIsApplicable(Order $order, CouponInterface $coupon):bool { 17 // TODO:: すでに適用されているクーポンとの状況も確認する必要がある 18 19 return $coupon->isApplicable($order); 20 } 21 22 public function applyCoupon(Order $order, CouponInterface $coupon): Order { 23 $order->addCoupon($coupon); 24 25 $appliedOrder = $this->calculateCouponsOrder($order); 26 27 return $appliedOrder; 28 } 29 30 private function calculateCouponsOrder(Order $order):Order { 31 $orders = []; 32 $coupons = $order->getCoupons()->asArray(); 33 34 $combinations = $this->permutation($coupons); 35 // クーポンの適用順で最も最小金額になる組み合わせを探す 36 foreach ($combinations as $coupons) { 37 $couponCollection = new Coupons($coupons); 38 $appliedOrder = $couponCollection->apply($order); 39 $orders[] = $appliedOrder; 40 } 41 // 最小注文金額のOrderを返す 42 $minTotalPriceOrder = $this->getMinTotalPriceOrder($orders); 43 44 return $minTotalPriceOrder; 45 } 46 47 private function getMinTotalPriceOrder(Array $orders):Order { 48 $totalPrices = []; 49 foreach($orders as $order) { 50 $totalPrices[] = $order->getTotalPrice(); 51 } 52 53 $minTotalPriceIndex = array_keys($totalPrices, min($totalPrices))[0]; 54 55 return $orders[$minTotalPriceIndex]; 56 } 57 58 // 本来は渡された配列の順列を返す 59 private function permutation(array $arr): array { 60 return [ 61 [$arr[0],$arr[1]], 62 [$arr[1],$arr[0]], 63 ]; 64 } 65}

Couponsクラス

php

1<?php 2namespace App\Beer; 3 4use App\Beer\Interface\CouponInterface; 5 6class Coupons { 7 8 private Array $coupons; 9 10 public function __construct( 11 Array $coupons, 12 ) { 13 $this->coupons = $coupons; 14 } 15 16 public function asArray():Array { 17 return $this->coupons; 18 } 19 20 public function addCoupon(CouponInterface $coupon):self { 21 $this->coupons[] = $coupon; 22 23 return new Coupons($this->coupons); 24 } 25 26 public function apply(Order $order): Order { 27 foreach($this->coupons as $coupon) { 28 $order = $coupon->apply($order); 29 } 30 31 return $order; 32 } 33}

ProductCouponクラス

php

1<?php 2namespace App\Beer; 3 4use App\Beer\Enums\CouponTarget; 5use App\Beer\Enums\DiscountType; 6use App\Beer\Interface\CouponInterface; 7 8class ProductCoupon implements CouponInterface { 9 10 private String $couponCode; 11 12 private int $specificPrice; 13 14 private Product $product; 15 16 public function __construct( 17 String $couponCode, 18 int $specificPrice, 19 Product $product 20 ) { 21 $this->couponCode = $couponCode; 22 $this->specificPrice = $specificPrice; 23 $this->product = $product; 24 } 25 26 public function isApplicable(Order $order): bool { 27 return $order->hasProduct($this->product); 28 } 29 30 public function apply(Order $order):Order { 31 $appliedOrderDetails = []; 32 foreach($order->getOrderDetails()->asArray() as $orderDetail) { 33 if ($orderDetail->getProduct()->getProductCode() == $this->product->getProductCode()) { 34 $appliedOrderDetail = new OrderDetail( 35 $orderDetail->getId(), 36 $this->specificPrice, 37 $this->product, 38 ); 39 $appliedOrderDetails[] = $appliedOrderDetail; 40 } else { 41 $appliedOrderDetails[] = $orderDetail; 42 } 43 } 44 $orderDetails = new OrderDetails($appliedOrderDetails); 45 46 $appliedOrder = new Order( 47 $order->getId(), 48 $orderDetails->getTotalPrice(), 49 $order->getCustomer(), 50 $orderDetails, 51 $order->getCoupons(), 52 $order->getDeliveryFee(), 53 ); 54 55 return $appliedOrder; 56 } 57 58 public function getCouponTarget(CouponTarget $couponTarget): CouponTarget 59 { 60 return CouponTarget::PRODUCT_CODE; 61 } 62 63 public function getDiscountType(DiscountType $discountType): DiscountType 64 { 65 return DiscountType::SPECIFIC_PRICE; 66 } 67}

CouponInterface

php

1<?php 2namespace App\Beer\Interface; 3 4use App\Beer\Enums\CouponTarget; 5use App\Beer\Enums\DiscountType; 6use App\Beer\Order; 7 8interface CouponInterface { 9 10 public function isApplicable(Order $order): bool; 11 12 public function apply(Order $order):Order; 13 14 public function getCouponTarget(CouponTarget $couponTarget):CouponTarget; 15 16 public function getDiscountType(DiscountType $discountType):DiscountType; 17 18}

聴きたい意見

クーポンの割引の適用処理をOrderクラスで行うべきか、OrderDomainServiceで行うべきか

Orderクラスからクーポンの適用処理を行おうとすると、金額を変更したOrderDetailや DeliveryFeeを持ったOrderクラス(自分自身)を返す必要があり、不自然な処理になっている気がします。

一方、OrderDomainServiceにOrderとCouponクラスを渡し、割引が適用されたOrderクラスを受け取るようにすると
適用処理はDomainServiceに任せているのに、Orderはどのクーポンが適用されているか把握する必要があるためCoupon配列を持っているという違和感を感じます。

現状、OrderDomainServiceで実装する方が良いと考えていますが、他の方法も含めて意見を聴きたいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

回答3

#1

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/05/14 09:24

編集2023/05/14 09:25

ここでは、クーポンの割引の適用処理はOrderDomainServiceで行った方が良いと思います。
理由は、Orderクラスは注文の状態を保持する役割に徹するべきだからです。

OrderDomainServiceにOrderとCouponクラスを渡し、割引が適用されたOrderクラスを受け取るようにすると適用処理はDomainServiceに任せているのに、Orderはどのクーポンが適用されているか把握する必要があるためCoupon配列を持っているという違和感を感じます。

「どのクーポンが適用されているか」という状態を、注文が保持するのは特におかしくないと思います。

たとえば、クーポンの種類別適用条件が、1注文内のトータル金額に依存しているとします。(例:「クーポンにはAタイプとBタイプがあるが、クーポン適用前の注文総額が1000円未満の場合はAタイプのクーポンを適用できない」)
この場合に、仮に注文が「どのクーポンが適用されているか」という情報を保持していないとしたら、注文内容が変更された場合に、クーポンの適用条件を再判定して割引価格を正しく更新することができないのではないでしょうか。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#2

rashild

総合スコア27

投稿2023/05/15 09:11

#1 回答ありがとうございます。

確かにに実装を進めてみましたが
・注文内容の変更
・他クーポンを使用する・しないの変更
に応じて
・クーポンが利用できるのか
・どの順序での適用が最も割引ができるのか
を再計算する必要があるため
クーポンの配列はOrderが持っていて良いと思いました。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#3

rashild

総合スコア27

投稿2023/05/17 16:48

今回の意見交換のもとになった問題について、記事を書きました。
こちらへの意見もいただけると嬉しいです。
「ソフトウェアテスト技法練習帳」40本ノック(3)/1杯目のビールの価格

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

最新の回答から1ヶ月経過したため この意見交換はクローズされました

意見をやりとりしたい話題がある場合は質問してみましょう!

質問する

関連した質問