Как получить доступ к хранилищу с уровня домена

Я думаю, что у меня может быть некоторая путаница в отношении разделения уровня домена/сервиса.

В моем приложении код домена должен генерировать общесистемный уникальный идентификатор с бизнес-значением. Это означает, что он должен получить доступ к хранилищу с монопольным доступом. Я мог бы сделать это прямо в коде домена, но считаю это неправильным по двум причинам:

  • Код домена не должен обращаться к хранилищу (я думаю...)
  • Получение эксклюзивной блокировки хранилища означает, что я буду блокировать одновременные операции с другими объектами домена, которым также нужен этот доступ, поскольку блокировка будет снята только после фиксации всего сеанса. Здесь кажется расточительным использовать оптимистичный параллелизм: хотя генерация идентификатора происходит быстро, бизнес-операции относительно длительны, что означает высокую вероятность двух параллельных бизнес-операций.

В качестве примера рассмотрим следующее:

class UnitOfWork
{
    public OrderRepository TheOrderRepository { get; private set; }
    public void Commit() { /* ... */ }
}

class UnitOfWorkFactory
{
     UnitOfWork CreateNewUnitOfWork() 
     {
         var sess = CreateNewSession();
         var trans = CreateNewTransaction(session);
         return new UnitOfWork 
             { 
                 TheOrderRepository = new OrderRepository(sess, trans);
             }
     }
}

class OrderService // application service layer
{
    public void ProcessOrder(ID id, Details details)
    {
        using (uow = UnitOfWorkFactory.CreateNewUnitOfWork())
        {
            Order order = TheOrderRepository.Load(id);
            order.Process(details);
            uow.TheOrderRepository.Update(order);
            uow.Commit();
        }
    }
}

class Order // domain layer
{
    public void Process(Details details)
    {
        // need to get a unique, business-related identifier here.
        // Where do I get it from?
    }
}

Есть два варианта, о которых я мог подумать:

// Option 1 - get BusinessIdRepository from Service layer

class Order // domain layer
{
    public void Process(Details details)
    {
        // bad, because other Orders will block in Process() until my Process()
        // is done.
        // also, I access the storage in my domain layer, which is a no-no (?)
        var id = m_businessIdRepository.GetUniqueId(details);
    }
}

А также:

// Option 2 - introduce a service. For this example, suppose the business ID is 
//            just an increasing counter

class BusinessIdBrokerService
{
    int GetBusinessId(Details details) 
    {
        int latestId;
        using (uow = UnitOfWorkFactory.CreateNewUnitOfWork())
        {
            latestId = TheIdRepository.GetLatest();  // takes lock
            latestId ++;
            TheIdRepository.SetLatestId(latestId);
            uow.Commit();  // lock is released
        }

        return latestId;
    }
}

class Order // domain layer
{
    public void Process(Details details)
    {
        // domain layer accessing the service layer. Is this bad?
        var id = m_businessIdBroker.GetBusinessId(details);
    }
}

Итак, вариант (1) имеет очевидные недостатки, но вариант (2) имеет уровень домена, обращающийся к сервисному уровню. На всех диаграммах, которые я видел, это большое нет-нет.

Следуя терминологии Джимми Богарда (http://lostechies.com/jimmybogard/2008/08/21/services-in-domain-driven-design/), похоже, мне нужна доменная служба (в отличие от службы приложений), но этот доменный сервис будет обращаться к хранилищу (не только через репозиторий: он создаст независимую сессию + транзакцию.)

Должен заметить, что вариант (2) имеет тот недостаток, что генерацию ID нельзя откатить в случае проблемы в Order.Process, потому что она уже зафиксирована. У меня нет проблем с этим в моем сценарии. Я не против тратить удостоверения личности.

Если это имеет значение, я использую NHibernate в качестве ORM.

Какой подход вы бы порекомендовали?


person telewin    schedule 19.05.2011    source источник


Ответы (1)


Чтобы удалить зависимость от Order к IdBroker, вы можете внедрить IdBrokerService, внедренный в OrderService, и передать новый идентификатор методу Order.Process в качестве параметра.

Если вы согласны с потерей идентификатора, то независимый сервис, который генерирует новый идентификатор, вероятно, является лучшим решением в вашем случае. Затем вы добавляете эту службу в качестве зависимости к другим службам, которые получают идентификаторы и передают их объектам домена. Таким образом, ваши объекты домена остаются свободными от любого внешнего взаимодействия.

person Iulian Margarintescu    schedule 19.05.2011
comment
@Iulian К сожалению, создание ID службой OrderService не вариант, поскольку в моем реальном сценарии Орден решает, когда ему нужны новые идентификаторы (может потребоваться несколько или вообще ни одного). Как вы относитесь к передаче IdBroker непосредственно объектам домена? - person telewin; 19.05.2011
comment
В этом случае вы можете передать IdBroker объекту домена. Обычно это называется DoubleDispatch. Просто убедитесь, что вы передаете его как интерфейс с минимально необходимыми методами для уменьшения зависимости от других компонентов (в вашем случае, вероятно, просто метод GetBusinessId()) - person Iulian Margarintescu; 19.05.2011
comment
@Iulian Да, это то, что я собираюсь сделать. Я столкнулся с еще одной забавной проблемой: сервис должен открыть отдельную сессию + транзакцию, а мой блок работы не позволяет (потому что он уже находится в транзакции). Это действительно очень раздражает. Любые мысли по решению этой проблемы (кроме использования потока пула потоков...)? - person telewin; 19.05.2011
comment
Вы можете создать новый TransactionScope и передать TransactionScopeOption.RequiresNew в качестве аргумента. Он должен создать новую транзакцию для этой области. - person Iulian Margarintescu; 19.05.2011
comment
@Iulian Но я думаю, что это привязывает меня к SQL Server, не так ли? Я узнал, что у Rhino commons есть кое-что, что выглядит хорошо. Я рассмотрю возможность его использования. Спасибо за вашу помощь! - person telewin; 19.05.2011
comment
TransactionScope не имеет отношения к SQL Server. Большинство (все?) поставщиков данных для .net поддерживают TransactionScope. Я не удивлюсь, если Rhino commons использует TransactionScope под капотом. - person Iulian Margarintescu; 19.05.2011