2016-12-21 3 views
3

저는 합리적으로 '순수한'DDD 응용 프로그램을 작성하고 있습니다. CQRS를 사용하지 않습니다. 지속성은 EF6을 사용하는 인프라 서비스입니다.Entity Framework 및 DDD를 사용할 때 새 객체를 인스턴스화하는 방법은 무엇입니까?

이제 유형 A의 새 항목을 만들고 다른 항목 (B)의 탐색 속성 모음에 추가해야하는 몇 가지 방법이 있다고 가정 해 보겠습니다. 이 방법은 도메인 어셈블리 어딘가에있을 수도 있고 응용 프로그램 서비스 어셈블리에있을 수도 있습니다.

작은 응용 프로그램을 작성하는 경우 DBSet.Create 메서드를 호출하여 프록시 객체 (지연로드 사용)에 대한 참조를 확보 한 다음 B의 탐색 속성에 추가 할 수 있습니다.

그러나 내 응용 프로그램과 도메인 어셈블리가 내 영속성을 위해 EF를 사용하고 있다는 것을 기쁜 마음으로 생각하면 게으른 로딩을 중단하지 않도록하려면 어떻게해야합니까? 방금 A의 생성자를 호출하면 프록시 객체가 없습니다. 내 응용 프로그램 서비스에서이 사실을 처리해야합니까 (모든 잘못 생각한 것), 아니면 생성자를 보호 한 다음 해당 도메인/응용 프로그램 서비스에 팩토리를 전달해야합니까?

편집 : 잘못하고 있습니까? 아마도 다음과 같이 내 문제를 줄이거 나 없앨 수 있습니다.

  • EF의 탐색 모음에 추가하고 저장 한 다음 다른 쪽 끝을 채우는 데 익숙합니다. 그러나, DDD에서, 생성시에, 나는 새로운 엔티티가 가지고있는 관계의 양쪽 끝을 채워야한다고 생각합니다. 이렇게하면 내 탐색 문제가 해결됩니다.
  • 프록시 개체가 여전히 필요합니까? 나는 대부분의 경우에, 아닙니다 짐작하고있다. 새로운 엔터티를 지속하는 트랜잭션이 발생할 때까지 DB에서 아무 것도 가져올 필요가 없습니다.
  • 만약 내가 Create (Insert) 트랜잭션 이상으로 DBContext의 수명을 유지하려고한다면, 내 영속 계층에 새로운 엔티티를 DBContext에 추가 할 필요가 있습니다.), 또는 DBContext의 DBSet.Create 메서드를 사용할 팩터 리에 대한 생성자 함수로 전달하십시오.
+2

왜 새 엔터티에 프록시 개체가 필요합니까? 그것은 곧 삽입 될 것입니다. 제 생각에는 응용 프로그램 서비스 나 도메인에있을 수 있습니다. 예를 들어, 새 오브젝트가 집계의 일부인 경우, 집계가 작성을 담당합니다. – jannagy02

+0

안녕하세요, 감사합니다. 컬렉션에서 null 참조 예외를 피하려면 프록시 객체가 필요합니다. 왜 그런지는 중요하지 않습니다.이 지속성 문제가 도메인에 누출되는 것을 막는 방법에 대한 자세한 내용입니다. 저는 대답이 합격 된 공장이어야한다고 생각합니다. 이는 응용 프로그램 서비스에서 사용하는 것입니다. – Chalky

+2

타입 A의 새로운 엔티티를 생성하고 그것을 다른 엔티티 (B)의 네비게이션 프로퍼티 콜렉션에 추가하기를 원한다면 그냥하라. 도메인은 여전히 ​​지속성이 유지되지 않습니다. 데이터베이스에 없으므로 지연로드가 필요하지 않으며 SaveChanges를 호출 할 때 EF가 삽입합니다. – jannagy02

답변

0

나는 당신의

합리적 '순수'DDD 응용 프로그램

으로 볼 수있는 가장 큰 문제는 지속성이 다른 모든 것들에 선행한다는 것이다. 파란 책과 그것의 패턴을 기억한다면, 집계를 만드는 팩토리처럼, 이것들을 갖는 이유는 유효한 상태로 도메인 객체를 만드는 것입니다. 그것은 끈기와 아무 관련이 없습니다.

리포지토리에 영속성 논리가 더 적합합니다. 도메인은 가정 논리 (도메인 로직)를 수행해야합니다. 지속성은 도메인 모델이 어떻게 든 계속 유지되도록주의해야합니다. EF를 선택하면 올바른 매핑을 수행하고 임피던스 불일치를 인식하십시오. 그러나 repository.Add(myNewAggregate)으로 전화하기 전에 EF 내부에 대해 생각하는 것은 거의 의미가 없습니다.

EF를 사용하는 일부 DDD 전술 패턴의 단순화되고 진부한 구현의 예에 관심이있는 경우 아래에서 하나를 찾습니다.

