PythonでボトムアップDDD 【ドメインサービス】

[ボトムアップドメイン駆動設計](https://nrslib.com/bottomup-ddd)の**ドメインサービス**について、Pythonで書いてみた。

C#で書かれているコードをPythonに翻訳したので、厳密に再現できていない箇所もあります。また、それぞれの呼び出しをテストコードで表し、ソースコードレベルで目的を明確にしました。

用語の説明やコードの詳細説明に関して、下記の参考文献を参照ください。

参考文献: [ボトムアップドメイン駆動設計](https://nrslib.com/bottomup-ddd)

|筆者が訳した「pythonでボトムアップDDD」|
|:---:|
|[PythonでボトムアップDDD 【値オブジェクト】](https://qiita.com/kotamatsuoka/items/832ffe97e2a1c19141b4)|
|[PythonでボトムアップDDD 【エンティティ】](https://qiita.com/kotamatsuoka/items/bc08eddfbcf308da30ec)|
|[PythonでボトムアップDDD 【ドメインサービス】](https://qiita.com/kotamatsuoka/items/d26b61e711d165447ea5)|
|[PythonでボトムアップDDD【値オブジェクト・エンティティ・ドメインサービスを利用する】](https://qiita.com/kotamatsuoka/items/8c2cd68fe17f0dd3719f)|
|[PythonでボトムアップDDD 【リポジトリ】](https://qiita.com/kotamatsuoka/items/97dab7518f3ce3ae8355)|
|[PythonでボトムアップDDD 【テスト用のリポジトリ】](https://qiita.com/kotamatsuoka/items/6db76080b9ea06afbb57)|
|[PythonでボトムアップDDD 【ユーザの登録・変更・削除・取得など】](https://qiita.com/kotamatsuoka/items/4564f32a18a859170f42)|



## バージョン
- Python 3.7.0

## 目次
- ドメインサービスとは
- エンティティ自身に横断的な知識を実装
- ドメインサービスに横断的な知識を実装
- 使う場面によっては別の案があるかも

## [ドメインサービスとは](https://nrslib.com/bottomup-ddd#outline__3_3)
ドメインサービスは、値オブジェクトとエンティティの**横断的な知識**を実装することが許されている。

## エンティティ自身に横断的な知識を実装
エンティティに横断的な知識を持たせるように実装してみる。
ユーザ自身に他のユーザとの重複を判断させるのは、確かに違和感がある。

```python
from __future__ import annotations
import unittest
import uuid
from dataclasses import dataclass

@dataclass(frozen=True)
class UserId:
    value: str

@dataclass(frozen=True)
class Username:
    username: str

@dataclass
class User:
    user_id: UserId
    username: Username

    def is_duplicated(self, user: User) -> bool:
        # 自分以外のユーザを引数に渡せる
        return user.username in USER_LIST

# DB代わりのリスト
USER_LIST = [Username("松岡"), Username("松田"), Username("松井")]

class TestUser(unittest.TestCase):
    def test_ユーザ自身が渡されたユーザのユーザ名の重複判断ができる(self):
        matsuoka = User(UserId(str(uuid.uuid4())), Username("松岡"))

        self.assertTrue(matsuoka.is_duplicated(matsuoka))

if __name__ == "__main__":
    unittest.main()
```


## ドメインサービスに横断的な知識を実装

```python
from __future__ import annotations
import unittest
import uuid
from dataclasses import dataclass


@dataclass(frozen=True)
class UserId:
    value: str


@dataclass(frozen=True)
class Username:
    username: str


@dataclass
class User:
    user_id: UserId
    username: Username


class UserService:
    def is_duplicated(self, user: User) -> bool:
        # 自らのユーザのみ重複判断できる
        return user.username in USER_LIST


# DB代わりのリスト
USER_LIST = [Username("松岡"), Username("松田"), Username("松井")]


class TestUserService(unittest.TestCase):
    def test_ユーザーが重複しているかをドメインサービスに任せる(self):
        matsuoka = User(UserId(str(uuid.uuid4())), Username("松岡"))

        self.assertTrue(UserService().is_duplicated(matsuoka))


if __name__ == "__main__":
    unittest.main()
```

このようにエンティティに関するロジックだが、エンティティ自身に持たせることが不自然なロジックはドメインサービスに実装する。

## 使う場面によっては別の案があるかも
自分自身の`username`が他のユーザの`username`と重複しているかを確かめるだけならば、下のようにUserエンティティに`is_deplicated()`を実装しても良いと思おう。

上の**「エンティティ自身に横断的な知識を実装」**との違いは、**自分自身のusername**しか、重複しているかの判断ができないことだ。

```python
from __future__ import annotations
import unittest
import uuid
from dataclasses import dataclass


@dataclass(frozen=True)
class UserId:
    value: str


@dataclass(frozen=True)
class Username:
    username: str


@dataclass
class User:
    user_id: UserId
    username: Username

    def is_duplicated(self) -> bool:
        # 自らのユーザのみ重複判断できる
        return self.username in USER_LIST


# DB代わりのリスト
USER_LIST = [Username("松岡"), Username("松田"), Username("松井")]


class TestDomainService(unittest.TestCase):
    def test_ユーザが自らのユーザ名の重複判断ができる(self):
        matsuoka = User(UserId(str(uuid.uuid4())), Username("松岡"))

        self.assertTrue(matsuoka.is_duplicated())


if __name__ == "__main__":
    unittest.main()
```

レビューお待ちしてます。

## 参考文献
- [ボトムアップドメイン駆動設計](https://nrslib.com/bottomup-ddd)