Clean Architectureソース解説編(後半) →記述中

[前の記事へ](https://qiita.com/sakaki_developer/items/060e7c777ade8f45fddf)

# はじめに
クリーンアーキテクチャをソースコードを読み解き説明します。
以下のソースを参考にさせていただきます。

https://github.com/matthewrenze/clean-architecture-demo

# 記事の構成
- Clean Architecture超概要編(前半)
章構成: 要約 = 手段 + 効果
- Clean Architecture概要編(中半)
章構成: 手段の説明
- Clean Architectureソース解説編(後半)
**今ここ👆**

# アプリケーション構成
販売情報を登録、表示するアプリケーションです。
販売情報には、顧客、従業員、商品が含まれまれています。
Salse
├─Customers
├─Employees
└Products


プロジェクト構成（フォルダ）は以下のようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2171762/9347c367-cfe1-85c9-efd0-a2c2c696fac3.png)

| ディレクトリ名 | 役割 |
| -------------- | ---- |
| Application | ユースケースとコントローラが混在。ユースケースはビジネスロジックを実装し、コントローラはユーザーの入力をユースケースにルーティング |
| Common | 共通のユーティリティやヘルパークラスを格納 |
| Diagrams | プロジェクトの構造や設計を説明する図表を格納 |
| Domain | ビジネスロジックとエンティティを格納。各ドメインモデルを定義 |
| Infrastructure | データベース、ファイルシステム、ウェブサービスなどとのインタラクションを管理 |
| Persistence | データの永続性とデータストアとのインタラクションを管理 |
| Presentation | ユーザーインターフェースとユーザー体験を管理。ビューとそのロジックを格納 |
| Service | 外部サービスとのインタラクションを管理 |
| Specification | アプリケーションのテストを格納。単体テスト、統合テスト、エンドツーエンドテストを格納 |

細かいフォルダの責務の説明はいかに記述しているため、都度参考にしてください。

```markdown
clean-architecture-demo
├─Application
│  ├─Customers
│  │  └─Queries : 顧客情報の取得クエリ
│  ├─Employees
│  │  └─Queries : 従業員情報の取得クエリ
│  ├─Interfaces : アプリケーション層のサービスインターフェース
│  ├─Products
│  │  └─Queries : 製品情報の取得クエリ
│  ├─Properties : アプリケーション層のプロジェクト設定
│  └─Sales
│      ├─Commands : 販売情報の作成コマンド
│      └─Queries : 販売情報の取得クエリ
├─Common
│  ├─Dates : 日付関連共通処理
│  ├─Mocks : テスト用モックオブジェクト
│  └─Properties : 共通部分のプロジェクト設定
├─Diagrams : プロジェクト設計図
├─Domain
│  ├─Common : ドメイン共有エンティティや値オブジェクト
│  ├─Customers : 顧客関連ビジネスルール
│  ├─Employees : 従業員関連ビジネスルール
│  ├─Products : 製品関連ビジネスルール
│  ├─Properties : ドメイン層のプロジェクト設定
│  └─Sales : 販売関連ビジネスルール
├─Infrastructure
│  ├─Inventory : 在庫関連インフラストラクチャ処理
│  ├─Network : ネットワーク関連インフラストラクチャ処理
│  └─Properties : インフラストラクチャ層のプロジェクト設定
├─Persistence
│  ├─Customers : 顧客情報の永続化処理
│  ├─Employees : 従業員情報の永続化処理
│  ├─Properties : 永続化層のプロジェクト設定
│  └─Sales : 販売情報の永続化処理
├─Presentation
│  ├─App_Start : アプリケーション起動設定
│  ├─Content : 静的ファイル（CSS等）
│  ├─Customers
│  │  └─Views : 顧客情報ビューとロジック
│  ├─DependencyResolution : 依存性解決設定
│  ├─Employees
│  │  └─Views : 従業員情報ビューとロジック
│  ├─Home
│  │  └─Views : ホームページビューとロジック
│  ├─Products
│  │  └─Views : 製品情報ビューとロジック
│  ├─Properties : プレゼンテーション層のプロジェクト設定
│  ├─Sales
│  │  ├─Models : 販売情報モデル
│  │  ├─Services : 販売情報サービス
│  │  └─Views : 販売情報ビューとロジック
│  └─Shared
│      └─Views : 複数ビュー共有部品
├─Service
│  ├─App_Start : サービス起動設定
│  ├─Customers : 顧客関連サービス
│  ├─Employees : 従業員関連サービス
│  ├─Products : 製品関連サービス
│  ├─Properties : サービス層のプロジェクト設定
│  └─Sales : 販売関連サービス
└─Specification
    ├─Common : テスト共通設定やユーティリティ
    ├─Customers
    │  └─GetCustomersList : 顧客情報取得テスト
    ├─Employees
    │  └─GetEmployeesList : 従業員情報取得テスト
    ├─Products : 製品関連テスト
    ├─Properties : テスト層のプロジェクト設定
    └─Sales
        ├─CreateASale : 販売作成テスト
        ├─GetSaleDetails : 販売詳細取得テスト
        └─GetSalesList : 販売情報取得テスト

```
# ソース解説
データをDBに保存するフローを例に説明します。
以下の画像のようなフローになっています。

