DDDとクリーンアーキテクチャをちょっとだけ理解した

## 新卒1年目でまさかのクリーンアーキテクチャ
こんにちは、僕は今年からエンジニアになった新卒1年目です。
入った会社はバリバリコーディングさせてくれるような良い環境に身を投げいることに成功しました。

ですが、自分が今入っているプロジェクトのアーキテクチャがDDDにクリーンアーキテクチャの思想を取り入れて構築されているようで、チームに入った最初の1ヶ月は死に物狂いでした。
それなりに時間も経ちちょっとだけ理解できたので、備忘録として残しておきます。

## 環境
・`macOS BigSur: 11.0.1`
・`Laravel: 6.18.43`
・`PHP: 7.3.18`

## サンプル
#### ビジネスルール
・ユーザーはタイトル、本文を入力して記事を投稿することができる

#### 機能仕様
・`route`はアーキテクチャとは別の概念になるかと思うので今回は記載なし
・ユーザーが記事を投稿できる`Api`を`Laravel`で実装
・データ登録のため、`id`, `title`, `body`カラムを用意
・各レイヤーに`Base...`クラスを用意し、`Base`を継承してクラスを実装
→　こうすることによって、共通の処理(そのレイヤーのベースになるような処理)を実装したいとき、または実装した後実装内容を変えた場合親クラスである`Base...`クラスを修正し、小クラスを修正すれば共通の処理は比較的楽に管理することができます。

## Controller
アーキテクチャ採用して開発したことある方だと大体聞いたことがあるかと思いますが`Controller`です。

`Controller`レイヤーの責務はエンドポイント処理入り口の役割を担い、以下のように動作します。

1. `Request`クラスを利用してパラメータを取得
2. `Servie`クラスの`execute`メソッドにパラメータを渡してビジネスロジックを実行(`Service`クラスの`execute`メソッドはビジネスロジック実行結果を返す)
3. ビジネスロジックの事項結果を元にしてレスポンスデータを作成するため、`Presenter`クラスの`output`にビジネスロジック実行結果を渡す。
4. `Presenter`クラスの`output`メソッドにレスポンスのデータフォーマットに加工する。
5. `Presenter`クラスの`output`メソッドの結果をレスポンスで返す。

```PHP:BaseController.php
namespace ProjectName\Controllers;

use Illuminate\Routing\Controller;

class BaseController extends Controller
{
}

```

```PHP:PostController.php
namespace ProjectName\Controllers\Post;

use Illuminate\Http\JsonResponse;
use ProjectName\Requests\Post\StoreRequest;
use ProjectName\Presenters\Post\StorePresenter;
use ProjectName\Services\Post\StoreService;
use ProjectName\Controllers\Post\BaseController;

class StoreController extends BaseController
{
    public function store(
        StoreRequest $request, StorePresenter $presenter, StoreService $service
    ): JsonResponse {
        // Requestクラスを使用してパラメータ取得
        $title = (string) $request->input('title');
        $body = (string) $request->input('body');
        // ビジネスロジック実行
        $result = $service->execute($title, $body);
        // Presenterクラスのoutputメソッドの結果をレスポンス
        return response()->json($presenter->output($result));
    }
}

```

## Service
`Service`層ではビジネスロジックを実装し、エンドポイント毎に`Service`を用意してビジネスロジックの結果モデルを返す責務があります。

`Controller`と`Service`は`1:1`で作成します。

```PHP:BaseService.php
namespace ProjectName\Services;

class BaseService
{
}
```

```PHP:StoreService.php
namespace ProjectName\Services\Post;

use ProjectName\Services\BaseService;
use ProjectName\Repositories\PostRepositoryInterface;
use ProjectName\Results\Post\StoreResult;

class StoreService extends BaseService
{
    protected PostRepositoryInterface $postRepository;
    // DI
    public function __construct(PostRepositoryInterface $postRepository)
    {
        $this->postRepositoryInterface = $postRepository;
    }

    public function execute(string $title, string $body): StoreResult
    {
        $post = $this->postRepository->create($title, $body);
        // Resultクラスに結果を格納して返す
        return StoreResult::makeSuccess()
            ->setPost($post);
    }
}

```

## InterfaceとRepository
### Interafce
`Repository`クラスの抽象クラスで、メソッドに渡す値と返す値は具像クラスに依存しない値にする必要があります。

