31

리포지토리 패턴을 사용하여 트랜잭션 방식으로 둘 이상의 엔터티를 저장하는 방법을 어떻게 캡슐화합니까? 예를 들어, 주문을 추가하고 해당 주문 작성을 기반으로 고객 상태를 업데이트하려는 경우, 주문이 성공적으로 완료된 경우에만 수행 할 수 있습니까? 이 예에서 주문은 고객 내부의 컬렉션이 아닙니다. 그들은 그들 자신의 실체입니다.리포지토리 패턴의 트랜잭션

이것은 단지 인위적인 사례 일 뿐이므로 주문이 고객 개체 내부 또는 동일한 제한된 컨텍스트 내에서 이루어져야하는지 안해야하는지에 대해서는별로 신경 쓰지 않습니다. 어떤 기본 기술이 사용될 것인지 (nHibernate, EF, ADO.Net, Linq 등) 실제로 신경 쓰지는 않습니다.이 호출 된 코드가 모든 것이나 전혀없는 동작의이 고의적 인 인위적인 예제에서 어떻게 보이는지를보고 싶습니다.

답변

7

일부 유형의 트랜잭션 범위/컨텍스트 시스템을 사용할 것입니다. 그래서 대략 닷넷 & C#을 기반으로 다음과 같은 코드가있을 수 있습니다.

public class OrderService 
{ 

public void CreateNewOrder(Order order, Customer customer) 
{ 
    //Set up our transactional boundary. 
    using (TransactionScope ts=new TransactionScope()) 
    { 
    IOrderRepository orderRepos=GetOrderRespository(); 
    orderRepos.SaveNew(order); 
    customer.Status=CustomerStatus.OrderPlaced; 

    ICustomerRepository customerRepository=GetCustomerRepository(); 
    customerRepository.Save(customer) 
    ts.Commit(); 
    } 
} 
} 

TransactionScope는 둥지를 틀 수 있으므로 응용 프로그램이 TransactionScope를 만들 때 여러 서비스를 교차 시켰다고 가정 해 봅시다. 현재 .net에서 TransactionScope를 사용하면 DTC로 에스컬레이션 할 위험이 있지만 나중에 해결 될 것입니다.

기본적으로 DB 연결을 관리하고 로컬 SQL 트랜잭션을 사용하는 자체 TransactionScope 클래스를 만들었습니다.

+0

나는 이것이 DDD의 정신에서 해결책이라고 생각하지 않는다. 기본적으로 도메인 모델 작업을 수행하는 트랜잭션 스크립트를 만들었습니다. 서비스는 예를 들어 고객 상태를 변경하면 안됩니다. –

+1

코드의 일부가이 비즈니스 규칙을 처리해야합니다.이 수준 또는 상위 수준에서 트랜잭션을 처리하기 위해 로컬 트랜잭션 또는 분산 트랜잭션을 허용하는 단일 TransactionScope 내에서 변경을 수행하고 있었습니까?주문이있을 때마다 고객을 업데이트하라는 비즈니스 규칙이 있으면 모든 주문이 여기에 적용되므로이를 처리하는 것이 좋습니다. – JoshBerke

3

작업 단위 패턴 구현을 검토하고 싶습니다. 거기에 NHibernate에 대한 구현이 있습니다. 하나는 Rhino Commons 프로젝트에 있으며, Machine.UoW도 있습니다. 당신은 당신이 안으로 실행하고자하는 방법을 선택하고 XML 파일에서

public class CustomerService : ICustomerService 
{ 
    private readonly ICustomerRepository _customerRepository; 
    private readonly IOrderRepository _orderRepository; 

    public CustomerService(
     ICustomerRepository customerRepository, 
     IOrderRepository orderRepository) 
    { 
     _customerRepository = customerRepository; 
     _orderRepository = orderRepository; 
    } 

    public int CreateOrder(Order o, Customer c) 
    { 
     // Do something with _customerRepository and _orderRepository 
    } 
} 

: 정상적으로 저장소 클래스를 작성하고 사용자 정의 XML 파일에 트랜잭션을 구성 할 수 있습니다 Spring.NET AOP + 자 NHibernate를 사용

5

트랜잭션 :

<object id="TxProxyConfigurationTemplate" 
      abstract="true" 
      type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data"> 

    <property name="PlatformTransactionManager" ref="HibernateTransactionManager"/> 

    <property name="TransactionAttributes"> 
     <name-values> 
     <add key="Create*" value="PROPAGATION_REQUIRED"/> 
     </name-values> 
    </property> 
    </object> 

    <object id="customerService" parent="TxProxyConfigurationTemplate"> 
    <property name="Target"> 
     <object type="MyNamespace.CustomerService, HibernateTest"> 
      <constructor-arg name="customerRepository" ref="customerRepository" /> 
      <constructor-arg name="orderRepository" ref="orderRepository" /> 
     </object> 
    </property> 

    </object> 

그리고 당신의 코드는이 같은 CustomerService를 클래스의 인스턴스를 구하십시오

ICustomerService customerService = (ICustomerService)ContextRegistry 
    .GetContent() 
    .GetObject("customerService"); 

Spring.NET은 CreateOrder 메서드를 호출 할 때 트랜잭션을 적용 할 CustomerService 클래스의 프록시를 반환합니다. 이렇게하면 서비스 클래스 내에 거래 전용 코드가 없습니다. AOP가 처리합니다. 자세한 내용은 Spring.NET의 설명서를 참조하십시오.

2

