UnitOfWork (NHibernate), только один активный UoW / сеанс за раз? (нужен совет) - PullRequest
3 голосов
/ 20 марта 2011

Я использую NHibernate, DI / IoC и шаблон единиц работы.

Большинство примеров UoW, которые я видел, удостоверяются, что может быть только один активный UoW / сеанс одновременно, например этот и этот .

К сожалению, я пока не совсем понимаю, как мне обращаться с двумя службами, обе из которых используют UoW, но одна вызывает другую.
Возьмите этот пример:

Регистратор, использующий UoW:

public class LoggerService : ILoggerService
{
    private ILoggerRepository repo;

    public LoggerService(ILoggerRepository Repo)
    {
        this.repo = Repo;
    }

    public void WriteLog(string message)
    {
        using (UnitOfWork.Start())
        {
            // write log
        }
    }
}

... и другая служба, которая также использует UoW И вызывает регистратор:

public class PlaceOrderService : IPlaceOrderService
{
    private IOrderRepository repo;
    private ILoggerService service;

    public PlaceOrderService(IOrderRepository Repo, ILoggerService Service)
    {
        this.repo = Repo;
        this.service = Service;
    }

    public int PlaceOrder(int orderNumber)
    {
        using (UnitOfWork.Start())
        {           
            // do stuff

            this.service.WriteLog("Order placed!");  // will throw exception!!

            // do more stuff
        }
    }
}

Если моя реализация UoW удостоверится, что одновременно существует только одна активная UoW (и выдает исключение, если вы попытаетесь запустить другую, как в обоих связанных примерах), мой код вылетит в строке this.service.WriteLog в методе PlaceOrder:
Уже будет активный UoW, созданный методом PlaceOrder, и метод WriteLog попытается открыть второй, поэтому реализация UoW выдает исключение из-за этого.

Итак, что я могу с этим поделать?
Я выдвинул две идеи, но обе они выглядят как-то «хакерскими» для меня.

  1. Не запускайте новое UoW в LoggerService, вместо этого предполагайте, что в вызывающем коде уже есть активный.
    Вот чем я сейчас занимаюсь. Я просто удалил материал using (UnitOfWork.Start()) из LoggerService и убедился, что вы не можете напрямую вызывать LoggerService, только из других служб.
    Это означает, что приведенный выше код будет работать, но LoggerService будет аварийно завершать работу, если вызывающий код не запускает UoW, потому что LoggerService предполагает, что он уже существует.

  2. Оставьте пример кода как есть, но измените реализацию UoW.Start () следующим образом:
    а) если нет активного UoW, начните новый
    б) если уже является активным UoW, верните это
    Это позволило бы мне вызывать LoggerService напрямую И из других служб, независимо от того, есть ли уже UoW или нет.
    Но я никогда не видел ничего подобного в сети.

(И в этом примере есть только две службы. Это может стать намного сложнее, просто подумайте о классе PlaceSpecialOrderService , который делает некоторые специальные вещи, а затем вызывает PlaceOrderService.PlaceOrder() ...)

Есть советы?
Заранее спасибо!


EDIT:

Спасибо за ответы.

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

В любом случае, мне все еще нужно найти способ заставить вложенные вызовы службы работать.
Вообразите какой-нибудь другой пример вместо регистрации, такой как пример PlaceSpecialOrderService, который я упоминал выше.

Ответчикам, предлагающим запустить UoW где-то в инфраструктуре, а не непосредственно в службах:
С одной стороны, это тоже имеет смысл, но с другой стороны это, очевидно, означало бы, что я не могу выполнить две разные транзакции в одном вызове службы.
Мне придется подумать об этом, потому что я совершенно уверен, что мне это где-то понадобится (например: сохранить заказ в одной транзакции, затем сделать больше вещей во второй транзакции, и даже если это не удастся, заказ не не откатывайся).

Вы делали это так (по одному UoW на сервисный вызов) в своих приложениях?
Разве вам не нужна была возможность запустить второй UoW в том же сервисном звонке?

Ответы [ 5 ]

3 голосов
/ 20 марта 2011

Я думаю, что вы нашли совершенно особый сценарий, когда вам никогда не следует использовать один и тот же сеанс для бизнес-службы и службы ведения журнала.UnitOfWork - это «бизнес-транзакция», но регистрация, очевидно, не является частью транзакции.Если ваша бизнес-логика выдает исключение, она откатит ваши логи !!!Для ведения журнала используйте отдельный сеанс (с отдельной строкой соединения, которая ограничивает участие в текущей транзакции).

2 голосов
/ 21 марта 2011

Лично я не думаю, что написание статуса заказа является проблемой регистрации. Для меня это бизнес, поэтому я бы реорганизовал вашу службу заказов примерно так:

public int PlaceOrder(int orderNumber)
    {
        using (UnitOfWork.Start())
        {           
            repository.SaveOrder(order)

            repository.SaveOrderStatus(order,"Order placed")

        }
    }

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

2 голосов
/ 20 марта 2011

Конструкция и управление временем жизни UOW не должны быть предметом заботы службы, реализующей вашу бизнес-логику.Вместо этого вы должны настроить свою инфраструктуру для управления UOW.В зависимости от вашего приложения у вас может быть UOW для запроса http или для вызова операции WCF, или MessageModule (подумайте NserviceBus).

Большинство контейнеров DI уже имеют некоторую поддержку для ассоциирования времени жизни экземпляра с приведенным вышеконтексты.

Также касается ведения журналов - в большинстве случаев это проблема инфраструктуры, и наличие службы ведения журналов рядом со службой обработки заказов - это запах.Позвольте log4net или nlog или как вам угодно делать то, для чего они созданы.

1 голос
/ 02 апреля 2011

Я думаю, что нашел решение сам.

На самом деле, это было одно из предложенных мной решений в моем вопросе:

Оставьте пример кода, как есть, но изменитереализация UoW.Start (), например, так:
a) если нет активного UoW, начните новый
b) если уже есть активный UoW, верните этот
Это позволит мнеВызовите LoggerService напрямую И из других служб, независимо от того, есть ли уже UoW или нет.
Но я никогда не видел ничего подобного ни в одном примере в сети.

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

НоЯ действительно нашел реализацию этого - я прочитал полный пост, но я просто перечитал соответствующую часть: -)
Это было в первой ссылке, которую я разместил в своем первоначальном вопросе .
UВ нашей реализации есть поле bool "isRootUnitOfWork".
Конструктор в основном делает это (упрощенно):

if (HasActiveSession)
{
    isRootUnitOfWork = false;
    session = GetActiveSession();
}
else
{
    isRootUnitOfWork = true;
    session = CreateSession();
}

Я думаю, что это наиболее гибкое решение.Я могу позвонить одному из моих сервисов или сделать так, чтобы один вызвал другой ... и все это работает, без каких-либо специальных трюков с UoW.

0 голосов
/ 20 марта 2011

Я рекомендую запускать и останавливать ваш UnitOfWork за пределами Сервисов.Не знаю подходящих инструментов для мира .net, но вам следует поискать некоторые Aspekt Oriented Programming Tools.Или вручную создайте службу foreach класса-оболочки, которая только запускает единицу работы, затем делегирует ее действительной службе, а затем закрывает единицу работы.Если одна служба вызывает другую, она использует реальную реализацию, а не единицу работы.

...