DDDことはじめ ~アプリケーションサービス~

こんにちは、マットです。
都内ITベンチャーのエンジニアです。
Go/Next.js/GraphQLを使っています。

前回の続きです。 今回はDDDのサービスについて振り返りたいと思います。

shikatech.hatenablog.com

アプリケーションサービス

前回に続きこちらの記事からコードを引用します。

How to Implement Domain-Driven Design (DDD) in Golang | by Percy Bolmér | Sep, 2021 | Towards Data Science

まずDDDにはドメインサービスとアプリケーションサービスという二つのサービスが登場します。

ドメインサービスはドメインの不自然さを解決するオブジェクトです。 ドメイン名sercive のように命名するのが一般的です。

対してアプリケーションサービスはドメインリポジトリを一つにまとめあげアプリケーションのユースケースを表現するオブジェクトです。

今回はアプリケーションサービスについて説明します。

まずはサービスのオブジェクトを定義します。

type OrderService struct {
    customers customer.CustomerRepository
    products  product.ProductRepository
}

customers, productsはエンティティの集約ルートオブジェクトです。 二つの集約オブジェクトを持たせOrderServiceを定義しています。

次にサービスの振る舞いをメソッドとして作成します。

func (o *OrderService) CreateOrder(customerID uuid.UUID, productIDs []uuid.UUID) (float64, error) {
    c, err := o.customers.Get(customerID)
    if err != nil {
        return 0, err
    }

    var products []product.Product
    var price float64
    for _, id := range productIDs {
        p, err := o.products.GetByID(id)
        if err != nil {
            return 0, err
        }
        products = append(products, p)
        price += p.GetPrice()
    }

    log.Printf("Customer: %s has ordered %d products", c.GetID(), len(products))

    return price, nil
}

名前の通り注文を作成するメソッドです。 Get(), GetByID()はインフラ層にあたるのでinterfaceを使用しています。

custoemr, product単体のドメインでは表現できなかった振る舞いがサービスとしてまとめられた事で見通しが良くなりました。

サービスの生成

サービスはmain.goで生成される事が多いと思いますが、テクニカルなコードが紹介されていたので振り返りたいと思います。

orderServiceと同じファイルで実装します。

type OrderConfiguration func(os *OrderService) error

func NewOrderService(cfgs ...OrderConfiguration) (*OrderService, error) {
    os := &OrderService{}

    for _, cfg := range cfgs {
        err := cfg(os)
        if err != nil {
            return nil, err
        }
    }
    return os, nil
}

func WithCustomerRepository(cr customer.CustomerRepository) OrderConfiguration {
    return func(os *OrderService) error {
        os.customers = cr
        return nil
    }
}

func WithMongoCustomerRepository(connectionString string) OrderConfiguration {
    return func(os *OrderService) error {
        cr, err := mongo.New(context.Background(), connectionString)
        if err != nil {
            return err
        }
        os.customers = cr
        return nil
    }
}

参考記事ではMongoDB, インメモリ二つのデータストアが提案されています。

NewOrderService()で可変量のOrderConfiguration()を受け取り簡単に永続先を入れ替える事を可能にしています。

order.NewOrderService(      
  order.WithMongoCustomerRepository("connection"),
  // or
  order.WithCustomerRepository(memoRepo),
    )

以上になります。 今回はアプリケーションサービスの振り返りをしました。

リポジトリパターンについては多くのアーキテクチャでも紹介されているので割愛させていただきます。

まとめと感想

DDDの学習は綺麗なコードを書く為にかなり即効性がある項目だと感じました。一方で個人で取り入れるには軽量DDD(いくつかのパターンだけを使用する方法)になってしまいがちです。

現職のチームでは軽量DDDに近い体制なのですが、もう少し踏み込んで取り入れていくと良いインパクトになりそうなので提案していきたいと思います。

最後に、 このブログではweb開発について発信していくのでまたご覧頂けると嬉しいです。 最後までお読み頂きありがとうございました。