```PHP:PostRepositoryInterface.php
namespace ProjectName\Repositories;

use ProjectName\Models\Domains\Entities;

interface PostRepositoryInterface
{
    public function create(string $title, string $body): Entities\Post; 
}
```

### Repository
`Interface`の具像クラスで、`Service`に対してデータのライフサイクルを制御するための操作やデータを永続化する処理を実装します。

必要な場合はEntityのインスタンス作成もここで行います。

```PHP:BaseRepository.php
namespace ProjectName\Repositories;

class BaseRepository
{
}
```

```PHP:PostRepository.php
namespace ProjectName\Repositories\Database;

use ProjectName\Database\Eloquent\Post;
use ProjectName\Repositories\PostRepositoryInterface;
use ProjectName\Models\Domains\Entities;

class PostRepository implements PostRepositoryInterface
{
    protected Post $postEntity;

    public function __construct(Post $postEntity)
    {
        $this->postEntity = $postEntity;
    }
    
    public function create(string $title, string $body): Entities/Post
    {
        $attribute = ['title' => $title, 'body' => $body];

        return $this->postEntity->create($attribute);
    }
}
```

## Presenter
必要に応じてエンドポイント毎に`Presenter`クラスを作成し、レスポンスやレンダリングに必要なデータ形式に加工する責務を持ちます。

```PHP:BasePresenter.php
namespace ProjectName\Presenters;

class BasePresenter
{
}

```

```PHP:StorePresenter.php
namespace ProjectName\Presenters\Post;

use ProjectName\Results\Post\StoreResult;

class StorePresenter extends BasePresenter
{
    public function output(StoreResult $result): array
    {
        $post = $result->getPost();

        return [
            'title' => $post->getAttribute('title'),
            'body' => $post->getAttribute('body'),
        ];
    }
}
```

## Request
リクエストパラメータをバリデーションを行うレイヤーで、リクエストデータを取得する責務も持ちます。

```PHP:BaseRequest.php
namespace ProjectName\Requests;

use Illuminate\Foundation\Http\FormRequest;

class BaseRequest extends FormRequest
{
    public function authorize(): bool
    {
        return false;
    }

    public function rules(): array
    {
        return [];
    }
}

```

```PHP:StoreRequest.php
namespace ProjectName\Requests\Post;

use ProjectName\Requests\BaseRequest;

class StoreRequest extends BaseRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'title' => [
                // note: ルール内はスペース入れてしまうと動かなくなるので×
                'required_if:status,published',
                'max:50',
            ],
            'body' => [
                'required_if:status,published',
                'max:1000',
            ],
        ];
    }
}

```

## Result
ビジネスロジックの結果を格納するモデルで、`Service`毎に作成が必要です。

```PHP:BaseResult.php
namespace ProjectName\Results;

class BaseResult
{
    private const RESULT_STATUS_SUCCESS = 'success';
    private const RESULT_STATUS_ERROR = 'error';

    private string $resultStatus;

    private function __construct(string $resultStatus)
    {
        $this->resultStatus = $resultStatus;
    }

    final public static function makeSuccess(): self
    {
        return new static(self::RESULT_STATUS_SUCCESS);
    }

    final public static function makeError(): self
    {
        return new static(self::RESULT_STATUS_ERROR);
    }

    final public function isSuccess(): bool
    {
        return $this->resultStatus === self::RESULT_STATUS_SUCCESS;
    }

    final public function isError(): bool
    {
        return $this->resultStatus === self::RESULT_STATUS_ERROR;
    }
}

```

```PHP:StoreResult.php
namespace ProjectName\Results\Post;

use ProjectName\Models\Domains\Entities;
use ProjectName\Results\BaseResult;

class StoreResult extends BaseResult
{
    protected Post $post;

    public function getPost(): Post
    {
        return $this->post;
    }

    public function setPost(Entities\Post $post): StoreResult
    {
        $this->post = $post;

        return $this;
    }
}

```

## Domain Entity
-- `2020/12/21/`更新 --

