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_2)
- [可変である](https://nrslib.com/bottomup-ddd/#outline__3_2_1_1)
- [同じ属性でも区別される](https://nrslib.com/bottomup-ddd/#outline__3_2_1_2)
- [同一性を持つ](https://nrslib.com/bottomup-ddd/#outline__3_2_1_3)
- 参考文献
- おまけ

## [エンティティとは](https://nrslib.com/bottomup-ddd/#outline__3_2)
詳しいエンティティの説明は、参考文献を参照してください。

ここでは、エンティティの3つの特徴だけおさえておきます。

- 可変である
- 同じ属性でも区別される
- 同一性を持つ


## [可変である](https://nrslib.com/bottomup-ddd/#outline__3_2_1_1)

```python
import dataclasses
import unittest


@dataclasses.dataclass(frozen=True)
class FullName:
    full_name: str


@dataclasses.dataclass
class User:
    def __init__(self, full_name: FullName):
        self.full_name = full_name

    def change_full_name(self, new_name: FullName) -> None:
        if new_name is None:
            raise ValueError(f"{new_name} is invalid argument")

        self.full_name = new_name


class TestUserEntity1(unittest.TestCase):
    def test_属性を変更できる(self):
        user = User(FullName("matsuoka kota"))
        user.change_full_name(FullName("tanaka satoshi"))

        self.assertEqual(FullName("tanaka satoshi"), user.full_name)


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

## [同じ属性でも区別される](https://nrslib.com/bottomup-ddd/#outline__3_2_1_2)

```python
import dataclasses
import unittest
import uuid


@dataclasses.dataclass(frozen=True)
class FullName:
    full_name: str


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


@dataclasses.dataclass
class User:
    full_name: FullName
    id: UserId


class TestUserEntity2(unittest.TestCase):
    def test_同じ属性でも区別される(self):
        tom1 = User(FullName("Tom Cruise"), UserId(str(uuid.uuid4())))
        tom2 = User(FullName("Tom Cruise"), UserId(str(uuid.uuid4())))

        self.assertNotEqual(tom1, tom2)


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

## [同一性を持つ](https://nrslib.com/bottomup-ddd/#outline__3_2_1_3)

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


@dataclasses.dataclass(frozen=True)
class FullName:
    full_name: str


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


@dataclasses.dataclass
class User:
    full_name: FullName
    id: UserId

    def change_full_name(self, new_name: FullName) -> None:
        if new_name is None:
            raise ValueError(f"{new_name} is invalid argument")

        self.full_name = new_name

    def __eq__(self, other: User):
        # idのみで比較する
        return isinstance(other, User) and (self.id == other.id)


class TestUserEntity3(unittest.TestCase):
    def test_オブジェクトの属性が変化しても同一性が保証される(self):
        tom = User(FullName("Tom Cruise"), UserId(str(uuid.uuid4())))

        before_tom = tom

        tom.change_full_name(FullName("Tom Hanks"))

        self.assertEqual(before_tom, tom)


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

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

## おまけ
参考文献に載せたブログ記事にはいつも助けられています。ぜひ、みなさんも読みながら、ご自身が使い慣れている言語で写経してみてください。

[Renttle](https://renttle.jp)というサービスを開発中です。ぜひ、使ってみて、レビューをください。