【C#】ASP.NET Web API 2のチュートリアルアプリをクリーンアーキテクチャにする

この前[クリーンアーキテクチャのルールについての記事](https://qiita.com/seiya2130/items/69801fc2637eef596137)を書いたので、今回は現場で利用している技術でクリーンアーキテクチャを実装しました。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3239333336382f33656631653930302d386537342d633935342d326133332d3466343663373536393234342e6a706567.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474060/fe08e9ec-080e-9c49-0f4d-7dbb070c57d7.jpeg)

書籍で紹介されていた上記クラス図を参考にしています。対象のチュートリアルアプリは[こちら](https://learn.microsoft.com/ja-jp/aspnet/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api)です

## 変更前のクラス図・コード
クリーンアーキテクチャにする前のチュートリアルアプリのクラス図・コードです。
クラス図はシンプルなMVCになっています。
![変更前.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474060/b64e252c-9727-8a1e-d273-4ec27633770c.png)

コードはControllerで保持しているProductクラスの配列をViewが表示しています。
```Product.cs
namespace ProductApp.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}
```
```ProductsController.cs
namespace ProductsApp.Controllers
{
    public class ProductsController : ApiController
    {
        Product[] products = new Product[]
        {
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }
        };

        public IEnumerable<Product> GetAllProducts()
        {
            return products;
        }

        public IHttpActionResult GetProduct(int id)
        {
            var product = products.FirstOrDefault((p) => p.Id == id);
            if (product == null)
            {
                return NotFound();
            }
            return Ok(product);
        }
    }
}
```
```index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Product App</title>
</head>
<body>

    <div>
        <h2>All Products</h2>
        <ul id="products" />
    </div>
    <div>
        <h2>Search by ID</h2>
        <input type="text" id="prodId" size="5" />
        <input type="button" value="Search" onclick="find();" />
        <p id="product" />
    </div>

    <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script>
    <script>
    var uri = 'api/products';

    $(document).ready(function () {
      // Send an AJAX request
      $.getJSON(uri)
          .done(function (data) {
            // On success, 'data' contains a list of products.
            $.each(data, function (key, item) {
              // Add a list item for the product.
              $('<li>', { text: formatItem(item) }).appendTo($('#products'));
            });
          });
    });

    function formatItem(item) {
      return item.Name + ': $' + item.Price;
    }

    function find() {
      var id = $('#prodId').val();
      $.getJSON(uri + '/' + id)
          .done(function (data) {
            $('#product').text(formatItem(data));
          })
          .fail(function (jqXHR, textStatus, err) {
            $('#product').text('Error: ' + err);
          });
    }
    </script>
</body>
</html>
```

## 作業の流れ
クラス図をクリーンアーキテクチャにするために、以下の順に作業をしていきます。
1. 関心事の分離
1. 依存関係の逆転

依存関係を逆転させるためには、既に依存関係が成立している状態である必要があります。
そのため、依存関係の逆転は関心事を分離して依存関係が一度成立している後に実施します。

最初から完成形を作成しようとも考えましたが、実務では一気に行うのではなく、段階的に行うと想定しているため作業を分けています。

## ①関心事の分離

変更前のクラス図は責務が明確になっていないため、クラスの責務をもう少し細かく整理します。

### クラス分け
|  クラス  |  責務  |
| ---- | ---- |
|  Entity  |  システムが扱うデータのモデル  |
|  UseCase  |  そのシステムができること(ユースケース)  |
|  Controller  |  ユーザーの入力を受け取り、UseCaseへ伝える  |
|  Presenter  |  Viewに表示するデータの加工  |
|  Repository  | UseCaseからDBへデータの保存や変更を行う  |
|  DB  | データの保持  |
|  View  | ユーザーへのデータの表示  |

※変更前のクラス図にあった`Model`は、冒頭に挙げたクリーンアーキテクチャの図を参考に名前を`Entity`に変更しました。

またクラス間のデータの受け渡しは専用のDTO(Data Transfer Ojbect)で行います。
|  クラス  |  責務  |
| ---- | ---- |
|  InputData  | ControllerからUseCase間の入力データ  |
|  OutputData  | ControllerからUseCase間の出力データ  |
|  ViewModel  | PresenterからView間のデータ  |

### クラス図
ここまでのクラス図です。※今回DBは利用しないため、DBはMemoryとしています。
![リファクタ1.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474060/4c90dbb6-5ec0-4a9e-47c2-351c47a66cea.png)

### Presenterが冒頭の図と異なる理由
`Presenter`は冒頭の図では`UseCaseInteractor`から参照されていますが、`Controller`から参照するようにしています。

  当初は冒頭の図の通りに`UseCaseInteractor`から`Presenter`を参照するようにしましたが、`Presenter`が処理後の値である`ViewModel`を`View`に渡すためには`Controller`を経由する必要があるためです。

そのため`Controller`から`Presenter`と処理後の値である`ViewModel`を参照するように変更しました。

## ②依存関係の逆転
先程の作業で責務を分割することができましたが、円の内側から外側(抽象から具体)に依存している箇所(下記赤枠)があるため、この箇所の依存関係を逆転します。

また、依存の向きは円の内側に向かっていますが、自分が直接使っていない`Entity`に推移的に依存している箇所があります。(下記青枠)
推移的な依存関係について、クリーンアーキテクチャの著書の中では以下の記述があります。
  > 推移的な依存関係は「ソフトウェアのエンティティは自分が直接使っていないものに依存するべきではない」という大原則に違反している

推移的に依存していると、直接依存している箇所以外からの影響を受ける可能性が高くなってしまいます。
そのため、依存関係の逆転を利用して推移的な依存関係を解消します。

![リファクタ2の前_v2.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474060/ddab9925-8e37-fe68-bde8-9c1ee69db0e2.png)


### 追加するインターフェース
|  インターフェース  |  役割  |
| ---- | ---- |
|  IRepository  |  UseCaseとRepositoryの依存関係を逆転させる  |
|  IUseCase  |  Controllerの推移的な依存関係を解消する  |

### クラス図
![リファクタ2_v2.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474060/28be39c7-97e5-284a-6de8-274dc27a4784.png)

依存関係の逆転をしたことで、冒頭で紹介したクリーンアーキテクチャのクラス図にすることができました。

### Presenterにインターフェースがない理由
`Presenter`について、冒頭の図では`UseCase Interactor`が`Output Boundary `インターフェースから`Presenter`を呼び出しています。
これはクリーンアーキテクチャのルールである、内側のレイヤーが外側のレイヤーを直接参照しないようにするためです。

しかし、今回`Presenter`用のインターフェースは下記理由から設置していません。
- `Presenter`を呼び出す`Controller`は同じレイヤーで、内側のレイヤーから外側のレイヤーを参照していない。
- `Presenter`が参照している`ViewModel`は`Controller`も参照しているため、推移的な依存関係が発生していない。

## 依存関係の注入
インターフェースとそれを実装する具象クラスの依存関係の登録は、DIコンテナーを利用します。
今回はMicrosoftの記事を参考にUnityコンテナを使用しています。

```WebApiConfig.cs
 public static void Register(HttpConfiguration config)
{
    var container = new UnityContainer();
    container.RegisterType<IGetAllProductsRepository, GetAllProductsRepository>(new HierarchicalLifetimeManager());
    container.RegisterType<IGetAllProducts, GetAllProducts>(new HierarchicalLifetimeManager());

    config.DependencyResolver = new UnityResolver(container);
    // 以下はAPIのルーティングに関する処理のため略
```

## 変更後のコード
量が多くなってしまうため、初期表示の際にすべてのProductを取得するコードのみ記載しています。
全体は[GitHub](https://github.com/seiya2130/ProductApp/tree/CleanArchitecture)に載せています。

### Entity
```Product.cs
namespace Domain.Products
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}
```
### UseCase
```IGetAllProducts.cs
namespace Application.Products.GetAllProducts
{
    public interface IGetAllProducts
    {
        List<GetAllProductsOutputData> Handle();
    }
}
```
```GetAllProduct.cs
namespace Application.Products.GetAllProducts
{
    public class GetAllProducts : IGetAllProducts
    {
        private readonly IGetAllProductsRepository getAllProductsRepository;

        public GetAllProducts(IGetAllProductsRepository getAllProductsRepository)
        {
            this.getAllProductsRepository = getAllProductsRepository;
        }

        public List<GetAllProductsOutputData> Handle()
        {
            return this.getAllProductsRepository.GetAllProducts().Select(x => new GetAllProductsOutputData(x.Name, x.Price)).ToList();
        }
    }
}
```
### Repository
```IGetAllProductsRepository.cs
namespace Application.Products.GetAllProducts
{
    public interface IGetAllProductsRepository
    {
        List<Product> GetAllProducts();
    }
}
```
```GetAllProductsRepository.cs
namespace InMemoryInfrastructure.Products
{
    public class GetAllProductsRepository : IGetAllProductsRepository
    {
        public List<Product> GetAllProducts()
        {
            return InMemoryProduct.products.ToList();
        }
    }
}
```
```InMemoryProduct.cs
namespace InMemoryInfrastructure.Products
{
    public static class InMemoryProduct
    {
        public static Product[] products = new Product[]
        {
            new Product { Id = 1, Name = "Tomato Soup", Category = "Groceries", Price = 1 },
            new Product { Id = 2, Name = "Yo-yo", Category = "Toys", Price = 3.75M },
            new Product { Id = 3, Name = "Hammer", Category = "Hardware", Price = 16.99M }
        };
    }
}
```
### Controller
```ProductsController.cs
namespace ProductsApp.Controllers
{
    public class ProductsController : ApiController
    {
        private readonly IGetAllProducts getAllProducts;
        private readonly GetAllProductsPresenter getAllProductsPresenter;

        public ProductsController(IGetAllProducts getAllProducts, IGetProduct getProduct)
        {
            this.getAllProducts = getAllProducts;
            this.getAllProductsPresenter = new GetAllProductsPresenter();
        }

        public List<GetAllProductsViewModel> GetAllProducts()
        {
            var getAllProductsOutputDataList = this.getAllProducts.Handle();
            return this.getAllProductsPresenter.Complete(getAllProductsOutputDataList);
        }
    }
}
```
### Presenter
```GetAllProductsPresenter.cs
namespace ProductsApp.Controllers.GetAllProducts
{
    public class GetAllProductsPresenter
    {
        public List<GetAllProductsViewModel> Complete(List<GetAllProductsOutputData> getAllProductsOutputData)
        {
            var getAllProductsViewModelList = new List<GetAllProductsViewModel>();

            getAllProductsOutputData.ForEach(x =>
                getAllProductsViewModelList.Add(new GetAllProductsViewModel(x.Name, x.Price))); ;

            return getAllProductsViewModelList;
        }
    }
}
```
### DTO
```GetAllProductsOutputData.cs
namespace Application.Products.GetAllProducts
{
    public class GetAllProductsOutputData
    {
        public string Name { get; set; }
        public decimal Price { get; set; }

        public GetAllProductsOutputData(string name, decimal price)
        {
            Name = name;
            Price = price;
        }
    }
}
```
```GetAllProductsViewModel.cs
namespace ProductsApp.Controllers.GetAllProducts
{
    public class GetAllProductsViewModel
    {
        public string Name { get; set; }
        public decimal Price { get; set; }

        public GetAllProductsViewModel(string name, decimal price)
        {
            Name = name;
            Price = price;
        }
    }
}
```
※`View`から`Controller`へ渡すデータがないため、`InputData`用のクラスはありません。

# 参考
- [Clean Architecture 達人に学ぶソフトウェアの構造と設計](https://www.amazon.co.jp/dp/4048930656)
- [実装クリーンアーキテクチャ](https://qiita.com/nrslib/items/a5f902c4defc83bd46b8)
- [ASP.NET Web API 2 の依存関係の挿入](https://learn.microsoft.com/ja-jp/aspnet/web-api/overview/advanced/dependency-injection)
- [Web Api Start up Exceptions with IDependencyResolver implementation](https://stackoverflow.com/questions/24114970/web-api-start-up-exceptions-with-idependencyresolver-implementation)