なぜドメインEntityを採用するのかの記事を更新しました。
[こちらからどうぞ](https://qiita.com/UserKazun/items/32889f680aab41e84a15)

 -- 更新終わり -- 

ライフサイクルと連続性を持つデータを扱います。
ドメインEntityは`Service`と`Repository`のみで扱い、外部にドメインEntityを直接渡すことはNGとされています。
もし外部に値を渡したいときは`DTO(Data Transfer Object)`を渡します。

この層の導入目的としては、コードの表現力をあげ不正な値を存在させず、さらに誤った代入を防ぐためです。

```PHP:BaseEntity.php
namespace ProjectName\Models\Domains\Entities;

abstract class BaseEntity
{
}
```

```PHP:Post.php
namespace ProjectName\Models\Domains\Entities;

class Post extends BaseEntity
{
    protected array $primaryKey = ['id'];

    protected array $columns = [
        'id',
        'title',
        'body',
    ];

    protected array $nullable = [];

    protected array $dates = [
        'created_at',
    ];

    protected array $fillable = [
        'title',
        'body',
    ];
}
```
# 主要レイヤーの各依存先
![スクリーンショット 2020-11-30 15.54.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376889/c03db5f1-3e68-5b56-fa40-df410caa0816.png)

一番下の`DataAccess`は実際にデータを取得する役割を持ちます。例えば`Api`を叩いたり、実際に`DB`にデータを取得しに行ったりといった具合です。

-- `2020/12/21`更新 --
# APIを叩く場合のDataAccess層
データベースから直接値を持ってくる,APIを叩いてデータを取得する場合はDataAccess層を用います。

```php
<?php

namespace ProjectName\Infrastructure\DataAccess\Http;

use GuzzleHttp\Client;
use Illuminate\Contracts\Config\Repository as Config;
use Psr\Http\Message\ResponseInterface;

abstract class BaseClient
{
    private Client $client;

    protected Config $config;

    protected array $defaultOptions;

    abstract protected function getBaseUri(): string;

    public function __construct(Client $client, Config $config)
    {
        $this->client = $client;
        $this->config = $config;
    }

    public function requestWithCommonKey($method, $uri, array $options = []): ResponseInterface
    {
        $options['headers'] = array_merge(($this->default_options['headers'] ?? []), [
            $this->config->get('projectName-api-client.projectName_key_name') => $this->config->get('projectName-api-client.projectName_key_value'),
        ]);
        $options = $this->getOptions($options);

        return $this->client->request($method, $uri, $options);
    }

    private function getOptions(array $options): array
    {
        return array_merge($this->defaultOptions, [
            'base_uri' => $this->getBaseUri(),
        ], $options);
    }
}
```

```php
<?php

namespace ProjectName\Infrastructure\DataAccess\Http;

use GuzzleHttp\Client;
use Illuminate\Contracts\Config\Repository as Config;

class Post extends BaseClient
{
    private array $options;

    public function __construct(Client $client, Config $config)
    {
        parent::__construct($client, $config);

        $this->options = $this->config->get('ProjectName.ProjectName.options');
    }

    protected function getBaseUri(): string
    {
        return $this->config->get('ProjectName-api-client.ProjectName.base_uri');
    }

    public function getArticles(string $name, string $email, string $password): array
    {
        $uri = $this->getBaseUri() . '/v1/post';
        $response = $this->requestWithCommonKey('GET', $uri, $this->options);
        $jsonResponse = $response->getBody()->getContents();

        return json_decode($jsonResponse, true);
    }
}
```

この様な感じで`API`を叩いてデータを`json`形式で受け取って`array`で返します。
ここから先は`Interface`を介して具象クラスに上記メソッドを呼ぶ処理を実装します。

```php
<?php

namespace ProjectName\Infrastructure\Repositories;

use ProjectName\Models\Domains\Entities;

interface PostRepositoryInterface
{
    public function getArticles(): array;
}
```

```php
namespace ProjectName\Infrastructure\Repositories\Database;

class PostRepository implements PostRepositoryInterface
{
    /** @var Post */
    protected $post;

    public function __construct(Post $post)
    {
        $this->post = $post;
    }

    public function getArticles()
    {
        return $this->post->getArticles();
    }
}
```

-- 更新終わり --

# これで終わりじゃない
ここまででも多くのレイヤーが登場しましたが、このほかにも`Value Object`や`Job`,`Enum`など多くのクラスを作成し、コードの保守性をあげ機能追加やメンテナンス性の向上などを図ることが可能です。
なので、まだまだ紹介する部分はありますが全部書くとかなり長くなってしまうので一旦ここまでにしておきます。

また、このアーキテクチャはまだ改良する余地があると思いっていて、例えば`Entity`は今の使い方だと[SOLID原則](https://postd.cc/solid-principles-every-developer-should-know/)に違反する可能性があったりします。

アーキテクチャは奥が深く、理解まで時間がかかるものの身につけるとリターンはでかい気がするので是非アーキテクチャに興味を持ってみてください。