またSQRSパターンで実装されています。
SQRSパターンとは
DBの書き込みをCommand(Write) と Query(Read) のモデルを分離するパターン

https://zenn.dev/shmi593/articles/c1baeb2d453929


![Group 42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2171762/df1d3eb6-7c4f-c995-3c1a-ae5e6cc77150.png)

## CreateSalesCommand : DBへの書き込み処理 Command(Write)
### SalesController
```C#
namespace CleanArchitecture.Presentation.Sales
{
    [RoutePrefix("sales")]
    public class SalesController : Controller
    {
        private readonly IGetSalesListQuery _salesListQuery;
        private readonly IGetSaleDetailQuery _saleDetailQuery;
        private readonly ICreateSaleViewModelFactory _factory;
        private readonly ICreateSaleCommand _createCommand;

        public SalesController(
            IGetSalesListQuery salesListQuery,
            IGetSaleDetailQuery saleDetailQuery,
            ICreateSaleViewModelFactory factory,
            ICreateSaleCommand createCommand)
        {
            _salesListQuery = salesListQuery;
            _saleDetailQuery = saleDetailQuery;
            _factory = factory;
            _createCommand = createCommand;
        }

        /* Queryの記述は省略 */
        [Route("create")]
        public ViewResult Create()
        {
            var viewModel = _factory.Create();

            return View(viewModel);
        }

        [Route("create")]
        [HttpPost]
        public RedirectToRouteResult Create(CreateSaleViewModel viewModel)
        {
            var model = viewModel.Sale;            

            _createCommand.Execute(model);

            return RedirectToAction("index", "sales");
        }

}

```
### CreateSalesCommand
```C#
namespace CleanArchitecture.Application.Sales.Commands.CreateSale
{
    public interface ICreateSaleCommand
    {
        void Execute(CreateSaleModel model);
    }
}
```
```C#
namespace CleanArchitecture.Application.Sales.Commands.CreateSale
{
    public class CreateSaleCommand
        : ICreateSaleCommand
    {
        private readonly IDateService _dateService;
        private readonly IDatabaseService _database;
        private readonly ISaleFactory _factory;
        private readonly IInventoryService _inventory;

        public CreateSaleCommand(
            IDateService dateService,
            IDatabaseService database,
            ISaleFactory factory,
            IInventoryService inventory)
        {
            _dateService = dateService;
            _database = database;
            _factory = factory;
            _inventory = inventory;
        }

        public void Execute(CreateSaleModel model)
        {
            // 1. Service: 日付を取得
            var date = _dateService.GetDate();

            // 2. Entity: データをModelに格納ロジック  
            var customer = _database.Customers
                .Single(p => p.Id == model.CustomerId);

            var employee = _database.Employees
                .Single(p => p.Id == model.EmployeeId);

            var product = _database.Products
                .Single(p => p.Id == model.ProductId);

            var quantity = model.Quantity;

            var sale = _factory.Create(
                date,
                customer, 
                employee, 
                product, 
                quantity);

            _database.Sales.Add(sale);

            // 3. Gateway: DBへ保存
            _database.Save();

            // 4. Gateway: Webへ保存
            _inventory.NotifySaleOccurred(product.Id, quantity);
        }
    }
}
```


## CreateSalesCommandで使用したクラスを解説
|クラス|レイヤー|説明|
|---|---|---|
|DateService|Utility|現在時刻を取得|
|DatabaseService|DB|データベースへデータを保存|
|SaleFactory|UseCase|Entityを生成し、データを格納|
|InventoryService|Web|Jsonを生成してWebApiへ渡している|

### DateService
IDateServiceを継承していて、現在時刻を取得しています。
Utility的なレイヤーだと解釈しています。
```C#
namespace CleanArchitecture.Common.Dates
{
    public interface IDateService
    {
        DateTime GetDate();
    }
}
```

