サービスレイヤ(ServiceLayer)パターン(PofEAA)

ドメインレイヤの前に置かれるFacadeとしてのレイヤ。

アプリケーションサービスとドメインサービスの2つがあり、コントローラーからアプリケーションロジックを分離する目的で作成されるのがアプリケーションサービス、アプリケーションサービスから複雑なドメインロジックをカプセル化するFacadeとして作成されるのがドメインサービスである。

ブラウザから実行する処理と同じことをバッチ処理やコンソールから実行できるようにしようとした時、同じアプリケーションサービスを呼び出すだけで実現できるようにできていれば、適切にアプリケーションロジックを分離できていると言える。

アプリケーションロジックを実装する上で必要なデータの収集はアプリケーションサービスから行われる。つまり、データソースレイヤ(インフラストラクチャ層、Gateways)に対するメソッド呼び出しはアプリケーションサービスから行われる(依存性の注入を用いるかどうかはともかくとして)。

ただ、コントローラーからデータソースレイヤへのアクセスを禁止すべきとは私は考えていない。ログイン中のユーザーのような情報はコントローラーのコールバックから参照できた方が良い場合もある。

RubyにはInteractorというアプリケーションサービスを構築するためのGemがある。このGemの場合だと、クラスとアプリケーションロジックが1対1になり、callメソッドがアプリケーションロジックのエントリポイントになる。

個人的にはこのinteractor gemを使うことには反対の立場である。

  • このGem固有の問題として、更新が停滞している点がある。2014年以降、二回しかリリースがなされていないし、その間にRubyファイルが変更されたのは6コミットだけである。v4の開発も2017年3月から放置されている。
  • 1クラス1アプリケーションロジックになるため、無闇にクラス名が長くなりやすい。関連するアプリケーションロジック(例えばCRUD操作など)を1つのクラスにまとめることができない。
  • 結果のステータスや戻り値はreturn { result: true, users: users }のようにHashで返せば良く、ライブラリに依存してまで複雑な仕組みを導入するメリットが薄い。理解も記述も容易なPOCOで十分である。

個人的な考えではあるが、アプリケーションサービスはユースケースに対応付けるのが良いと考えている。サブジェクトとアクターごとにモジュールを切り、ユースケース名をクラス名、各ステップをメソッドとして定義する。詳細は要求分析駆動設計のユースケース、スイムレーンに記述しているのでそちらを参照してもらいたい。

# 取引サブジェクト
module CommerceSubject
  # 一般ユーザーアクター
  module UserActor
    # 商品注文ユースケース
    class OrderGoodsUseCase
      attr_accessor :actor
      # 実行するアクターのインスタンスをコンストラクタで受け取る
      def initialize(actor)
        self.actor = actor
      end

      # 「欲しい商品を見つけたらカートに追加する」に対応するメソッド
      def add_goods_to_cart(goods_id)
        # 結果とカートを返す
        { result: result, data: cart }
      end

      # 他に「カートから取り除く」や「注文を確定する」などに対応するメソッドがある...
    end
  end
end

すでに書いている通り、ドメインサービスは複雑なドメインロジックをカプセル化するFacadeとして作成される。ドメインモデル(DomainModel)パターンを実装した複数のクラスを使う処理がアトミックな時にカプセル化したり、同じドメインロジックが複数のアプリケーションロジックに現れる時に共通化のために作成する。

動機があるならドメインサービスを作るべきだが、アプリケーションサービスからドメインモデルを呼び出す形で問題がないなら作る必要はないクラスである。

また、ドメインサービスはドメインレイヤのクラスのため、ドメインモデルと同様にデータソースレイヤの呼び出しはしてはならない。

2022/3/21追記:同僚に指摘されてわかったことなのだが、ドメインサービスからリポジトリを使うことはありらしい。オニオンアーキテクチャではドメインレイヤにリポジトリのインターフェースをおいているし、IDDDではドメインレイヤからレジストリ経由でリポジトリを呼んでいる(第7章サービス 7.3ドメインにおけるサービスのモデリング)。ただ、同僚曰くreadのみでwriteはアプリケーションサービスでやるらしい。個人的な所感を述べると依然としてドメインサービスであろうとドメインレイヤからDBなどの外部リソースにアクセスするのは避けるべきだという考えは変わらない。私はドメインレイヤのクラスは参照透過性を持つべき、つまり、同じ呼び出し方をされた場合は同じ結果を返すべきだと考えている。「べきだ」というよりも、そのようにした方が安全だと考えていると言った方が良い(細かいことをいえば、ドメインモデルによってはインスタンス変数を書き換えたりするので、参照透過性という言葉を使うのは間違っていると思うが)。