インフラストラクチャの永続レイヤーの設計Design the infrastructure persistence layer

データ永続化コンポーネントは、マイクロサービス (つまり、マイクロサービスのデータベース) の境界内でホストされているデータへのアクセスを提供します。Data persistence components provide access to the data hosted within the boundaries of a microservice (that is, a microservice’s database). 内容としては、リポジトリや作業単位クラス (カスタム Entity Framework (EF) DbContext など) などのコンポーネントの実際の実装が含まれています。They contain the actual implementation of components such as repositories and Unit of Work classes, like custom Entity Framework (EF) DbContext objects. EF DbContext でリポジトリ パターンと Unit of Work パターンの両方を実装します。EF DbContext implements both, the Repository and the Unit of Work patterns.

リポジトリ パターンThe Repository pattern

リポジトリは、データ ソースへのアクセスに必要なロジックをカプセル化するクラスまたはコンポーネントです。Repositories are classes or components that encapsulate the logic required to access data sources. リポジトリは一般的なデータ アクセス機能を一元管理して保守性を向上させ、ドメイン モデル レイヤーからデータベースにアクセスするためのインフラストラクチャやテクノロジを切り離します。They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer. Entity Framework などのオブジェクト リレーショナル マッパー (ORM) を使用する場合、LINQ と厳密な型指定により、実装する必要があるコードが簡素化されます。If you use an Object-Relational Mapper (ORM) like Entity Framework, the code that must be implemented is simplified, thanks to LINQ and strong typing. これによりデータ アクセスの組み込みではなく、データ永続化ロジックに集中できるようになります。This lets you focus on the data persistence logic rather than on data access plumbing.

リポジトリ パターンは、ドキュメントが整備されたデータ ソース操作方法です。The Repository pattern is a well-documented way of working with a data source. Martin Fowler は、著書『エンタープライズ アプリケーション アーキテクチャ パターン』の中でリポジトリのことを次のように述べています。In the book Patterns of Enterprise Application Architecture, Martin Fowler describes a repository as follows:

リポジトリは、ドメイン モデル レイヤーとデータ マッピングの間を媒介するタスクを実行し、メモリ内のドメイン オブジェクト セットに似たような動作をする。A repository performs the tasks of an intermediary between the domain model layers and data mapping, acting in a similar way to a set of domain objects in memory. クライアント オブジェクトは宣言によってクエリを構築し、リポジトリに送信して回答を得る。Client objects declaratively build queries and send them to the repositories for answers. 概念上、リポジトリはデータベースに格納されている一連のオブジェクトとオブジェクト上で実行可能な操作をカプセル化し、永続レイヤーに近い方法を提供する。Conceptually, a repository encapsulates a set of objects stored in the database and operations that can be performed on them, providing a way that is closer to the persistence layer. また、リポジトリは、作業ドメインとデータの割り当て、すなわちマッピングとの依存関係を明確かつ一方向に切り離すという目的をサポートする。Repositories, also, support the purpose of separating, clearly and in one direction, the dependency between the work domain and the data allocation or mapping.

集約ごとにリポジトリを定義するDefine one repository per aggregate

各集約または集約ルートに1 つのリポジトリ クラスを作成する必要があります。For each aggregate or aggregate root, you should create one repository class. ドメイン駆動設計 (DDD) パターンに基づくマイクロサービスでは、リポジトリはデータベースの更新に使用する唯一のチャネルです。In a microservice based on Domain-Driven Design (DDD) patterns, the only channel you should use to update the database should be the repositories. これは、リポジトリには集約ルートとの一対一の関係があるためで、これにより集約の不変性とトランザクションの一貫性が管理されます。This is because they have a one-to-one relationship with the aggregate root, which controls the aggregate’s invariants and transactional consistency. データベースのクエリは他のチャネルで実行することもできますが (CQRS アプローチに従って実行することも可能)、それは、クエリではデータベースの状態が変更されないためです。It's okay to query the database through other channels (as you can do following a CQRS approach), because queries don't change the state of the database. ただし、トランザクション領域 (つまり、更新) は、常にリポジトリと集約ルートで制御する必要があります。However, the transactional area (that is, the updates) must always be controlled by the repositories and the aggregate roots.

