Как да получите достъп до хранилище от домейн слой

Мисля, че може да имам известно объркване относно разделянето на слоя домейн/услуга.

В моето приложение кодът на домейна трябва да генерира уникален идентификатор за цялата система с бизнес значение. Това означава, че трябва да има достъп до хранилището с изключителен достъп. Бих могъл да направя това директно в кода на домейна, но мисля, че това е грешно поради две причини:

  • Кодът на домейна не трябва да има достъп до хранилището (мисля...)
  • Получаването на изключително заключване на хранилището означава, че ще блокирам едновременни операции на други обекти на домейн, които също се нуждаят от този достъп, тъй като заключването ще бъде освободено само когато цялата сесия е ангажирана. Чувства се излишно да се използва оптимистична паралелност тук: докато генерирането на ID е бързо, бизнес операциите са сравнително дълги, което означава голяма вероятност от две едновременни бизнес операции.

Като пример разгледайте следното:

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, и да предадете новия Id към метода Order.Process като параметър.

Ако ви е добре да губите идентификатори, независима услуга, която генерира нов идентификатор, вероятно е най-доброто решение във вашия случай. След това добавяте тази услуга като зависимост към други услуги, които получават идентификаторите и ги предават на обекти на домейн. По този начин обектите на вашия домейн остават ясни от всяко външно взаимодействие.

person Iulian Margarintescu    schedule 19.05.2011
comment
@Iulian Да накарате OrderService да генерира ID за съжаление не е опция, тъй като в моя действителен сценарий Order решава кога има нужда от нови ID (може да има нужда от няколко или изобщо от нито един). Какво е вашето мнение относно предаването на 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