ドメイン駆動設計 アプリケーションサービスについて

## はじめに
- この資料は社内勉強会用に用意したスライドです
- [ドメイン駆動設計入門 ボトムアップでわかる！ドメイン駆動設計の基本](https://www.amazon.co.jp/dp/B082WXZVPC/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)第6章の内容を参考にしています

---

## もくじ
- アプリケーションサービスとは
- ユースケースを組み立てる
- ドメインルールの流出
- アプリケーションサービスと凝集度
- アプリケーションサービスのインターフェース
- サービスとは何か

---
## アプリケーションサービスとは
- アプリケーションサービス＝ユースケースを実現するオブジェクト
- アプリケーションとは？
    - 利用者の目的に応じたプログラムのこと
    - 利用者の目的はユースケースとして定義される
    - つまりアプリケーション＝ユースケースの集合
- アプリケーションサービスは、アプリケーションを構築するユースケースを実現する

---
## ユースケースを組み立てる
- ユースケースはドメインオブジェクトを用いて実現する
- ユースケース＝ドメインオブジェクトを用いて行う手続きの定義

例 ユーザーの登録処理を実装する

```rb:user.rb
class User
  attr_reaeder :name, :id

  def initialize(id: nil, name:)
    @name = url_name
    @id = id 
    @id ||= UserId.new(value: SecureRandom.uuid)
  end

  def change_name(name:)
    raise Error if name.nil?
    @name = name
  end
end
```

```rb:user_id.rb
class UserId
  attr_reader :value

  def initialize(value:)
    raise Error unless value.is_a?(String)
    @value = value
  end
end
```

```rb:user_service.rb
class UserService
  def initialize(user_repository:)
    @user_repository = user_repository
  end

  def exists?(user:)
    !@user_repository.find(id: user.id).nil?
  end
end
```

```rb:user_repositable.rb
module UserRepositable
  def find(id:)
    raise NotImprementedError
  end

  def save(name:)
    raise NotImprementedError
  end
end
```

```rb:user_repository.rb
class UserRepository
  include UserRepositable
  @@db = {}

  def find(id:)
    @@db[id.vallue]    
  end

  def save(user:)
    @@db[user.id.value] = user
  end
end
```

```rb:user_application_service.rb
class UserApplicationService
  attr_reader :user_repository, :user_service  

  def initialize(user_repository:, user_service:)
    @user_repository = user_repository
    @user_service = user_service
  end

  def register(name:)
    user = User.new(name: name)
    user_repository.save(user: user) unless user_service.exists?(user: user)
  end
end
```

---
## ユースケースを組み立てる2
- 前ページの実装では、`UserApplicationService#register`が`User`のインスタンスを返している
- ドメインオブジェクトを返すかどうかは重要な判断
    - ドメインオブジェクトを返す＝受け取った先でドメインオブジェクトへアクセスできてしまう
    - ドメインオブジェクトの操作をアプリケーションサービス外で許容している
- ドメインオブジェクトを返すと判断したときは、以下のようなことができてしまうことを知っていないといけない

```rb
# アプリケーションサービスを読んでいるどこかのコード
repositroy = UserRepository.new
application_service = UserApplicationService.new(user_repository: repository, user_service: UserService.new(user_repository: repository))
user = application_service.register(name: 'hoge')
user.change_name(name: 'fuga') # アプリケーションサービス外でドメインオブジェクトの操作をしている
```

---
## ユースケースを組み立てる3
- アプリケーションサービスの外へのドメインオブジェクトの露出を防ぐ方法にDTO(Data Transfer Object)という考え方がある
    - ドメインオブジェクトのプロパティのみを含んだオブジェクト
- DTOを用いることで、ドメインオブジェクトの操作はアプリケーションサービスに閉じることを強制することができる

```rb:user.rb
class User
  attr_reaeder :name, :id

  def initialize(id: null, name:)
    @name = name
    @id = id 
    @id ||= UserId.new(value: SecureRandom.uuid)
  end

  def change_name(name)
    raise Error if name.nil?
    @name = name
  end

  class Data
    attr_reader :id, :name
    def initialize(user)
      @id = user.id
      @name = user.name
    end
  end
end
```

```rb:user_application_service.rb
class UserApplicationService
  attr_reader :user_repository, :user_service  

  def initialize(user_repository:, user_service:)
    @user_repository = user_repository
    @user_service = user_service
  end

  def register(name:)
    user = User.new(name: name)
    raise Error unless user_service.exists?(user: user)
    if user_repository.save(user: user)
      return User::Data.new(user)
    end
  end

  def update_user(user:, name:)
    user.change_name(name)
    user_repository.save(user: user)
  end
end
```

---
## ユースケースを組み立てる4

- Update処理を考える
    - 更新が行えるプロパティは複数指定できる（name, emailなど）
- 変更が行えるプロパティが増えたらアプリケーションサービスも変更する？
    - Entityにアプリケーションサービスのインターフェースが依存する
- コマンドオブジェクトを用いることでインターフェースの依存を逆転させる→Rubyではハッシュ引数で解決できる

```rb:user_application_service.rb
def update_user(attributes)
  user.change_name(attributes[:name]) if attributes[:name]
  user_repository.save(user: user)
end
```


---
## ドメインルールの流出
- アプリケーションサービスはドメインオブジェクトではない
    - ドメインオブジェクトを用いてユースケースを実現する
- アプリケーションサービスにドメインの知識が記述されると、メンテナンス性が低下する
- アプリケーションサービスではドメインオブジェクトのメッセージパッシングでのみ処理を行う

---
## アプリケーションサービスと凝集度
- 凝集度＝モジュールの責任範囲がどれだけ集中しているか
- 凝集度が高い＝堅牢性・信頼性・再利用性・可読性が高い
- LCOM＝凝集度を測る方法の１つ
    - メソッド内で使われているインスタンス変数の分割可能性が高いほど凝集度が低い


例

```rb
class Hoge
  attr_reader :value1, :value2, :value3, :value4

  def method1
     value1 + value2
  end

  def method2
    value3 + valu4
  end
end

#=> (value1,value2)と(value3, value4)は同じメソッドから参照されていないので分割できる
```

---
## アプリケーションサービスと凝集度2
- 疑問1: Entityの場合はこれはよく起こることでは？
    - Entityのプロパティを同じメソッドで参照することの方が珍しい
- 疑問2: アプリケーションサービスにおいてはパブリックインターフェースを１つだけにするのが理想なのでは？
    - そもそも１つのアプリケーションサービスで複数のユースケースを実現しようとすることに違和感を感じる
    - １つのパブリックインターフェースを用意するのが、LCOM的には凝集度が必ず高くなると思う

---
## アプリケーションサービスのインターフェース
- アプリケーションサービスのパブリックインターフェースを定義する
    - アプリケーションサービスが開発をブロックしない
    - インターフェースが同じであればモックも用意できる

```rb
class UserReidserService
  attr_reader :user, :repository
  def initialize(repository:, user:)
    @user = user
    @repository = repository
  end
  
  def call
    if repository.save(user)
      return User::Data.new(user: user)
    end
  end
end
```

---
## サービスとは何か
- ドメインオブジェクトは、自己の振る舞いを持っている
    - あるドメインにある実体をマッピングしたもの

---
## サービスとは何か2
- ドメインサービスとは何か
    - ドメインサービスにもドメインとしては存在する
    - 例: ユーザーの重複チェック→実世界にも重複チェックをしている誰か（何か）はあるはず
    - しかしその実体はドメインモデルにおいて表現する必要がない、または表現ができないものが、ドメインサービスとなる
    - つまり、ドメインサービス＝実世界のドメインからドメインモデルへマッピングされるときに実体を落とした振る舞いの総称
    - だからドメインサービスは行動に焦点が当たる（誰がするか、ではなく何をするか）
    
---
## サービスとは何か3
- アプリケーションサービスとは何か
    - アプリケーションとは、実世界には関係ないプロダクトとして存在するもの
    - アプリケーションとは１つの存在として自己の振る舞いを持っている
        - ユーザー登録、ユーザー削除など
    - しかしアプリケーションも実体としての存在はない（アプリケーションそのものが実体）
    - アプリケーションはユースケースの集合体→ユースケースとはいわばアプリケーションが行う行動
    - つまり、アプリケーションサービス＝アプリケーションというものの振る舞いの総称

---
## サービスとは何か4
- 本質的にドメインサービスとアプリケーションサービスには差がない
    - 何を対象にしているかだけが異なる
      - ドメインサービスはドメインを対象にした振る舞い
      - アプリケーションサービスはアプリケーションを扱うユーザーを対象にした振るまい
- サービスは状態を持たない
    - 状態をもてない。自己の振る舞いを持たない=自己の状態をもてない

---
## おわり
---
## 参考文献
- [ドメイン駆動設計入門 ボトムアップでわかる！ドメイン駆動設計の基本](https://www.amazon.co.jp/dp/B082WXZVPC/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)