基本的に、リポジトリでは、データベースから取得されたデータをドメイン エンティティの形式でメモリ内に設定することができます。Basically, a repository allows you to populate data in memory that comes from the database in the form of the domain entities. メモリ内に設定されたエンティティは、トランザクションによって変更し、再度データベースに保存することができます。Once the entities are in memory, they can be changed and then persisted back to the database through transactions.

前述したように、CQS/CQRS アーキテクチャ パターンを使用している場合、Dapper を使用した単純な SQL ステートメントによって実行されるドメイン モデルからの副クエリによって最初のクエリが実行されます。As noted earlier, if you're using the CQS/CQRS architectural pattern, the initial queries are performed by side queries out of the domain model, performed by simple SQL statements using Dapper. この方法は、必要な任意のテーブルを照会および結合可能で、クエリが集約からのルールによって制限されないため、リポジトリよりはるかに柔軟です。This approach is much more flexible than repositories because you can query and join any tables you need, and these queries aren't restricted by rules from the aggregates. その場合、データはプレゼンテーション レイヤーまたはクライアント アプリに移動します。That data goes to the presentation layer or client app.

ユーザーが変更を行うと、更新されるデータは、クライアント アプリまたはプレゼンテーション レイヤーからアプリケーションレイヤー (Web API サービスなど) に移動します。If the user makes changes, the data to be updated comes from the client app or presentation layer to the application layer (such as a Web API service). コマンド ハンドラーでコマンドを受信したら、リポジトリを使用して、更新するデータをデータベースから取得します。When you receive a command in a command handler, you use repositories to get the data you want to update from the database. メモリ内で、コマンドを使用して渡されたデータを更新した後、トランザクションによりデータベース内のデータ (ドメイン エンティティ) を追加または更新します。You update it in memory with the data passed with the commands, and you then add or update the data (domain entities) in the database through a transaction.

もう一度強調しますが、図 7-17 に示すように、集約ルートごとにリポジトリを 1 つのみ定義することが重要です。It's important to emphasize again that you should only define one repository for each aggregate root, as shown in Figure 7-17. 集約内のすべてのオブジェクト間のトランザクションの一貫性を維持するという集約ルートの目標を達成するため、データベース内の各テーブルのリポジトリは作成しません。To achieve the goal of the aggregate root to maintain transactional consistency between all the objects within the aggregate, you should never create a repository for each table in the database.

ドメイン レイヤーとインフラストラクチャ レイヤー間のリレーションシップ:バイヤー集約は IBuyerRepository に依存し、Order Aggregate は IOrderRepository インターフェイスに依存し、これらのインターフェイスは UnitOfWork に依存する対応するリポジトリによってインフラストラクチャ レイヤーに実装され、データ層のテーブルにアクセスする場所にも実装されます。

図 7-17Figure 7-17. リポジトリ、集約、およびデータベース テーブル間のリレーションシップThe relationship between repositories, aggregates, and database tables

リポジトリごとに 1 つの集約ルートを適用Enforce one aggregate root per repository

リポジトリ設計を実装する際に、集約ルートのみにリポジトリを設定するというルールを適用することが重要な場合があります。It can be valuable to implement your repository design in such a way that it enforces the rule that only aggregate roots should have repositories. 操作するエンティティの型を制約するジェネリック型または基本型のリポジトリを作成し、IAggregateRoot マーカー インターフェイスを設定することができます。You can create a generic or base repository type that constrains the type of entities it works with to ensure they have the IAggregateRoot marker interface.

このように、インフラストラクチャ レイヤーで実装されている各リポジトリ クラスに独自のコントラクトまたはインターフェイスが実装されます (次のコードを参照)。Thus, each repository class implemented at the infrastructure layer implements its own contract or interface, as shown in the following code:

namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
    public class OrderRepository : IOrderRepository
    {
      // ...
    }
}

リポジトリの各インターフェイスには、ジェネリック IRepository インターフェイスが実装されます。Each specific repository interface implements the generic IRepository interface:

public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);
    // ...
}

しかし、各リポジトリが単一の集計に関連付けられる規則を適用するコードを用意するには、ジェネリック型のリポジトリを実装することをお勧めします。However, a better way to have the code enforce the convention that each repository is related to a single aggregate is to implement a generic repository type. そうすることで、特定の集約を対象とするリポジトリを使用していることが明確になります。That way, it's explicit that you're using a repository to target a specific aggregate. これは、次のコードのように、そのジェネリックの IRepository 基底インターフェイスを実装することで簡単に行えます。That can be easily done by implementing a generic IRepository base interface, as in the following code:

