最近「保守性の高いプログラムを作るにはどうすればよいか」と考えてドメイン駆動設計を学習しています。(同じ動機でDDDを学習中の方も多いと思います。)
しかし、なじみのない用語が大量に出てきて肝心の設計の意図や目的が頭に入ってきませんでした。(ドメインサービスとアプリケーションサービスの違いとは? 仕様って? etc...)
そこで今回はDDDで使われる用語の定義と具体的を整理します。
(あくまでも重要なのは用語の暗記ではなくプログラムの内部品質向上のためにDDDを実践することですが、用語が分からないためにDDDを理解できないのは本末転倒です)
今回はDDDの基本的な要素である値オブジェクトを取り扱います。
値オブジェクトとは
システム固有の値のルールを定義・表現するためのオブジェクト(クラス)です。
intやStringなどのプリミティブ型よりも諸々都合がよくなります。
値オブジェクトのメリット
1. 不正な値が混入するのを防ぐ
ECサイトを例にとって考えます。 このサイトには「1度に注文できるのは10個まで」というルールがあります。
このシステムにおいて。OrderクラスのorderQuantityプロパティ(注文数を保持するプロパティ)がプリミティブなint型の場合どうなるでしょうか。
class Order {
__orderQuantity :int
}
誤ってOrder.orderQuantityに11や1000のようなシステムとして不正な値が代入されてもプログラムとしては問題ありません。(11も1000もint型の値です) しかし、システムのルールに違反しているので本来あってはならない値です。
orderQuantityに代入される前に値をチェックすればバグを防ぐことはできますが、入力値チェック用のロジックが複数箇所に散在してしまいます。この問題を値オブジェクトで解消します。
以下は注文数を表す値オブジェクトです。
import dataclasses
from typing import ClassVar
@dataclasses.dataclass(frozen=True)
class OrderQuantity {
value: int
MIN_QUANTITY: ClassVar[int] = 1
MAX_QUANTITY: ClassVar[int] = 10
def __init__(self, value: int) {
if not int(value)
raise ValueError("整数を入力してください")
if value < self.MIN_QUANTITY
raise ValueError(f."{self.MIN_QUANTITY} 個未満です")
if value > self.MAX_QUANTITY
raise ValueError(f."{self.MAX_QUANTITY} 個を超過しています")
object.__setattr_(self, "value", value)
}
これでOrder.orderQuantityプロパティをOrderQuantity型の値として定義すれば少数や負の数、10000などの不正な値が代入されなくなります。(「注文数は整数でなければならない」というのはシステム固有のルールではないので、別クラスに移植するのもアリだと思います。)
2. ロジックが散在しなくなる
値オブジェクトを使えばビジネスルールの変更にも容易に対応可能です。
例えば事業拡大に伴い注文個数の上限値が20個に引き上げられた場合もOrderQuantityクラスのMAX_QUANTITY変数を1か所修正すれば事足ります。
import dataclasses
from typing import ClassVar
@dataclasses.dataclass(frozen=True)
class orderQuantity {
value: int
MIN_QUANTITY: ClassVar[int] = 1
# 修正が1か所で済む
# MAX_QUANTITY: ClassVar[int] = 10
MAX_QUANTITY: ClassVar[int] = 20
... 省略 ...
値オブジェクトを使わない場合、注文個数をチェックしている箇所を調査しなければなりません。時間もかかりますし抜け漏れも発生する可能性があります。
値オブジェクトを使えば注文個数に関するルールはOrderQuantityクラスに定義されていることがすぐにわかるので修正するまでの調査工数も大幅に減ります。
3. ソースコードが自己文書化する
「ビジネスルールやアプリの仕様がまとめられたドキュメントが存在するものの仕様変更に合わせて変更されておらず、実態と乖離している」という経験をした方は多いと思います。(私もその一人です)
ドキュメントで仕様やルールを管理していると修正漏れが発生したり、ドキュメントで使われている用語とプログラム内のデータが対応しておらずどちらが正しいのか分からなくなったりします。
それに対して値オブジェクトで仕様を管理しておけば注文のルールはOrderクラス、注文数のルールはOrderQuantityクラスに書いてあることが分かるので仕様をすぐに確認することができます。
その他
- システム固有のプロパティを値オブジェクトにする基準: そのプロパティにルールが存在するか・それ単体で扱うか
- 値オブジェクトはデータの入れ物ではなく、振る舞いを持ったオブジェクトである。 逆に言えば値オブジェクトに定義されていない振る舞いはそのオブジェクトがしない(できない)こと