31

Как я могу инкапсулировать сохранение более чем одного объекта транзакционным способом с использованием шаблона репозитория? Например, что, если я хотел бы добавить заказ и обновить статус клиента на основе этого создания заказа, но только сделать это, если заказ успешно завершен? Имейте в виду, что для этого примера заказы не являются коллекцией внутри клиента. Это их собственная сущность.Сделки в шаблоне хранилища

Это всего лишь надуманный пример, поэтому мне все равно, должны ли заказы быть или не быть внутри объекта клиента или даже в том же ограниченном контексте. Мне все равно, какие базовые технологии будут использоваться (nHibernate, EF, ADO.Net, Linq и т. Д.). Я просто хочу посмотреть, как может выглядеть код вызова в этом, по общему признанию, надуманном примере операции «все или ничего».

ответ

7

Я бы посмотрел на использование какой-либо системы транзакций/контекста. Таким образом, у вас может быть следующий код, который примерно основан на .Net & 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, но это будет разрешено в будущем.

Мы создали собственный класс TransactionScope, который в основном управлял нашими соединениями с БД и использовал локальные транзакции SQL.

+0

Я не думаю, что это решение в духе DDD. В основном вы создали сценарий транзакции, который выполняет работу с моделью домена. Например, служба не должна изменять статус клиента. –

+1

Что-то в коде должно обрабатывать это бизнес-правило, будь то на этом уровне или на более высоком уровне, когда делались изменения в пределах одного TransactionScope, позволяющего обрабатывать транзакции локальными транзакциями или распределенными транзакциями.Если в бизнес-правиле говорится о том, что клиент обновляется каждый раз, когда размещается заказ, это хорошее место для обработки, поскольку все заказы проходят здесь. – JoshBerke

3

Вы хотите посмотреть на реализацию единицы рабочего шаблона. Существуют версии для NHibernate. Один из них находится в проекте Rhino Commons, есть также Machine.UoW.

5

Использование Spring.NET АОП + NHibernate вы можете написать свой репозиторий класса в обычном режиме и настраивать операции в пользовательском файле 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 Вы выбираете, какие методы вы хотели бы быть выполнена внутри сделка:

<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 вернет вам прокси класса CustomerService, который будет применять транзакцию при вызове метода CreateOrder. Таким образом, внутри ваших классов обслуживания нет кода транзакции. АОП заботится об этом. Для получения дополнительной информации вы можете ознакомиться с документацией Spring.NET.

2

Как герметизировать сохранение более чем одного объекта в транзакционной манере, используя хранилище шаблон? Например, что , если бы я хотел добавить заказ и обновить статус клиента на основании этого создание заказа, но только сделайте это, если заказов успешно завершен? Держите в , что в этом примере заказы не являются коллекцией внутри клиента. Это их собственная сущность.

Его не несет ответственности за хранилище, его обычно что-то делается на более высоком уровне.Хотя вы заявили, что не интересуетесь конкретными технологиями, я думаю, что стоит упомянуть о решениях, например, при использовании NHibernate с веб-приложением, которое вы, вероятно, рассмотрите, используя session-per request.

Так что, если вы можете управлять транзакциями на более высоком уровне, чем мои два варианта будут:

  1. Искренние проверка - Например, в службе координации поведения решить, если вы хотите продолжить, задавая Заказ/Заказчик, если они говорят, что они этого не делают, даже не пытаются обновить ни один из них.
  2. Откат - Просто приступайте к обновлению Заказчика/Заказ, и если что-то не удается частично откат транзакции базы данных.

Если вы переходите к второму варианту, тогда возникает вопрос, что происходит с объектами в памяти, ваш Клиент может быть оставлен в противоречивом состоянии. Если это имеет значение, и я работаю в сценариях, где это не так, как объект был загружен только для этого запроса, тогда я бы рассматривал предварительную проверку, если это возможно, потому что это намного проще, чем альтернативы (откат в -memory изменяет или перезагружает объекты).

+1

Почему это не несет ответственность за репозиторий? Разве не вся идея абстрагировать операции с базами данных от модели домена? Для меня репозиторий - лучшее место для размещения этой транзакционной поддержки. –

12

Загрузив компьютер сегодня утром, я столкнулся с точной проблемой для проекта, над которым я работаю. У меня были некоторые идеи, которые приводят к следующему дизайну - и комментарии были бы более чем удивительными. К сожалению, дизайн, предложенный Джошем, невозможен, поскольку я должен работать с удаленным SQL-сервером и не могу включить службу Distribute Transaction Coordinator, на которую он опирается.

Мое решение основано на нескольких, но простых изменениях в моем существующем коде.

Во-первых, у меня есть все мои хранилищами реализовать простой интерфейс маркера:

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

Во-вторых, я позволяю все мои сделки позволили хранилищами реализовать следующий интерфейс:

/// <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(); 
} 

Идея заключается в том, что в все мои репозитории я реализую этот интерфейс и добавляю код, который вводит транзакцию напрямую в зависимости от фактического провайдера (для поддельных репозиториев я составил список делегатов, которые выполняются при фиксации). Для LINQ к SQL было бы легко сделать реализации, такие как:

#region IHasTransactions Members 

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

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

#endregion 

Это, конечно, требует, чтобы новый репозиторий класса создается для каждого потока, но это разумно для моего проекта.

Каждый метод, использующий репозиторий, должен вызывать BeginTransaction() и EndTransaction(), если репозиторий реализует IHasTransactions. Чтобы сделать этот вызов еще проще, я придумал следующие расширения:

/// <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

Очень интересное решение ... –