これはWHITEPLUS Advent Calendar 2019の17日目の記事です。
はじめに
ホワイトプラスが運営するリネットではドメイン駆動設計(DDD)を取り入れた開発をしています。
2年前のアドベントカレンダーの記事でGoでDDDを実践するモデリングの記事を書きました。 これはテスト駆動開発の書籍の内容を題材にしたのですが、今回は実際の現場で行った内容を題材にしてみます。
お題「生産日程の算出」
ホワイトプラスが運営するリネットはWebで申し込める宅配クリーニングです。 システムはWebの申込みだけでなく、クリーニング工場の生産管理も含まれます。
生産管理においては生産日程の算出は基本的な機能のひとつですが、それが今回のお題です。
さすがにすべての設計をここで書くわけにもいかないので、今回は「工場に到着する日(=着荷日)の算出」をテーマにします。
まずは数式で
着荷日がどう決まるのか。まずは式で表してみます。
着荷日 = 集荷日 + 配送にかかる日数(配送日数) + 工場定休日による調整日数(定休日調整日数) 集荷日: ユーザーが宅配業者に荷物(クリーニング品)をお預けする日 配送日数: ユーザーが出した場所から工場到着までにかかる日数。住所や荷物を預けた時間で変わる。 定休日調整日数: 工場の定休日は荷物が届かないので、工場が営業する日までの日数。
ユーザーが荷物を預けた日(集荷日)を基準として、配送にかかる日数や工場定休日の調整などをして工場への荷物の到着日(着荷日)がきまります。
モデリング
型定義
モデリングを始めるにあたって、まずは出てきた言葉を素直に型にしていきます。DDDでのいわゆるValue Objectです。
// 着荷日 class ArrivalDate { /** * @var Date */ private $value; public function __construct(Date $value) { $this->value = $value; } } // 集荷日 class PickupDate { /** * @var Date */ private $value; public function __construct(Date $value) { $this->value = $value; } } // 配送日数 class DeliveryDayCount { /** * @var DayCount */ private $value; public function __construct(DayCount $value) { $this->value = $value; } } // 定休日調整日数 class DayOffDayCount { /** * @var DayCount */ private $value; public function __construct(DayCount $value) { $this->value = $value; } } // 日時 class Date { /** * @var DateTimeImmutable */ private $value; public function __construct(DateTimeImmutable $value) { $this->value = $value; } } // 日数 class DayCount { /** * @var int */ private $value; public function __construct(int $value) { $this->value = $value; } }
それぞれ独自の型を定義し、内部的には日付計算、日数の算出を行う型に処理を委譲していきます。
ロジックの実装
着荷日クラスにstatic factory methodを作成して算出ロジックの実装をしていきます。
class ArrivalDate { /** * @var Date */ private $value; public function __construct(Date $value) { $this->value = $value; } public static function create( PickupDate $pickupDate, DeliveryDayCount $deliveryDayCount, DayOffDayCount $dayOffDayCount): self { return (new self($pickupDate->value())) ->add($deliveryDayCount->value()) ->add($dayOffDayCount->value()); } public function add(DayCount $dayCount): self { return new self($this->value->addDays($dayCount)); } }
最初に出した式を素直に実装できます。
問題発生
先程の実装には実は問題が潜んでいます。工場定休調整日数をパラメーターで渡していますが、実はこれはできません。
なぜかというと、工場定休日の調整は集荷日+配送日数で算出した日が工場定休日かどうかを基準にして出されるものだからです。
こういう場合にどうしているかというと、ドメインサービスを導入しています。
class ArrivalDate { /** * @var Date */ private $value; public function __construct(Date $value) { $this->value = $value; } public static function create( PickupDate $pickupDate, DeliveryDayCount $deliveryDayCount, DayOffService $dayOffService): self { $candidate = (new self($pickupDate->value())) ->add($deliveryDayCount->value()); return $candidate->add($dayOffService->getDayOffDayCount($candidate->value)); } public function add(DayCount $dayCount): self { return new self($this->value->addDays($dayCount)); } } interface DayOffService { public function getDayOffDayCount(Date $date): DayCount; }
こうすることで、後から決まるものに対して必要な処理を注入できます。
まとめ
DDDでモデリングを進めるポイントはいかに焦点を絞れるかだと思っています。
今回は「着荷日の算出」に焦点を合わせ、配送日数や工場調整日数の算出は出来るものとして一旦考慮の外に置いてます。 実際には配送日数や工場調整日数の算出も結構な機能になるのですが、着荷日の算出においてはそれらの結果だけが必要なのでいかに問題を分割できるかがロジックの見通しを良くします。
手続き型からモデリングへの変換は考え方も大きく変える必要がありますが、今回の例が少しでも参考になれば幸いです。
DDDを使ったモデリングでの開発に興味があれば、エンジニア絶賛募集中ですのでぜひご応募ください!