你的浏览器还没开启 Javascript 功能!

ASP.NET Core MVC 階層化アーキテクチャ Chap3 (個別Repositoryで独自処理を実装する)

前回のコンテンツASP.NET Core MVC 階層化アーキテクチャ Chap2 (Generic Repositoryで共通化を図る)では、個別repositoryのデータ操作処理を抽出して、Generic repositoryを作成しました。

データ操作は共通化されたRipositoryできて完璧のように思えますが、業務システムではGeneric repositoryだけでは複雑な要件を満たすことはできません。

今回はクラスごとの異なるデータ操作をしたいときに、Repositoryの実装方法を紹介したいと思います。

前提

このコンテンツで扱うこと

  • Generic Repositoryの継承
  • 個別Repositoryの実装

開発環境

環境/ソフトウェア内容
オペレーティングシステムWindows 10 1903
.NET Core SDK3.0.100
IDEVisual Studio Code 1.39.1
BrowserGoogle Chrome 78.0.3904.70

前回のおさらい

前回の改修を終え、プロジェクトフォルダの構成はおそらく下記の図のようになっていると思います。

Generic repositoryが既にあるため、個別Repository(CustomerRepository, SupplierRepository)とインターフェース(ICustomerRepository, ISupplierRepository)はすべて削除済みです。

そして、クラスを取得するメソッドGetOneAsyncの引数はLinq Expressionを使っています。

// IRepository
Task<bool> ExistsAsync(Expression<Func<TEntity, bool>> expression);

// Generic Repository
public async Task<TEntity> GetOneAsync(Expression<Func<TEntity, bool>> expression)
{
    return await db.Set<TEntity>().FirstOrDefaultAsync(expression);
}

// Controller
var customers = await _customerRepository.GetOneAsync(x => x.CustomerId == id);

例えば、customersを取得するための引数をLinq Expressionではなく、主キー(CustomerId)で取得したい
となった場合、Generic Repositoryの機能だけではこの要件を満たせません。

そうなると、当然このままでは取得条件が複雑になってくるデータ操作のロジックはControllerで書くことになり、
リクエストを制御する役割であるControllerにビジネスロジックとデータ操作をさせることは関心の分離にも違反します。

更に、ControllerとRepositoryの両方は同じ人で実装する必要があり、複数人同時開発の効率も良くありません。

個別Repositoryの実装

このようなケースの場合、特別な操作は個別Repository、共通処理はGeneric Repositoryの構成になるように改修します。

個別インターフェースを作成

まずは、ICustomerRepositoryとISupplierRepositoryをもう一度作成し、
個別機能のメソッドのみ定義します。

  • ICustomerRepository.cs
using System.Threading.Tasks;

namespace ds.NorthwindApp.Web.Models.Interface
{
    public interface ICustomerRepository : IRepository<Customers>
    {
        Task<Customers> GetOneByIdAsync(string id);

        Task<bool> ExistAsync(string id);
    }
}
  • ISupplierRepository.cs
using System.Threading.Tasks;

namespace ds.NorthwindApp.Web.Models.Interface
{
    public interface ISupplierRepository : IRepository<Suppliers>
    {
        Task<Suppliers> GetOneByIdAsync(int id);
        Task<bool> ExistAsync(int id);
    }
}

個別Repositoryを作成

個別Repositoryを作成し、データ操作(CRUD)の共通処理はGeneric Repositoryを継承し、個別処理は実装します。

  • CustomerRepository.cs
using ds.NorthwindApp.Web.Models.Interface;
using System.Threading.Tasks;

namespace ds.NorthwindApp.Web.Models.Repository
{
    public class CustomerRepository : GenericRepository<Customers>, ICustomerRepository
    {
        public CustomerRepository(NorthwindContext northwindContext)
            :base(northwindContext)
        {
        }

        public async Task<Customers> GetOneByIdAsync(string id)
        {
            return await this.GetOneAsync(x => x.CustomerId == id);
        }

        public async Task<bool> ExistAsync(string id)
        {
            return await this.ExistAsync(x => x.CustomerId == id);
        }
    }
}
  • SupplierRepository.cs
using ds.NorthwindApp.Web.Models.Interface;
using System.Threading.Tasks;

namespace ds.NorthwindApp.Web.Models.Repository
{
    public class SupplierRepository : GenericRepository<Suppliers>, ISupplierRepository
    {
        public SupplierRepository(NorthwindContext northwindContext)
            : base(northwindContext)
        {
        }

        public async Task<Suppliers> GetOneByIdAsync(int id)
        {
            return await this.GetOneAsync(x => x.SupplierId == id);
        }

        public async Task<bool> ExistAsync(int id)
        {
            return await this.ExistAsync(x => x.SupplierId == id);
        }

    }
}

Controllerの修正

Controllerは今までGeneric Repositoryのインターフェースから個別Repositoryのインターフェースに差し替えます。
id取得のメソッドを修正します。

  • CustomersController.cs
public class CustomersController : Controller
{

    private readonly ICustomerRepository _customerRepository;

    public CustomersController(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }

    // 引数をLinq Expressionからidへ修正
    _customerRepository.GetOneByIdAsync(id);
    _customerRepository.ExistAsync(customers.CustomerId)

}
  • SuppliersController.cs
public class SuppliersController : Controller
{
    private readonly ISupplierRepository _supplierRepository;

    public SuppliersController(ISupplierRepository supplierRepository)
    {
        _supplierRepository = supplierRepository;
    }

    // 引数をLinq Expressionからidへ修正
    _supplierRepository.GetOneByIdAsync(id.Value);
    _supplierRepository.ExistAsync(suppliers.SupplierId);
}

依存性注入

Startup.csに個別Repositoryの依存性注入を行います。

public void ConfigureServices(IServiceCollection services)
{
    // 省略

    // Repository
    services.AddScoped(typeof(IRepository<>), typeof(GenericRepository<>));
    services.AddScoped<ICustomerRepository,CustomerRepository>();
    services.AddScoped<ISupplierRepository, SupplierRepository>();

    // 省略
}

まとめ

このシリーズを最初から読んでいる人は今回のコンテンツで混乱するかもしれません。

  1. 最初に個別Repositoryを作って
  2. 共通処理を抜き出し、Generic Repositoryを作る
  3. 今度はまた個別Repositoryを作る

朝令暮改はいい加減にしろっと思うかもしれません。

このような構成になったには理由がありまして、徐々にコード改修を通じて、階層化アーキテクチャを本質的に理解を深めるためです。

徐々にコード改修することで、「なぜこの実装になったのか」 がより明確になります。

もちろん、最初から完成版のコードを出してもいいですが、それでは途中経過がわからず、なんとなくの理解になってしまう恐れがあるからです。

次回からはプロジェクトの責務をより分けるように、本格的にコードをWeb UI層、データアクセス層、ビジネスロジック層に分割していきたいと思います。

備考

今回作成したソースコードです。

GitHubリポジトリ

では!!( `ー´)ノ