Python3.7でCrean Architectureを実現する

先日あるプロジェクトで、python 3.7を利用したクリーンアーキテクチャの設計を行いました。

その時に得た知見を紹介したいと思います。

基本的に、この記事ではDDDに関しての説明は[この連載](https://codezine.jp/article/corner/655)を参考していきます。

## Domainモデル

### ValueObject

参考記事によるとValueObjectでは、次の６つの要素を持つものである、と定義されています。

>
| No | 値オブジェクトの特徴 | 説明 |
|--:|:--|:--|
| 1 | 計測／定量化／説明 | ドメイン内の何かを計測したり定量化したり説明したりする |
| 2 | 不変性 | 状態を不変に保つことができる |
| 3 | 概念的な統一体 | 関連する属性を不可欠な単位として組み合わせることで、概念的な統一体を形成する |
| 4 | 交換可能性 | 計測値や説明が変わったときには、全体を完全に置き換えられる |
| 5 | 等価性 | 値が等しいかどうかを、他と比較できる |
| 6 | 副作用のない振る舞い | 協力関係にあるその他の概念に「副作用のない振る舞い」を提供する |

python 3.7からはValue Objectを作成する際にdataclassを利用するのが最もシンプルに記載できます。

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class MonetaryValue():
    amount: Decimal
    currency: str
```

dataclassを利用することで、先程の特性の2. 編成、4. 交換可能性、5. 透過性、6. 副作用のない振る舞いを手に入れることができます。 1. の計測／定量化／説明及び3. の概念的な統一体は設計に依存する箇所であるため、これでValueObjectを記述することができました。

#### 注意事項

1. 個人情報に該当する箇所はreprで出力されないようにマスクする  
    例えば次のようなクラスがあった場合に、何かの拍子にパスワードがログに書かれて、更に何かの拍子に流出する、という事がないように、ハッシュ化前のパスワード等の重要なものはログに出力されないようにしましょう。

    ```python
    from dataclasses import dataclass, field

    @dataclass(frozen=True)
    class UserPassword():
        name: str
        password: str = field(repr=False)

    user = UserPassword(name="foo", password="bar")
    print(user)

    >>> UserPassword(name='foo')
    ```

2. 例えばテナントIDのような、単一の値を持つValueObjectを作成する場合は、継承と委譲と２種類の実装があります。  
    継承の場合:
    
    ```python
    class TenantId(str):
        @classmethod
        def __new__(cls, value):
            # do some validation
            return cls(value)
    ```

    委譲の場合:
    
    ```python
    class TenantId():
        def __init__(self, value):
            super().__init__()
            this.__value = value

        # 他に必要なメソッドをvalueからdelegateする(色々)
        def __eq__(self, value):
            ...
    ```

    委譲のほうが設計としては良いのですが、記述は継承のほうがシンプルです


### Entity
Entityも基本的には同じで設計可能ですが、dataclassでは特定のフィールドだけ書き換え不可能、という書き方ができません。
そのため、idの不変性を持つためのEntity classを作成し、それを継承してEntityを作るようにします。

```python
from dataclasses import dataclass, field

@dataclass
class Entity():
    id: str = field(compare=True)

    def __setattr__(self, name, value):
        if (name == 'id' and hasattr(self, name) ):
            raise SystemError()

        super().__setattr__(name, value)


@dataclass
class User(Entity):
    name: str = field(compare=False)


jon = User(id='foo', name='jon')
pochi = User(id='foo', name='pochi')

jon == pochi

>>> True
```

これにより、idに初期値を代入後再代入することが不可能となるため、idの不変性を得ることができました。


と、楽したいためにここまでdataclassを利用して来ましたが、特に[エンティティのValidation](https://codezine.jp/article/detail/10038?p=4)がうまくできないため、dataclassを利用しない実装も検討の余地がありそうです。

## Dependency Injection
pythonは静的型付け言語ではないため、DIコンテナの利用は必須では無いです。しかし、クラスのインスタンス化の手間を考えると、DIコンテナを利用して依存性の解決を行う方が良いと思います。

Dependency Injectionのおすすめライブラリは[injector](https://github.com/alecthomas/injector)です。シンプルな記述で必要最低限の機能を持つため、簡単にDIコンテナによる依存性注入を実現できます。

```python
from injector import inject, singleton, Injector, Module
from abc import ABC, abstractmethod

class IARepo(ABC):
    @abstractmethod
    def get_user(self):
        raise NotImprementedError('not impremented')

@singleton
class ARepo(IARepo):
    def get_user(self):
        return User(id='aaa', name='bbb')

@singleton
class Usecase():
    @inject
    def __init__(self, repo: IARepo):
        super().__init__()
        self.__repo = repo

    def get_user(self):
        return self.__repo.get_user()

class InjectConfig(Module):
    def configure(self, binder):
        binder.bind(IARepo, to=ARepo, scope=singleton)

inj = Injector(InjectConfig())

usecase = inj.get(Usecase)
usecase.get_user()
```

### DIを利用した単体テスト

また、DIコンテナを利用することで、例えばユースケース等、十分なテストを行いたいモジュールの単体テストも簡単にできるようになります。

```python
from unittest.mock import create_autospec

def test_run():
    repo_mock = create_autospec(IARepo)
    usecase = Usecase(repo=repo_mock)

    user = User(id='baz', name='hoge')
    repo_mock.get_user.return_value = user

    result = usecase.get_user()
    assert result == user

test_run()
```