팁 (조언)
이 콘텐츠는 .NET Docs 또는 오프라인으로 읽을 수 있는 다운로드 가능한 무료 PDF로 제공되는 컨테이너화된 .NET 애플리케이션용 .NET 마이크로 서비스 아키텍처인 eBook에서 발췌한 내용입니다.
솔루션 폴더에는 SeedWork 폴더가 포함되어 있습니다. 이 폴더에는 도메인 엔터티 및 값 개체의 기반으로 사용할 수 있는 사용자 지정 기본 클래스가 포함되어 있습니다. 각 도메인의 개체 클래스에 중복 코드가 없도록 이러한 기본 클래스를 사용합니다. 이러한 유형의 클래스에 대한 폴더는 Framework와 같은 것이 아니라 SeedWork라고 합니다. 폴더에는 실제로 프레임워크로 간주할 수 없는 재사용 가능한 클래스의 작은 하위 집합만 포함되어 있기 때문에 SeedWork 라고 합니다. 시드워크 는 마이클 페더스가 도입하고 마틴 파울러가 대중화한 용어이지만, Common, SharedKernel 또는 이와 유사한 폴더의 이름을 지정할 수도 있습니다.
그림 7-12는 정렬 마이크로 서비스에서 도메인 모델의 시드워크를 형성하는 클래스를 보여 줍니다. 여기에는 Entity, ValueObject, Enumeration와 같은 몇 가지 사용자 지정 기본 클래스와 몇 가지 인터페이스가 있습니다. 이러한 인터페이스(IRepository 및 IUnitOfWork)는 인프라 계층에 구현해야 하는 사항을 알려줍니다. 이러한 인터페이스는 애플리케이션 계층에서 종속성 주입을 통해도 사용됩니다.
기본 클래스 및 인터페이스(Entity.cs, Enumeration.cs, IAggregateRoot.cs, IRepository.cs, IUnitOfWork.cs 및 ValueObject.cs 포함하는 SeedWork 폴더의 자세한 내용입니다.
그림 7-12. 도메인 모델 "seedwork" 기본 클래스 및 인터페이스의 샘플 집합
이는 많은 개발자가 공식 프레임워크가 아닌 프로젝트 간에 공유하는 복사 및 붙여넣기 재사용 유형입니다. 모든 계층 또는 라이브러리에 시드워크를 사용할 수 있습니다. 그러나 클래스 및 인터페이스 집합이 충분히 커지면 단일 클래스 라이브러리를 만들 수 있습니다.
사용자 지정 엔터티 기본 클래스
다음 코드는 엔터티 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가 설명 한 대로 "분리된 인터페이스를 사용하여 한 패키지에서 인터페이스를 정의하지만 다른 패키지에서 구현합니다. 이렇게 하면 인터페이스에 대한 종속성이 필요한 클라이언트가 구현을 완전히 인식하지 못할 수 있습니다."
구분된 인터페이스 패턴을 따르면 애플리케이션 계층(이 경우 마이크로 서비스에 대한 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; }
}
추가 리소스
- 마틴 파울러. 구분된 인터페이스입니다.
https://www.martinfowler.com/eaaCatalog/separatedInterface.html
.NET