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

## 目次
- テスト用のリポジトリを実装
- リポジトリの依存を除去
- 参考文献
- おまけ

### ディレクトリ構成
ファイルを分割しているので、ディレクトリ構成を載せておきます。

```
DDD_test_repository
├── in_memory_user_reposiotry.py
├── programn.py
├── test
│   └── test_program.py
├── user.py
└── user_service.py
```

## テスト用のリポジトリを実装
テスト用にSQLを用意するのは、面倒なことが多々あるので、配列で代替します。

```python:user_repository.py
import uuid
from typing import Union

from DDD_test_repository.user import UserId, FullName, Username, User


class InMemoryUserRepository:
    def __init__(self):
        self.data = []

        user_id = UserId(str(uuid.uuid4()))
        initial_user = User(user_id, Username("kota"), FullName("こうた", "まつい"))
        self.data.append({user_id: initial_user})

    def find(self, username: Username) -> Union[User, None]:
        # もう少しきれいに書けるはず
        target_user = [user for user in self.data if list(user.values())[0].username.value == username.value]
        if target_user:
            return list(target_user[0].values())[0]
        else:
            return None

    def save(self, user: User) -> None:
        self.data.append({user.id: user})
```


## リポジトリの依存を除去

### リポジトリのインターフェースの用意する
インターフェイス用の完全抽象クラスを作ります。

```python:user_repository.py
import uuid
from abc import ABCMeta, abstractmethod
from typing import Union

from DDD_test_repository.user import UserId, FullName, Username, User


class IUserRepository(metaclass=ABCMeta):
    @abstractmethod
    def find(self, username: Username) -> Union[User, None]:
        pass

    @abstractmethod
    def save(self, user: User):
        pass


class InMemoryUserRepository(IUserRepository):
    def __init__(self):
        self.data = []

        user_id = UserId(str(uuid.uuid4()))
        initial_user = User(user_id, Username("kota"), FullName("こうた", "まつい"))
        self.data.append({user_id: initial_user})

    def find(self, username: Username) -> Union[User, None]:
        # もう少しきれいに書けるはず
        target_user = [user for user in self.data if list(user.values())[0].username.value == username.value]
        if target_user:
            return list(target_user[0].values())[0]
        else:
            return None

    def save(self, user: User) -> None:
        self.data.append({user.id: user})
```

### リポジトリを外部から渡す
ここからの実装は、[こちらの記事](https://qiita.com/kotamatsuoka/items/97dab7518f3ce3ae8355)で実装した内容を参考にしてください。


```python:test/test_program.py
import unittest

from DDD_test_repository.in_memory_user_reposiotry import InMemoryUserRepository
from DDD_test_repository.programn import Program
from DDD_test_repository.user import Username


class TestProgram(unittest.TestCase):
    def test_usernameが重複時は保存できない(self):
        in_memory_user_repository = InMemoryUserRepository()
        with self.assertRaises(ValueError):
            Program().creat_user("kota", "こうた", "まつい", in_memory_user_repository)

    def test_usernameが重複していない時は保存してそのusernameが一件だけ存在するか(self):
        username = "yuki"
        in_memory_user_repository = InMemoryUserRepository()

        Program().creat_user(username, "ゆうき", "まつい", in_memory_user_repository)
        actual = in_memory_user_repository.find(Username(username))

        self.assertEqual(username, actual.username.value)


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

```python:user_service.py
from typing import Union

from DDD_test_repository.in_memory_user_reposiotry import IUserRepository
from DDD_test_repository.user import User


class UserService:
    def __init__(self, user_repository: IUserRepository):
        self.user_repository = user_repository

    def is_duplicated(self, user: User) -> Union[User, None]:
        return self.user_repository.find(user.username)
```

```python:program.py
import uuid

from DDD_test_repository.in_memory_user_reposiotry import IUserRepository
from DDD_test_repository.user import User, UserId, Username, FullName
from DDD_test_repository.user_service import UserService


class Program:
    def creat_user(self, username: str, fist_name:str, family_name: str, user_repository: IUserRepository):
        user_repository = user_repository
        user = User(UserId(str(uuid.uuid4())),
                    Username(username),
                    FullName(fist_name, family_name))

        user_service = UserService(user_repository)
        if user_service.is_duplicated(user):
            raise ValueError("重複しています")
        else:
            user_repository.save(user)
```

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

## おまけ
これでテスト用のリポジトリに取り替えが、できるようになりました。コードとの結合が薄いテストが実装できることは嬉しいことですね。