public interface IRepository<T> where T : IAggregateRoot
{
    //....
}

アプリケーション ロジックのテストを容易にするリポジトリ パターンThe Repository pattern makes it easier to test your application logic

リポジトリ パターンを利用すると、単体テストでアプリケーションを簡単にテストすることができます。The Repository pattern allows you to easily test your application with unit tests. 単体テストの対象はインフラストラクチャではなく、コードのみであるため、リポジトリの抽象化により目的を達成しやすくなります。Remember that unit tests only test your code, not infrastructure, so the repository abstractions make it easier to achieve that goal.

前のセクションで説明したように、リポジトリ インターフェイスをドメイン モデル レイヤーに定義および配置し、アプリケーション レイヤー (Web API マイクロサービスなど) が、実際のリポジトリ クラスを実装しているインフラストラクチャ レイヤーに直接依存しないようにすることをお勧めします。As noted in an earlier section, it's recommended that you define and place the repository interfaces in the domain model layer so the application layer, such as your Web API microservice, doesn't depend directly on the infrastructure layer where you've implemented the actual repository classes. これにより、Web API のコント ローラーで依存関係の挿入を使用して、データベースのデータではなく疑似データを返すモック リポジトリを実装できます。By doing this and using Dependency Injection in the controllers of your Web API, you can implement mock repositories that return fake data instead of data from the database. このような分離アプローチでは、データベースへの接続を必要とせず、アプリケーションのロジックに集中する単体テストを作成し、実行することができます。This decoupled approach allows you to create and run unit tests that focus the logic of your application without requiring connectivity to the database.

データベースに対して膨大なテストを実行することには、データベースへの接続が失敗する可能性があることに加え、さらに大きな 2 つの理由から問題があります。Connections to databases can fail and, more importantly, running hundreds of tests against a database is bad for two reasons. その 1 つは、テスト量の多さにより時間がかかる可能性があることです。First, it can take a long time because of the large number of tests. もう一つは、データベースのレコードが変更され、テスト結果に影響して、データの一貫性がなくなる可能性があるということです。Second, the database records might change and impact the results of your tests, so that they might not be consistent. データベースに対するテストは単体テストではなく、統合テストです。Testing against the database isn't a unit test but an integration test. 多数の単体テストを高速で実行する必要がありますが、データベースに対する統合テストは大量に行う必要はありません。You should have many unit tests running fast, but fewer integration tests against the databases.

単体テストの問題の分離という観点から、ロジックはメモリ内のドメイン エンティティで動作します。In terms of separation of concerns for unit tests, your logic operates on domain entities in memory. ドメイン エンティティはリポジトリ クラスで提供されるものと想定します。It assumes the repository class has delivered those. また、ロジックによって変更されたドメイン エンティティは、リポジトリ クラスに適切に保存されるものと想定します。Once your logic modifies the domain entities, it assumes the repository class will store them correctly. ここでの重要なポイントは、単体テストはドメイン モデルとそのドメイン ロジックに対して作成するということです。The important point here is to create unit tests against your domain model and its domain logic. 集約ルートは DDD の主な整合性境界です。Aggregate roots are the main consistency boundaries in DDD.

eShopOnContainers に実装されたリポジトリは、その変更追跡を使用するリポジトリ パターンと Unit of Work パターンでの EF Core の DbContext 実装に依存するため、この機能を複製することはありません。The repositories implemented in eShopOnContainers rely on EF Core’s DbContext implementation of the Repository and Unit of Work patterns using its change tracker, so they don’t duplicate this functionality.

リポジトリ パターンと、従来のデータ アクセス クラス (DAL クラス) パターンの違いThe difference between the Repository pattern and the legacy Data Access class (DAL class) pattern

データ アクセス オブジェクトは、データ アクセスおよび永続化操作を記憶域に対して直接実行します。A data access object directly performs data access and persistence operations against storage. リポジトリでは、メモリ内の作業単位オブジェクトの操作対象データにマークを付けますが (例: DbContext クラスを使用する場合の EF)、更新はデータベースに対してすぐには実行されません。A repository marks the data with the operations you want to perform in the memory of a unit of work object (as in EF when using the DbContext class), but these updates aren't performed immediately to the database.

