のんラボ

社内でドメイン駆動設計入門の読書会 #11

2020/05/11 2020/05/11 社内でドメイン駆動設計入門の読書会 #11

社内でドメイン駆動設計入門の読書会 #11

こんにちは。Nonです。

今回も会社で読書会をしている話をしようと思います。

内容は控えめに、ディスカッションの内容重視で書いていきたいと思います。

より具体的なコードや内容がみたい!という方は購入しましょう!

前回:会社でドメイン駆動設計入門の読書会 #10

読んでいる本

読んでいる本はこちらのドメイン駆動設計です。

ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本


以前の記事でも言っていたように、業務でDDDを利用して開発することが多くなったのですが、DDDに精通している人が少ないという問題がありました。

そこで、その精通している人が読書会をしようかと誘ってくださいまして、是非にと参加させていただきました。

進行方法

読書会の進行方法は

  1. 今回読書の対象にする章を決める。
  2. 10〜20分間その章を読む
    1. 読み終わってしまった人は、もう一周読み直すか、次の章に進んでもらう
  3. その後40分間で、その章に対する疑問や考え方をディスカッションする
  4. 1〜3を毎週定期的に行う

という進行方向となっています。

社内で読書会をするのはこれが初めてなので、進行方法はもっといいのあったら教えて下さい。

今回読んだ内容

  • アプリケーションを1から組み立てる
  • アプリケーションを組み立てるフロー
  • 題材とする機能
    • サークル機能の分析
  • サークルの知識やルールをオブジェクトとして準備する
  • ユースケースを組み立てる
    • 言葉との齟齬が引き起こす事態
    • 漏れ出したルールがもたらすもの
  • まとめ

ディスカッション

今回は実装ベースでの話

今回は実装する時にどのようにコーディングしていくかという話でまとめに入っていました。

実装方法は人それそれですし、この本の手法もあくまで一例で書かれているだろうということで、ここには私の所感を書かせていただきます。

ディレクトリの切り方はドメイン毎

ディレクトリはフレームワークに依存しない方がいいと思います。
もちろん、ドメインオブジェクトをフレームワーク依存させる方針であれば問題ありません。

このようにしておかないと、フレームワークの変更時に再利用できないからです。

copied.app━laravel
src┓
   ┣Domain
   ┣Domain
   ┣Domain
   ...

ドメインオブジェクトから作る

1. Entityが最重要

ドメインオブジェクトの中で最重要なのはEntityではないかという結論になりました。

ライフサイクル(ふるまい)を持つ注目したいドメインの実態というのがその理由となります。

copied.class User
{
    private $id;
    private $name;

    public function __construct(int $id, UserName $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    public function changeName(UserName $userName): void
    {
        $this->useName = $userName;
    }

    public function id(): int
    {
        return (string)$this->name;
    }

    public function name(): UserName
    {
        return $this->name;
    }
}

このEntityから読み取れるのはユーザーは自分のユーザー名を変更することができるということですね。

2. Entityを構成するValueObject

そして、そのEntityに知識をもたせるという意味でValueObjectを作成していきます。

copied.class UserName
{
    private $userName;

    public function __construct(string $userName)
    {
        if (mb_strlen($userName) > 20) {
            throw new OverflowException('ユーザー名は20文字以内です。');
        }
        $this->userName = $userName;
    }

    public function __toString(): string
    {
        return (string)$this->userName;
    }
}

ここから読み取れるのはユーザー名は20文字以内という知識がここに溜まることになります。

3. ライフサイクルを永続化するためのRepositoryInterfaceなど

ユーザー情報をデータストアに永続化するためのRepositoryの抽象クラスを作成します。

copied.interface UserRepositoryInterface
{
    public function save(User $user): User;
}
copied.interface CircleFactoryInterface
{
    public function create(CircleName $circleName): Circle;
}

4. 関連するDomainService

最後に関連するドメインサービスです。ここではユーザーはCircleを追加できることがわかるドメインオブジェクトとしてのふるまいが存在します。

copied.class UserService
{
    private $circleFactory;

    public function __construct(CircleFactoryInterface $circleFactory)
    {
        $this->circleFactory = $circleFactory;
    }

    public function createCircle(CircleName $circleName): Circle
    {
        return $this->circleFactory->create($circleName);
    }
}
copied.class Circle
{
    private $id;
    private $circleName;
    private $members;

    public function __construct(int $id, CircleName $circleName, MemberCollection $members)
    {
        $this->id = $id;
        $this->circleName = $circleName;
        $this->members = $members;
    }

    // ...(略)
}

class CircleName
{
    private $circleName;

    public function __construct(string $circleName)
    {
        if (mb_strlen($circleName) > 50) {
            throw new OverflowException('サークル名は50文字以内です。');
        }
        $this->circleName = $circleName;
    }

    public function toString(): string
    {
        return (string)$this->circleName;
    }
}

class MemberCollection
{
    private $members;

    public function __construct(array $members = [])
    {
        foreach ($members as $member) {
            if ($member instanceof User) {
                throw new RuntimeException('Member is must be User type.');
            }
        }
        $this->members = $members;
    }

    public function toArray(): array
    {
        $result = [];
        foreach ($this->members as $member) {
            $result[] = [
                'id' => $member->id(),
                'name' => $member->name(),
            ];
        }
        return $result;
    }
}

のような感じでしょうか。

著書にはもっと具体的な内容が載っていますので、気になる方は是非。

5. ドメインオブジェクトを駆使してユースケースを作成

copied.class CreateCircle
{
    private $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    public function process(CreateCircleInputPort $inputPort, CreateCircleOutputPort $outputPort)
    {
        $circleName = new CircleName($inputPort->circleName());
        $circle = $this->userService->createCircle($circleName);
        $outputPort->output($circle);
    }
}

interface CreateCircleInputPort
{
    public function circleName(): string;
}

interface CreateCircleOutputPort
{
    public function output(Circle $circle): void;
}

という順序で作成していけば、プログラミングに沿った考えではなく現実世界の本来のユースケース、つまりドメインを中心にプログラミングすることができそうです。

最後に

一度ここで、中盤のまとめ的なポジションの章でした。

次回はドメインのルールを守る「集約」です。
また記事にしますので、その時はよしなに。

.