은 어떻게 저장소 패턴을 사용하여 트랜잭션 방식으로 하나의 엔티티보다 더 의 구원을 캡슐화합니까? 예를 들어 주문을 추가하고 고객 주문을 업데이트하려면 을 입력하고 주문을 성공적으로 완료 한 경우에만 주문을 작성 하시겠습니까? 이 예의 경우 주문은 이며 고객 내부의 컬렉션이 아닙니다. 그들은 그들 자신의 실체입니다.

저장소의 책임이 아니라 일반적으로 상위 수준에서 수행되는 작업입니다.특정 기술에 관심이 없다고 말했지만 솔루션을 묶는 것이 가치 있다고 생각합니다. 예를 들어 NHibernate를 웹 응용 프로그램과 함께 사용할 때는 session-per request을 사용하는 것이 좋습니다. 더 높은 수준에서 트랜잭션을 관리 할 수있는 경우

그럼 내 두 가지 옵션은 다음과 같습니다

  1. 선취 체크 - 예를 들어 서비스에 당신이 요청하여 진행하려면 결정 동작을 배위 주문/고객 중 하나라도 업데이트하지 않는다고 말하면 주문/고객.
  2. 롤백 - 고객/주문을 계속 업데이트하고 데이터베이스 트랜잭션을 롤백하는 데 일부 실패하는 경우.

두 번째 옵션을 선택하면 메모리 내 개체에 어떤 문제가 발생하며 고객은 일관성없는 상태로 남을 수 있습니다. 그 문제가 있다면, 그리고 그 시나리오는 그 개체가 그 요청에 대해서만로드되지 않은 시나리오에서 일하면서, 가능한 경우라면 선행 검사를 고려할 것입니다. 대안보다 훨씬 쉽기 때문에 (in - 메모리 변경 또는 객체 재로드).

+1

왜 그것이 저장소의 책임이 아닙니까? 도메인 모델을 벗어나 데이터베이스 작업을 추상화하려는 아이디어가 전부 아닌가? 나에게 저장소는 트랜잭션 지원을 배치하는 가장 좋은 장소입니다. –

12

오늘 아침에 컴퓨터를 부팅하면 작업중인 프로젝트의 정확한 문제가 발생했습니다. 나는 다음과 같은 디자인으로 이어지는 몇 가지 아이디어를 가지고 있으며, 코멘트는 그 이상이 될 것입니다. 불행하게도 Josh가 제안한 디자인은 불가능합니다. 원격 SQL 서버를 사용해야하고 의존하는 Distribute Transaction Coordinator 서비스를 사용할 수 없기 때문입니다.

내 솔루션은 기존 코드에 대한 몇 가지 간단한 변경 사항을 기반으로합니다.

첫째, 내 모든 저장소는 간단한 마커 인터페이스 구현이 있습니다

/// <summary> 
/// Provides methods to enable transaction support. 
/// </summary> 
public interface IHasTransactions : IRepository 
{ 
    /// <summary> 
    /// Initiates a transaction scope. 
    /// </summary> 
    void BeginTransaction(); 

    /// <summary> 
    /// Executes the transaction. 
    /// </summary> 
    void CommitTransaction(); 
} 

아이디어는에서이다 : 두 번째

/// <summary> 
/// A base interface for all repositories to implement. 
/// </summary> 
public interface IRepository 
{ } 

을, 내 모든 거래 활성화 저장소는 다음과 같은 인터페이스를 구현하자 내 모든 저장소이 인터페이스를 구현하고 실제 공급자에 따라 트랜잭션을 직접 도입하는 코드를 추가합니다 (가짜 저장소의 경우 커밋시 실행되는 대리자 목록을 만들었습니다). LINQ는 SQL하는 것이 같은 구현을 쉽게 만들 수있을 것입니다 : 물론

#region IHasTransactions Members 

public void BeginTransaction() 
{ 
    _db.Transaction = _db.Connection.BeginTransaction(); 
} 

public void CommitTransaction() 
{ 
    _db.Transaction.Commit(); 
} 

#endregion 

이를 새 저장소 클래스가 각 스레드 생성되어 있어야하지만,이 내 프로젝트에 대한 합리적이다.

저장소가 IHasTransactions 인 경우 저장소를 사용하는 각 방법은 BeginTransaction()EndTransaction()을 호출해야합니다. 이 전화를 더 쉽게 사용하기 위해 다음 확장명을 제안했습니다.

/// <summary> 
/// Extensions for spawning and subsequently executing a transaction. 
/// </summary> 
public static class TransactionExtensions 
{ 
    /// <summary> 
    /// Begins a transaction if the repository implements <see cref="IHasTransactions"/>. 
    /// </summary> 
    /// <param name="repository"></param> 
    public static void BeginTransaction(this IRepository repository) 
    { 
     var transactionSupport = repository as IHasTransactions; 
     if (transactionSupport != null) 
     { 
      transactionSupport.BeginTransaction(); 
     } 
    } 

    public static void CommitTransaction(this IRepository repository) 
    { 
     var transactionSupport = repository as IHasTransactions; 
     if (transactionSupport != null) 
     { 
      transactionSupport.CommitTransaction(); 
     } 
    } 
} 

댓글 감사합니다.

+0

또한 변형을 사용하여 각 트랜잭션에 대한 저장소 인스턴스를 만들고 using 문 안에 넣은 다음 Dispose()가 트랜잭션을 커밋하도록 할 수 있습니다. 이것은 호출자 메소드의 트랜잭션에 대해 알아야 할 추상화입니다. –

+0

꽤 달콤합니다. –

+0

매우 흥미로운 솔루션 ... –