作業単位は複数の挿入、更新、または削除操作が関与する単一のトランザクションとして表されます。A unit of work is referred to as a single transaction that involves multiple insert, update, or delete operations. つまり、特定のユーザー操作 (Web サイトへの登録など) に対して、すべての挿入、更新、および削除の操作が単一のトランザクションとして処理されることを意味します。In simple terms, it means that for a specific user action, such as a registration on a website, all the insert, update, and delete operations are handled in a single transaction. これは、複数のデータベース トランザクションを対話方法で処理するよりも効率的です。This is more efficient than handling multiple database transactions in a chattier way.

これらの複数の永続化操作は、後でアプリケーション レイヤーのコードが命令を発行したときに、1 アクションで実行されます。These multiple persistence operations are performed later in a single action when your code from the application layer commands it. 実際のデータベース記憶域にメモリ内の変更を適用する決定は、通常、作業単位パターンに基づいて行われます。The decision about applying the in-memory changes to the actual database storage is typically based on the Unit of Work pattern. EF には、作業単位パターンは DbContext として実装されます。In EF, the Unit of Work pattern is implemented as the DbContext.

多くの場合、このパターンまたは記憶域に対する操作の適用方法により、アプリケーションのパフォーマンスを向上させ、不整合の可能性を低減することができます。In many cases, this pattern or way of applying operations against the storage can increase application performance and reduce the possibility of inconsistencies. また、意図したすべての操作が単一のトランザクションの一部としてコミットされるので、データベース テーブル内でブロックされるトランザクションも少なくなります。It also reduces transaction blocking in the database tables, because all the intended operations are committed as part of one transaction. これは、データベースに対して多数の単独の操作を実行する場合と比べて効率的です。This is more efficient in comparison to executing many isolated operations against the database. そのため、小規模な個別のトランザクションを多数実行するのではなく、同じトランザクション内で複数の更新操作をグループ化することにより、選択した ORM でデータベースに対する実行を最適化できます。Therefore, the selected ORM can optimize the execution against the database by grouping several update actions within the same transaction, as opposed to many small and separate transaction executions.

リポジトリは必須ではないRepositories shouldn't be mandatory

カスタム リポジトリは、前述の理由により便利であり、eShopOnContainers の順序付けマイクロサービスで採用されているアプローチですが、Custom repositories are useful for the reasons cited earlier, and that is the approach for the ordering microservice in eShopOnContainers. DDD 設計の実装、または一般的な .NET 開発に不可欠なパターンではありません。However, it isn't an essential pattern to implement in a DDD design or even in general .NET development.

たとえば、このガイドに対するフィードバックを直接提供してくれた Jimmy Bogard は次のように述べています。For instance, Jimmy Bogard, when providing direct feedback for this guide, said the following:

おそらく、これは私の最大のフィードバックになるでしょう。This’ll probably be my biggest feedback. 私はリポジトリが好きではありません。その主な理由は、基本となる永続化メカニズムの重要な詳細が隠ぺいされていることです。I’m really not a fan of repositories, mainly because they hide the important details of the underlying persistence mechanism. 私がコマンドに MediatR を採用する理由もそこにあります。It’s why I go for MediatR for commands, too. 永続レイヤーの機能をフルに活用し、集約ルートにドメイン動作のすべてをプッシュすることができます。I can use the full power of the persistence layer, and push all that domain behavior into my aggregate roots. 通常は、リポジトリでのシミュレーションを行いたくないので、実際のデータで統合テストを実行する必要があります。I don’t usually want to mock my repositories – I still need to have that integration test with the real thing. CQRS を利用すれば、もうリポジトリは不要です。Going CQRS meant that we didn’t really have a need for repositories any more.

リポジトリは便利な場合がありますが、集約パターンやリッチ ドメイン モデルとは異なり、DDD 設計においてリポジトリは不可欠ではありません。Repositories might be useful, but they are not critical for your DDD design, in the way that the Aggregate pattern and rich domain model are. したがって、リポジトリ パターンは使用しても、しなくても、かまいません。Therefore, use the Repository pattern or not, as you see fit. いずれにしても、EF Core を使用するたびに、リポジトリ パターンを使用することになりますが、この場合、リポジトリはマイクロサービス全体または境界付けられたコンテキストをカバーします。Anyway, you’ll be using the repository pattern whenever you use EF Core although, in this case, the repository covers the whole microservice or bounded context.

その他の技術情報Additional resources

リポジトリ パターンRepository pattern

Unit of Work パターンUnit of Work pattern