概要
こんにちは!
ソフトウェア設計におけるドメインモデル貧血症、聞いたことあるけどつまりどういうこと?と思われる方も多いと思います。この記事では、ドメインモデル貧血症の一例とその解決方法について紹介します。
ドメインモデル貧血症とは
ドメインモデル貧血症は、「ドメインモデルが単なるデータの入れ物としてでしか機能せず、ビジネスロジックや振る舞いを含まない状態」 を指します。
いきなり何を言っているのか分からないという感じですよね。これよりもう少しわかりやすくかみ砕いて説明していきます。
まず、ドメイン貧血症を理解するには前提として「ドメインモデル」と「ビジネスロジック」について理解する必要があります。(以下へ続く
1.「ドメインモデル」とは
DDD(ドメイン駆動設計)の文脈でよく使われる 「ドメインモデル」とは、実世界のビジネスや問題領域をコンピュータプログラムで表現したものです。
例として、あなたはとあるブランドを運営していて、これからオンラインショッピングサイトに展開したいと考えています。
ショッピングサイトを運営するにあたって必要なものとして、「顧客」「商品」「注文書」があるとします。これらの現実世界で必要であるものをプログラムの世界で再現したものがドメインモデルです。
2.ビジネスロジックとは
「ビジネスロジック」とは、そのビジネスの特定のルールやプロセスをコンピュータプログラムで表現したものになります。たとえば、商品には、買われる度に在庫数が減るというごく当たり前の営みがありますよね。(商品の在庫管理)
それがビジネスロジックです。
Goにおけるドメインモデル貧血症の例(アンチパターン)
ドメインモデル貧血症の例として、先ほど説明した「商品の在庫管理」をアンチパターンとして実装します。この場合、Product 構造体はデータのみを保持し、ビジネスロジックは別の構造体や関数に分離されます。
package main
import (
"fmt"
"errors"
)
// Product 構造体は商品を表し、データのみを保持
type Product struct {
ID string
Name string
Price float64
Quantity int
}
// NewProduct は新しい商品を作成
func NewProduct(id, name string, price float64, quantity int) *Product {
return &Product{
ID: id,
Name: name,
Price: price,
Quantity: quantity,
}
}
// ProductService 構造体は商品に関連するビジネスロジックを担う
type ProductService struct{}
// UpdateProductQuantity は商品の在庫数を更新(Product 構造体から分離されている)
func (ps ProductService) UpdateProductQuantity(p *Product, change int) error {
if p.Quantity - change < 0 {
return errors.New("不十分な在庫です")
}
p.Quantity = p.Quantity - change
return nil
}
func main() {
// 商品の例
product := NewProduct("001", "スニーカー", 12990.0, 50)
// 商品情報の表示
fmt.Printf("商品ID: %s\n商品名: %s\n価格: %.2f\n在庫数: %d\n", product.ID, product.Name, product.Price, product.Quantity)
// ProductService を使用して在庫の更新
service := ProductService{}
err := service.UpdateProductQuantity(product, -20)
if err != nil {
fmt.Println("エラー:", err)
}
// 更新後の商品情報の表示
fmt.Printf("更新後の商品ID: %s\n商品名: %s\n価格: %.2f\n在庫数: %d\n", product.ID, product.Name, product.Price, product.Quantity)
}
このコードでは、Product 構造体は単にデータ(商品のID、名前、価格、在庫数)を保持するだけで、在庫数を更新するビジネスロジックは ProductService 構造体によって提供されています。
これはドメインモデル貧血症の一例で、ドメインモデル(この場合は Product)が単なるデータの入れ物としての役割のみで、ビジネスロジックが別の場所に分離されていることを示しています。
何が問題か
このアプローチの問題は以下の2点が挙げられます。
・ビジネスロジックがドメインモデルから分離されているため、コードの理解や保守が難しくなる。(特に新しい開発者がプロジェクトに参加した場合、ビジネスの流れを理解するために複数のクラスやモジュールを跨がなければならないことが多い)
・ドメインモデルがビジネスルールや振る舞いを表現していないため、ビジネスの変更に柔軟に対応できない。(ビジネスルールの変更が必要になった際に、複数の場所を変更する必要がある。)
解決方法:リッチドメインモデルの採用
まずは、コードから。
package main
import (
"fmt"
"errors"
)
// Product 構造体は商品を表します。
type Product struct {
ID string
Name string
Price float64
Quantity int
}
// NewProduct は新しい商品を作成します。
func NewProduct(id, name string, price float64, quantity int) *Product {
return &Product{
ID: id,
Name: name,
Price: price,
Quantity: quantity,
}
}
// UpdateQuantity は商品の在庫数を更新します。
func (p *Product) UpdateQuantity(change int) error {
if p.Quantity + change < 0 {
return errors.New("不十分な在庫です")
}
p.Quantity += change
return nil
}
// DisplayInfo は商品の情報を表示します。
func (p *Product) DisplayInfo() {
fmt.Printf("商品ID: %s\n商品名: %s\n価格: %.2f\n在庫数: %d\n", p.ID, p.Name, p.Price, p.Quantity)
}
func main() {
// 商品の例
product := NewProduct("001","スニーカー", 12990.0, 50)
// 商品情報の表示
product.DisplayInfo()
// 在庫の更新
err := product.UpdateQuantity(-20)
if err != nil {
fmt.Println("エラー:", err)
}
// 更新後の商品情報の表示
product.DisplayInfo()
}
リッチドメインモデルとは?
リッチドメインモデルは、プログラミングにおいて、データ(情報)だけでなく、そのデータに関連する操作やルール(ビジネスロジック)も同じ場所(クラスや構造体)に含める設計のことです。
これにより、データが「何を持っているか」だけでなく、「何ができるか」も表現できます。
メリット
■理解しやすい:
データとそれを操作するロジックが一緒にあるため、全体を理解しやすい。
■変更に強い:
ビジネスルールが変わった時に、そのルールに関連する部分だけを変更すれば良いので、変更が簡単。さらにビジネスロジックの処理の全てが一か所にあるので、変更点を見つけやすい。
■再利用しやすい:
同じ種類の操作が他の場所でも必要になった場合、既に定義されているメソッドを再利用できる。これにより、コードの重複を減らし、効率的に開発できる。
■一貫性の保持:
データとそれに対する操作が一緒にあることで、データに対する処理の一貫性が保たれる。同じデータに対して異なる部分で異なる処理が行われることを防げる。
結論
いかがでしょうか??
リッチドメインモデルを採用することにより、ビジネスルールと振る舞いがドメインモデルに密接に結びついて、ビジネスの変更や新しい要件への対応が容易になります。
ドメインモデルがビジネスの核心を正確に反映し、必要なビジネスロジックを内包していることが、ビジネスの変化に迅速かつ柔軟に対応する鍵となります。
さいごに❤くれると大変励みになるので良いと思った方はよろしくお願いいたします❤