면책 조항 : 방금 작성 했으므로 실제 데이터베이스에서는 테스트하지 않았습니다. ID 처리 기능 없음, IoC/DI 구성 없음, 트랜잭션 없음. 그러나 당신이 아이디어를 얻길 바랍니다. EF는 NHibernate, Marten, RavenDb 또는 다른 어떤 코드가 될 수있는 몇 줄의 코드로 이러한 인터페이스를 구현하는 방법 일뿐입니다.

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Linq; 
using System.Linq.Expressions; 

namespace SomeStuff 
{ 
    public interface IAggregateRoot 
    { 
     int Id { get; } 
    } 

    public interface IRepository<T> where T : IAggregateRoot 
    { 
     IUnitOfWork UnitOfWork { get; } 
     void Add(T entity); 
     T Load(int id); 
    } 

    public interface IUnitOfWork 
    { 
     void Commit(); 
    } 

    public class EntityFrameworkUnitOfWork : IUnitOfWork 
    { 
     public EntityFrameworkUnitOfWork(DbContext context) 
     { 
      Context = context; 
     } 

     public DbContext Context { get; } 

     public void Commit() => Context.SaveChanges(); 
    } 

    public class EntityFrameworkRepository<T> : IRepository<T> where T : class, IAggregateRoot 
    { 
     private readonly DbSet<T> _set; 
     public IUnitOfWork UnitOfWork { get; } 

     public EntityFrameworkRepository(EntityFrameworkUnitOfWork unitOfWork) 
     { 
      UnitOfWork = unitOfWork; 
      _set = unitOfWork.Context.Set<T>(); 
     } 

     public void Add(T entity) => _set.Add(entity); 

     public T Load(int id) => _set.Find(id); 

     protected IQueryable<T> Query(Expression<Func<T, bool>> filter) => _set.Where(filter); 
    } 

    public class Employee : IAggregateRoot 
    { 
     public int Id { get; set; } 
     public string Name { get; private set; } 
     public int DepartmentId { get; private set; } 

     private Employee(string name) 
     { 
      Name = name; 
     } 

     public static Employee Create(string name) => 
      new Employee(name); 

     public void AssignToDepartment(int departmentId) => DepartmentId = departmentId; 
    } 

    public interface IEmployeeRepository : IRepository<Employee> 
    { 
     IEnumerable<Employee> AllEmployeesForDepartment(int departmentId); 
    } 

    public class EmployeeRepository : EntityFrameworkRepository<Employee>, IEmployeeRepository 
    { 
     public EmployeeRepository(EntityFrameworkUnitOfWork unitOfWork) : base(unitOfWork) 
     { 
     } 

     public IEnumerable<Employee> AllEmployeesForDepartment(int departmentId) => 
      Query(e => e.DepartmentId == departmentId); 
    } 

    public class EmployeeService 
    { 
     private readonly IEmployeeRepository _repository; 

     public EmployeeService(IEmployeeRepository repository) 
     { 
      _repository = repository; 
     } 

     public void RegisterEmployee(string name) 
     { 
      var employee = Employee.Create(name); 
      _repository.Add(employee); 
      _repository.UnitOfWork.Commit(); 
     } 

     public void AssignEmployeeToDepartment(int employeeId, int departmentId) 
     { 
      var employee = _repository.Load(employeeId); 
      if (employee == null) 
       throw new InvalidOperationException("Employee not found"); 

      employee.AssignToDepartment(departmentId); 
      _repository.UnitOfWork.Commit(); 
     } 
    } 
} 
+0

귀하의 답변을 주셔서 감사합니다,하지만 내 질문에 실제로 해결되지 않습니다. – Chalky

+0

글쎄, 도메인 기반 디자인에 대한 참조를 제거하면 원하는 방식으로 질문에 적용 할 수있는 답변을 얻을 수 있습니다. –

+0

음, 시간을내어 주셔서 감사합니다. – Chalky

0

이 시나리오에서는 네비게이션을 전혀 다룰 필요가 없다고 생각합니다.

항목 만들기를 처리하고 새 항목을 유지하는 지속성 하나의 집계 루트를 가져옵니다. said 본이 버논처럼 :

이제
public void planProductBacklogItem(String aProductId,String aSummary, String aCategory, String aBacklogItemType) { 

//aggregate root with enough info to create the item and check rules 
Product product = productRepository.productOfId(aProductId); 

//Create the item. Assign procutID to new backlogitem. Also check rules (max planed items? wrong itemType for this product?, etc...) 
BacklogItem plannedBacklogItem = product.planBacklogItem(aSummary,aBacklogItemType,aCategory); 

//just persist the new item. As it has a reference to productID everything goes OK. 
backlogItemRepository.add(plannedBacklogItem); 

} 

가, EF 버논과이를 구현하는 것은 domain object backed by a state object를 사용하여 선택합니다.