```C#
namespace CleanArchitecture.Common.Dates
{
    public class DateService : IDateService
    {
        public DateTime GetDate()
        {
            return DateTime.Now.Date;
        }
    }
}
```
### DatabaseService
IDatabaseはApplicationプロジェクトに存在していて、PresistanceプロジェクトのDatabaseServiceから依存されています。
```C#
namespace CleanArchitecture.Application.Interfaces
{
    public interface IDatabaseService
    {
        // Entityをモデルに使用
        IDbSet<Customer> Customers { get; set; }

        IDbSet<Employee> Employees { get; set; }
        
        IDbSet<Product> Products { get; set; }
        
        IDbSet<Sale> Sales { get; set; }

        void Save();
    }
}

```



```C#
namespace CleanArchitecture.Persistence
{
    public class DatabaseService : DbContext, IDatabaseService
    {
        // Entityをモデルに使用
        public IDbSet<Customer> Customers { get; set; }

        public IDbSet<Employee> Employees { get; set; }

        public IDbSet<Product> Products { get; set; }

        public IDbSet<Sale> Sales { get; set; }

        public DatabaseService() : base("CleanArchitecture")
        {
            Database.SetInitializer(new DatabaseInitializer());
        }

        public void Save()
        {
            this.SaveChanges();
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Configurations.Add(new CustomerConfiguration());
            modelBuilder.Configurations.Add(new EmployeeConfiguration());
            modelBuilder.Configurations.Add(new ProductConfiguration());
            modelBuilder.Configurations.Add(new SaleConfiguration());
        }
    }
}

```
### InventoryService
InventoryServiceも同様にApplicationプロジェクトに存在していて、InfrastructureプロジェクトのInventoryServiceから依存されています。

```C#
namespace CleanArchitecture.Application.Interfaces
{
    public interface IInventoryService
    {
        void NotifySaleOccurred(int productId, int quantity);
    }
}
```
```C#
using System;
using System.Collections.Generic;
using System.Linq;
using CleanArchitecture.Application.Interfaces;
using CleanArchitecture.Infrastructure.Network;

namespace CleanArchitecture.Infrastructure.Inventory
{
    public class InventoryService 
        : IInventoryService
    {
        // Note: these are hard coded to keep the demo simple
        private const string AddressTemplate = "http://abc123.com/inventory/products/{0}/notifysaleoccured/";
        private const string JsonTemplate = "{{\"quantity\": {0}}}";

        private readonly IWebClientWrapper _client;

        public InventoryService(IWebClientWrapper client)
        {
            _client = client;
        }

        public void NotifySaleOccurred(int productId, int quantity)
        {
            var address = string.Format(AddressTemplate, productId);

            var json = string.Format(JsonTemplate, quantity);

            _client.Post(address, json);
        }
    }
}
```
### SaleFactory
Entityオブジェクトを生成しています。

```C#
namespace CleanArchitecture.Application.Sales.Commands.CreateSale.Factory
{
    public interface ISaleFactory
    {
        Sale Create(DateTime date, Customer customer, Employee employee, Product product, int quantity);
    }
}
```
```C#
namespace CleanArchitecture.Application.Sales.Commands.CreateSale.Factory
{
    public class SaleFactory : ISaleFactory
    {
        public Sale Create(DateTime date, Customer customer, Employee employee, Product product, int quantity)
        {
            var sale = new Sale();

            sale.Date = date;

            sale.Customer = customer;

            sale.Employee = employee;

            sale.Product = product;

            sale.UnitPrice = sale.Product.Price;

            sale.Quantity = quantity;

            // Note: Total price is calculated in domain logic

            return sale;
        }
    }
}

```



## SalesController : DBから読み込み Query(Read)
DBからデータを読み込みリストを取得する処理になります。

![w:300](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2171762/ae1de269-2989-bf37-8b8b-d7cc465f5cca.png)

### SalesController
```C#
namespace CleanArchitecture.Presentation.Sales
{
    [RoutePrefix("sales")]
    public class SalesController : Controller
    {
        private readonly IGetSalesListQuery _salesListQuery;
        private readonly IGetSaleDetailQuery _saleDetailQuery;
        private readonly ICreateSaleViewModelFactory _factory;
        private readonly ICreateSaleCommand _createCommand;

        public SalesController(
            IGetSalesListQuery salesListQuery,
            IGetSaleDetailQuery saleDetailQuery,
            ICreateSaleViewModelFactory factory,
            ICreateSaleCommand createCommand)
        {
            _salesListQuery = salesListQuery;
            _saleDetailQuery = saleDetailQuery;
            _factory = factory;
            _createCommand = createCommand;
        }

        [Route("")]
        public ViewResult Index()
        {
            var sales = _salesListQuery.Execute();

            return View(sales);
        }

        [Route("{id:int}")]
        public ViewResult Detail(int id)
        {
            var sale = _saleDetailQuery.Execute(id);

            return View(sale);
        }
        /* commandの記述は省略 */
}

```

