👀

ドメインモデルとデータモデルの違いをGo言語で解説

2024/01/08に公開

はじめに

ソフトウェア開発において、ドメインモデルとデータモデルの理解は非常に重要です。これらはしばしば混同されがちですが、役割と目的が異なります。

本記事では、オンライン注文システムを例にこれらの違いを明確に説明します。

ドメインモデルとは?

ドメインモデルは、ビジネスのロジックや規則を表すオブジェクトモデルです。これは、アプリケーションが解決しようとしているビジネス問題の抽象表現です。

オンライン注文システムのコンテキストでは、以下のOrder、OrderItem、Productなどがドメインモデルの典型的な例です。

Order

注文に関連するビジネスロジックを表すオブジェクト。

type Order struct {
    ID         string
    CustomerID string
    Items      []OrderItem
    Status     OrderStatus
    TotalPrice float64
}

func (o *Order) AddItem(item OrderItem) {
    o.Items = append(o.Items, item)
}

func (o *Order) CalculateTotal() float64 {
    total := 0.0
    for _, item := range o.Items {
        total += item.Price * float64(item.Quantity)
    }
    return total
}

type OrderStatus string  // 例: "新規", "発送済み"

OrderItem

注文内の各アイテムを表します。

type OrderItem struct {
    Product   Product
    Quantity  int
    Price     float64
}

Product

製品情報を表すオブジェクト。

type Product struct {
    ID    string
    Name  string
    Price float64
}

ProductOrder

顧客からの注文情報を受け取るためのオブジェクト。

type ProductOrder struct {
    ProductID string
    Quantity  int
}

データモデルとは?

データモデルは、データベースの構造やデータの形式を定義するモデルです。データ永続化のために使用され、アプリケーションが操作するデータの物理的表現を提供します。

OrderModel

データベースの注文テーブルを反映する構造体。

type OrderModel struct {
    ID         string  `db:"id"`
    CustomerID string  `db:"customer_id"`
    Status     string  `db:"status"`
    TotalPrice float64 `db:"total_price"`
}

OrderItemModel

データベースの注文アイテムテーブルを反映する構造体。

type OrderItemModel struct {
    ID        string  `db:"id"`
    OrderID   string  `db:"order_id"`
    ProductID string  `db:"product_id"`
    Quantity  int     `db:"quantity"`
    Price     float64 `db:"price"`
}

ProductModel

データベースの製品テーブルを反映する構造体。

type ProductModel struct {
    ID    string  `db:"id"`
    Name  string  `db:"name"`
    Price float64 `db:"price"`
}

モデルの使い分け

オンライン注文システムにおいて、ドメインモデルはビジネスロジックの処理に、データモデルはデータの保存や取得に使用されます。

製品情報取得関数

データベースから製品情報を取得する処理。

// GetProductByID は指定された製品IDに基づいて製品情報をデータベースから取得します。
func GetProductByID(db *sql.DB, productID string) (Product, error) {
    // データベースから製品情報を取得するSQLクエリ
    query := `SELECT id, name, price FROM products WHERE id = $1`

    // クエリの実行
    row := db.QueryRow(query, productID)

    // ProductModel に対応する構造体
    var pm ProductModel

    // クエリ結果のスキャン
    err := row.Scan(&pm.ID, &pm.Name, &pm.Price)
    if err != nil {
        if err == sql.ErrNoRows {
            // 製品が見つからない場合のエラーハンドリング
            return Product{}, err
        }
        // その他のエラーハンドリング
        return Product{}, err
    }

    // ProductModel から Product ドメインモデルへの変換
    product := Product{
        ID:    pm.ID,
        Name:  pm.Name,
        Price: pm.Price,
    }

    return product, nil
}

注文作成関数

顧客からの注文を受けて、注文を生成し、データベースに保存する処理。

func CreateOrder(db *sql.DB, customerID string, productOrders []ProductOrder) (*Order, error) {
    order := Order{
        ID:         GenerateOrderID(),
        CustomerID: customerID,
        Status:     "新規",
    }

    // データベーストランザクションの開始
    tx, err := db.Begin()
    if err != nil {
        return nil, err
    }

    // 各注文アイテムに対する処理
    for _, po := range productOrders {
        product, err := GetProductByID(db, po.ProductID)  // GetProductByID は上記で定義
        if err != nil {
            tx.Rollback()
            return nil, err
        }

        item := OrderItem{
            Product:  product,
            Quantity: po.Quantity,
            Price:    product.Price,
        }
        order.AddItem(item)

        // OrderItemModel を使用して注文アイテムをデータベースに保存
        itemModel := OrderItemModel{
            OrderID:   order.ID,
            ProductID: item.Product.ID,
            Quantity:  item.Quantity,
            Price:     item.Price,
        }
        itemInsertQuery := `INSERT INTO order_items (order_id, product_id, quantity, price) VALUES ($1, $2, $3, $4)`
        _, err = tx.Exec(itemInsertQuery, itemModel.OrderID, itemModel.ProductID, itemModel.Quantity, itemModel.Price)
        if err != nil {
            tx.Rollback()
            return nil, err
        }
    }

    // 注文のデータモデルの準備
    orderModel := OrderModel{
        ID:         order.ID,
        CustomerID: order.CustomerID,
        Status:     string(order.Status),
        TotalPrice: order.CalculateTotal(), // 総額を計算
    }

    // 注文をデータベースに保存
    orderInsertQuery := `INSERT INTO orders (id, customer_id, status, total_price) VALUES ($1, $2, $3, $4)`
    _, err = tx.Exec(orderInsertQuery, orderModel.ID, orderModel.CustomerID, orderModel.Status, orderModel.TotalPrice)
    if err != nil {
        tx.Rollback()
        return nil, err
    }

    // トランザクションのコミット
    if err := tx.Commit(); err != nil {
        return nil, err
    }

    return &order, nil
}

まとめ

ドメインモデルとデータモデルは、ソフトウェア開発において異なる役割を果たします。
ドメインモデルはビジネスロジックと操作を、データモデルはデータの永続化と構造を担当します。これらの違いを理解し、適切に使い分けることで、より効率的で保守しやすいソフトウェアを開発することが可能になると考えてます。

Discussion