Внедрение IOrderSender в объект домена

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

У меня есть объект PurchaseOrder. Этот заказ отправляется поставщику с помощью какой-либо службы (электронной почты или веб-сервиса). После отправки заказа пользователю, оформившему заказ, должно быть отправлено подтверждение.

Итак, я понял, что это тот случай, когда события домена были бы хорошим способом реализации публикации события PurchaseOrderMade.

Потом я задумался:

Действительно ли заказ сделан, если заказ не был отправлен?

Вы не сделали заказ только потому, что решили его сделать и записали, а заказ сделан в тот момент, когда вы передали его поставщику по договору без ошибок.

Поэтому я передумал и подумал, что в конце концов это может принадлежать домену, поэтому я должен отправить его, внедрив IPurchaseOrderSender в свой домен, а затем опубликовать OrderMadeEvent после успешной транзакции и отправки подтверждения в EventHandler.

Причина такова:

  1. Отправка заказа ЯВЛЯЕТСЯ важной частью процесса и может привести к изменению состояния (т.е. установить флаг, что заказ отправлен)
  2. Подтверждение НЕ имеет решающего значения, если это не удастся, заказ все равно будет сделан, и все пойдет по плану.
  3. Легко читать и изменять реализацию IOrderService.

Вопросы:

  1. Неужели это так плохо делать?
  2. Нарушает ли это принципы DDD?
  3. Вы сталкивались с этим раньше и решили это лучше?

Вот код:

        public void MakeOrder(PurchaseOrder order, IPurchaseOrderSender orderSender)
    {
        if(PurchaseOrders == null)
            PurchaseOrders = new List<PurchaseOrder>();
        PurchaseOrders.Add(order);

        orderSender.Send(order);
        DomainEvents.Raise(new PurchaseOrderIsMade(){Order = order});
    }

    public interface IPurchaseOrderSender
{
    void Send(PurchaseOrder order);
}

person cfs    schedule 25.10.2013    source источник


Ответы (1)


Я сталкивался с этим раньше, вот что я сделал:

Разделить локальную транзакцию удаленным вызовом процедуры.

Думаю, ничего страшного, если отправка заказа не удалась. В этом случае либо заказ размещен, но не установлен на «отправлено», либо заказ откатывается. Бизнес-оператор может вмешаться, если заказ не отправлен, или клиент позвонит, если заказ не размещен.

Но это раздражает, если что-то не так с транзакцией после успешной отправки заказа. В этом случае, если заказ откатывается, вмешательство становится более сложным, потому что мы потеряли уведомление поставщика. Уведомление обычно содержит идентификатор заказа поставщика, поэтому при необходимости мы можем отменить заказ с этим идентификатором.

Поэтому мы решили использовать обмен сообщениями.
1) PlaceOrderService отвечает за хранение заказа и отправляет сообщение.
2) Потребитель сообщения отправляет заказ поставщику и отправляет сообщение, содержащее уведомление поставщика.
3) Другой потребитель сообщения уведомления обновляет состояние заказа.

Каждый шаг изменяет только один агрегат или просто вызывает удаленный.

Надеюсь это поможет.

Обновить

1. Как бы вы реализовали здесь часть обмена сообщениями?

Я использую решение, упомянутое в dddsample Эрика Эванса, ApplicationEvents. Это просто простой интерфейс и реализация jms, что-то вроде

    public void placeOrder(...) {// method in application service
        ....//order making
        orderRepository.store(order);
        applicationEvents.orderWasPlaced(order);//injected applicationEvents 
        //better move this step out of transaction boundary if not using 2pc commit
        //make the method returnning order and use decorator to send the message 
        //    placeOrder(...) {
        //        Order order = target.placeOrder(...);//transaction end here
        //        applicationEvents.orderWasPlaced(order);
        //        return order;
        //    }
    }

    public class JmsApplicationEvents implements ApplicationEvents {
        public void orderWasPlaced(Order order) {
             //sends the message using messaging api of your platform
        }
    }

2. Я вижу, вы упомянули уведомление поставщиков, но давайте предположим, что это делается по электронной почте (что будет здесь основным сценарием). Я хотел бы знать, что транзакция была выполнена без ошибок (т. е. без smtp или сбоя соединения), но не могу полагаться на ответ, что заказ действительно был получен, это что-то изменит?

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

  1. Решение для обмена сообщениями по-прежнему подходит, если вам нужна строгая согласованность. Обмен сообщениями является транзакционным и может включать глобальную транзакцию, а электронная почта - нет.

  2. Обмен сообщениями обеспечивает большую доступность (ваш заказ не потерпит неудачу, даже если почтовый сервер не работает) и масштабируемость (очереди).

  3. Обработка сбоев более сложна в решении для обмена сообщениями, обычно требует компенсирующих действий. И пользователю сложнее получить информацию об обработке. Например, вы должны уведомить пользователя о ходе обработки заказа, поскольку следующие шаги являются асинхронными. И электронное письмо должно быть отправлено клиенту, если заказ отклонен поставщиком.

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

person Yugang Zhou    schedule 25.10.2013
comment
Хорошая мысль, но у меня есть несколько вопросов: 1. Как бы вы реализовали здесь часть обмена сообщениями (я читал о шинах сообщений, таких как nServiceBus, но это кажется довольно сложным только для этой функции)? 2. Я вижу, вы упомянули об уведомлении поставщиков, но предположим, что это делается по электронной почте (что будет здесь основным сценарием). Я хотел бы знать, что транзакция была выполнена без ошибок (т. на ответ, что заказ был действительно получен, это что-то изменит? - person cfs; 25.10.2013
comment
Да я вижу. Звучит разумно. Вы бы сделали PlaceOrderService службой домена или оставили бы все это за пределами домена? Ссылка на последний пункт. Программа осуществляет заказ товаров у поставщиков с доставкой на следующий день. К сожалению, только самые крупные компании имеют решения для прямой связи со своими системами. Так что на самом деле в этом случае, если есть проблема с подключением, заказ должен откатиться, так как нет возможности быть уверенным, что товар будет доставлен вовремя - возможно, даже придется попросить пользователя использовать телефон, чтобы быть уверенным, и запишите, что произошло в ретроспективе. - person cfs; 26.10.2013
comment
@cfs Обычно я размещаю PlaceOrderService на прикладном уровне, потому что он отвечает за полный вариант использования. Он организует модели предметной области. Я обновил ответ на случай сбоя. - person Yugang Zhou; 27.10.2013