### GetSaleDetailQuery
```C#
namespace CleanArchitecture.Application.Sales.Queries.GetSaleDetail
{
    public interface IGetSaleDetailQuery
    {
        SaleDetailModel Execute(int id);
    }
```

```C#
namespace CleanArchitecture.Application.Sales.Queries.GetSaleDetail
{
    public class GetSaleDetailQuery
        : IGetSaleDetailQuery
    {
        private readonly IDatabaseService _database;

        public GetSaleDetailQuery(IDatabaseService database)
        {
            _database = database;
        }

        public SaleDetailModel Execute(int saleId)
        {
            var sale = _database.Sales
                .Where(p => p.Id == saleId)
                .Select(p => new SaleDetailModel()
                {
                    Id = p.Id, 
                    Date = p.Date,
                    CustomerName = p.Customer.Name,
                    EmployeeName = p.Employee.Name,
                    ProductName = p.Product.Name,
                    UnitPrice = p.UnitPrice,
                    Quantity = p.Quantity,
                    TotalPrice = p.TotalPrice
                })
                .Single();

            return sale;
        }
    }
}


```

### GetSalesListQuery
```C#

namespace CleanArchitecture.Application.Sales.Queries.GetSalesList
{
    public interface IGetSalesListQuery
    {
        List<SalesListItemModel> Execute();
    }
}

```
```C#
using System;
using System.Collections.Generic;
using System.Linq;
using CleanArchitecture.Application.Interfaces;

namespace CleanArchitecture.Application.Sales.Queries.GetSalesList
{
    public class GetSalesListQuery 
        : IGetSalesListQuery
    {
        private readonly IDatabaseService _database;

        public GetSalesListQuery(IDatabaseService database)
        {
            _database = database;
        }

        public List<SalesListItemModel> Execute()
        {
            var sales = _database.Sales
                .Select(p => new SalesListItemModel()
                {
                    Id = p.Id, 
                    Date = p.Date,
                    CustomerName = p.Customer.Name,
                    EmployeeName = p.Employee.Name,
                    ProductName = p.Product.Name,
                    UnitPrice = p.UnitPrice,
                    Quantity = p.Quantity,
                    TotalPrice = p.TotalPrice
                });

            return sales.ToList();
        }
    }
}


```
### DatabaseService
IDatabaseはApplicationプロジェクトに存在していて、PresistanceプロジェクトのDatabaseServiceから依存されています。

```C#
namespace CleanArchitecture.Persistence
{
    public class DatabaseService : DbContext, IDatabaseService
    {
        public IDbSet<Customer> Customers { get; set; }

        public IDbSet<Employee> Employees { get; set; }

        public IDbSet<Product> Products { get; set; }

        public IDbSet<Sale> Sales { get; set; }

        public DatabaseService() : base("CleanArchitecture")
        {
            Database.SetInitializer(new DatabaseInitializer());
        }

        public void Save()
        {
            this.SaveChanges();
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Configurations.Add(new CustomerConfiguration());
            modelBuilder.Configurations.Add(new EmployeeConfiguration());
            modelBuilder.Configurations.Add(new ProductConfiguration());
            modelBuilder.Configurations.Add(new SaleConfiguration());
        }
    }
}


```

```C#
using System.Data.Entity;
using CleanArchitecture.Domain.Customers;
using CleanArchitecture.Domain.Employees;
using CleanArchitecture.Domain.Products;
using CleanArchitecture.Domain.Sales;

namespace CleanArchitecture.Application.Interfaces
{
    public interface IDatabaseService
    {
        // Entityをモデルに使用
        IDbSet<Customer> Customers { get; set; }

        IDbSet<Employee> Employees { get; set; }
        
        IDbSet<Product> Products { get; set; }
        
        IDbSet<Sale> Sales { get; set; }

        void Save();
    }
}

```

[前の記事へ](https://qiita.com/sakaki_developer/items/060e7c777ade8f45fddf)


# 参考
https://buildersbox.corp-sansan.com/entry/2019/07/10/110000