ヒント
このコンテンツは、.NET Docs で入手できる、またはオフラインで読み取ることができる無料のダウンロード可能な PDF として入手できる、コンテナー化された .NET アプリケーションの電子ブックである .NET マイクロサービス アーキテクチャからの抜粋です。
ソリューション フォルダーには 、SeedWork フォルダーが含まれています。 このフォルダーには、ドメイン エンティティと値オブジェクトのベースとして使用できるカスタム 基本クラスが含まれています。 各ドメインのオブジェクト クラスに冗長なコードが含まれていないように、これらの基底クラスを使用します。 これらの種類のクラスのフォルダーは、Framework のようなものではなく、SeedWork と呼ばれます。 このフォルダーには、フレームワークと見なすことができない再利用可能なクラスのごく一部のみが含まれているため、 SeedWork と呼ばれます。 Seedwork は Michael Feathers によって導入され、 Martin Fowler によって普及した用語ですが、Common、SharedKernel などのフォルダーに名前を付けることもできます。
図 7-12 は、注文マイクロサービスのドメイン モデルのシードワークを形成するクラスを示しています。
Entity、ValueObject、Enumerationなどのいくつかのカスタム 基底クラスに加えて、いくつかのインターフェイスがあります。 これらのインターフェイス (IRepository と IUnitOfWork) は、実装する必要がある内容についてインフラストラクチャ レイヤーに通知します。 これらのインターフェイスは、アプリケーション レイヤーからの依存関係の挿入でも使用されます。
基本クラスとインターフェイスを含む SeedWork フォルダーの詳細な内容:Entity.cs、Enumeration.cs、IAggregateRoot.cs、IRepository.cs、IUnitOfWork.cs、およびValueObject.cs。
図 7-12 ドメイン モデルの "シードワーク" 基底クラスとインターフェイスのサンプル セット
これは、多くの開発者が正式なフレームワークではなく、プロジェクト間で共有するコピーと貼り付けの再利用の種類です。 任意のレイヤーまたはライブラリに seedworks を含めることができます。 ただし、クラスとインターフェイスのセットが十分に大きくなる場合は、1 つのクラス ライブラリを作成できます。
カスタム エンティティの基本クラス
次のコードは、エンティティ ID、 等値演算子、エンティティごとのドメイン イベント リストなど、任意のドメイン エンティティで同じように使用できるコードを配置できる Entity 基本クラスの例です。
// COMPATIBLE WITH ENTITY FRAMEWORK CORE (1.1 and later)
public abstract class Entity
{
int? _requestedHashCode;
int _Id;
private List<INotification> _domainEvents;
public virtual int Id
{
get
{
return _Id;
}
protected set
{
_Id = value;
}
}
public List<INotification> DomainEvents => _domainEvents;
public void AddDomainEvent(INotification eventItem)
{
_domainEvents = _domainEvents ?? new List<INotification>();
_domainEvents.Add(eventItem);
}
public void RemoveDomainEvent(INotification eventItem)
{
if (_domainEvents is null) return;
_domainEvents.Remove(eventItem);
}
public bool IsTransient()
{
return this.Id == default(Int32);
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is Entity))
return false;
if (Object.ReferenceEquals(this, obj))
return true;
if (this.GetType() != obj.GetType())
return false;
Entity item = (Entity)obj;
if (item.IsTransient() || this.IsTransient())
return false;
else
return item.Id == this.Id;
}
public override int GetHashCode()
{
if (!IsTransient())
{
if (!_requestedHashCode.HasValue)
_requestedHashCode = this.Id.GetHashCode() ^ 31;
// XOR for random distribution. See:
// https://learn.microsoft.com/archive/blogs/ericlippert/guidelines-and-rules-for-gethashcode
return _requestedHashCode.Value;
}
else
return base.GetHashCode();
}
public static bool operator ==(Entity left, Entity right)
{
if (Object.Equals(left, null))
return (Object.Equals(right, null));
else
return left.Equals(right);
}
public static bool operator !=(Entity left, Entity right)
{
return !(left == right);
}
}
エンティティごとにドメイン イベント リストを使用する前のコードについては、ドメイン イベントに焦点を当てる際の次のセクションで説明します。
ドメイン モデル レイヤーのリポジトリ コントラクト (インターフェイス)
リポジトリ コントラクトは、各集計に使用するリポジトリのコントラクト要件を表す単なる .NET インターフェイスです。
EF Core コードまたはその他のインフラストラクチャの依存関係とコード (Linq、SQL など) を含むリポジトリ自体は、ドメイン モデル内に実装しないでください。リポジトリは、ドメイン モデルで定義したインターフェイスのみを実装する必要があります。
このプラクティスに関連するパターン (ドメイン モデル レイヤーにリポジトリ インターフェイスを配置する) は、分離インターフェイス パターンです。 Martin Fowler によって 説明 されているように、「分離インターフェイスを使用して、1 つのパッケージでインターフェイスを定義し、別のパッケージに実装します。 このように、インターフェイスへの依存関係を必要とするクライアントは、実装を完全に認識できません。
分離インターフェイス パターンに従うと、アプリケーション レイヤー (この場合はマイクロサービスの Web API プロジェクト) は、ドメイン モデルで定義されている要件に依存できますが、インフラストラクチャ/永続化レイヤーへの直接の依存関係はありません。 さらに、依存関係の挿入を使用して、リポジトリを使用してインフラストラクチャ/永続化レイヤーに実装される実装を分離できます。
たとえば、IOrderRepository インターフェイスを使用した次の例では、OrderRepository クラスがインフラストラクチャ レイヤーで実装する必要がある操作を定義します。 アプリケーションの現在の実装では、クエリは簡略化された CQRS アプローチに従って分割されるため、コードはデータベースに注文を追加または更新するだけで済みます。
// Defined at IOrderRepository.cs
public interface IOrderRepository : IRepository<Order>
{
Order Add(Order order);
void Update(Order order);
Task<Order> GetAsync(int orderId);
}
// Defined at IRepository.cs (Part of the Domain Seedwork)
public interface IRepository<T> where T : IAggregateRoot
{
IUnitOfWork UnitOfWork { get; }
}
その他のリソース
- Martin Fowler。 分離インターフェイス。
https://www.martinfowler.com/eaaCatalog/separatedInterface.html
.NET