Агрегировать как услугу - PullRequest
2 голосов
/ 02 мая 2019

Предположим, что для обработки какого-либо запроса службе требуется глобальная конфигурация.

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

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

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

ConfigService {
@Inject
configRepository;
@Inject
eventGateway;

@CommandHandler
handle(changeConfig){
let current = configRepository.loadCurrent;
//some checks

//persist here?
eventGateway.send(confgChanged)
}

@EventHandler
on(configChanged){
//or persist here?
configRepository.saveCurrent(configChanged.data)
}

}

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

Я не уверен, что мне здесь не хватает в понимании концепций DDD, проще говоря, я хотел бы знать, гдепоместить обработчик команд для чего-то, что не является ни агрегатом, ни сущностью.Может быть, я должен создать обработчик команд, который вызывает службу Config, вместо этого делая config config обработчиком команд.

Ответы [ 2 ]

2 голосов
/ 06 мая 2019

Используете ли вы Axon без источников событий?

В среде Axon, как правило, рекомендуется изменять только состояние агрегата с событиями. Если вы собираетесь смешивать состояние или конфигурацию, загруженную из репозитория, с состоянием из хранилища событий, как вы сможете гарантировать, что при воспроизведении одних и тех же событий результирующее состояние будет одинаковым? В следующий раз, когда агрегат загружен, в вашем configRepository может быть другое состояние, что приведет к другому состоянию и другому поведению вашего агрегата.

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

Конкретный пример: представьте, что ваш агрегат обработал команду для включения службы электронной почты. Агрегат сделал это, применив EmailServiceEnabledEvent и изменив свое собственное состояние на «логическое emailEnabled = true». Через некоторое время агрегат выгружается из памяти. Теперь вы измените этот конфигурационный репозиторий, чтобы отключить включение службы электронной почты. Когда агрегат загружается снова, применяются события из хранилища событий, но на этот раз он загружает конфигурацию из вашего репозитория, которая сообщает, что не должна включать службу электронной почты. Состояние логического emailEnabled остается ложным. Вы отправляете команду отключения службы электронной почты в агрегат, но обработчик команд в агрегате считает, что электронная почта уже отключена и не применяет EmailServiceDisabledEvent. Сервис электронной почты оставлен включенным.

Вкратце: я бы порекомендовал использовать команды для изменения конфигурации вашего агрегата.

1 голос
/ 05 мая 2019

Мне кажется, что ваша глобальная конфигурация является либо спецификацией , либо набором правил, как в движке правил .

В отличие от шаблонов, описанных в GOF book , в DDD некоторые строительные блоки / шаблоны являются более общими и могут применяться к различным типам объектов, которые у вас есть.

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

A Объект значения - это то, что не имеет идентичности, (в большинстве случаев) является неизменным, два экземпляра можно сравнить по равенству их свойств. Объект стоимости представляет важные понятия в наших доменах, такие как: Деньги в системе, которая занимается бухгалтерским учетом, банковским делом и т. Д., Vector3 и Matrix3 в системах, которые выполняют математические вычисления и симуляции, таких как системы моделирования (3dsMax, Maya), видеоигры и т. Д. Они содержат важное поведение.

Таким образом, все, что вам нужно отследить и идентифицировать, может быть Entity .

Вы можете иметь Спецификацию , которая является сущностью, Правило , которая является сущностью, Событие также может быть объектом, если ему присвоен уникальный идентификатор. В этом случае вы можете относиться к ним так же, как к любому другому объекту. Вы можете создавать агрегаты, иметь репозитории и сервисы и использовать EventSourcing при необходимости.

С другой стороны a Спецификация , a Правило , Событие или Команда также может быть Значения объектов .

Технические характеристики и Правила также могут быть Доменные службы .

Одной из важных вещей здесь является также Ограниченный контекст . Система, которая обновляет эти правила, вероятно, находится в другом ограниченном контексте , чем система, которая применяет эти правила. Также возможно, что это не так.

Вот пример.

Давайте создадим систему, в которой Клиент сможет покупать вещи. Эта система также будет иметь Скидки на Заказы , которые имеют определенные Правила .

Допустим, у нас есть правило, которое гласит: если Клиент сделал Заказ с более чем 5 LineItems он получает скидку. Если у этого ордера общая стоимость (скажем, 1000 $), он получает скидку.

Процент скидок может быть изменен Отделом продаж . Система продаж содержит OrderDicountPolicy агрегатов, которые она может изменять. С другой стороны, Система заказов только читает OrderDicountPolicy агрегатов и не сможет их изменять, так как это является обязанностью Отдел продаж .

Система продаж и Система заказов могут быть частью двух отдельных Ограниченных контекстов : Продажа и Заказы . Ограниченный контекст заказов зависит от Ограниченный контекст продаж .

Примечание. Я пропущу большинство деталей реализации и добавлю только релевантные элементы, чтобы сократить и упростить этот пример. Если его намерение неясно, я отредактирую и добавлю дополнительные сведения. UUID , DiscountPercentage и Деньги являются ценными объектами, которые я пропущу.

public interface OrderDiscountPolicy {

    public UUID getID();

    public DiscountPercentage getDiscountPercentage();
    public void changeDiscountPercentage(DiscountPercentage percentage);

    public bool canApplyDiscount(Order order);
}

public class LineItemsCountOrderDiscountPolicy implements OrderDiscountPolicy {

    public int getLineItemsCount() { }

    public void changeLineItemsCount(int count) { }

    public bool canApplyDiscount(Order order) { 
        return order.getLineItemsCount() > this.getLineItemsCount();
    }

    // other stuff from interface implementation
}

public class PriceThresholdOrderDiscountPolicy implements OrderDiscountPolicy {

    public Money getPriceThreshold() { }

    public void changePriceThreshold(Money threshold) { }

    public bool canApplyDiscount(Order order) { 
        return order.getTotalPriceWithoutDiscount() > this.getPriceThreshold();
    }

    // other stuff from interface implementation
}

public class LineItem {

    public UUID getOrderID() { }
    public UUID getProductID() { }
    public Quantity getQuantity { }
    public Money getProductPrice() { } 

    public Money getTotalPrice() {
        return getProductPrice().multiply(getQuantity());
    }
}

public enum OrderStatus { Pending, Placed, Approced, Rejected, Shipped, Finalized }

public class Order {

    private UUID mID;
    private OrderStatus mStatus;
    private List<LineItem> mLineItems;
    private DscountPercentage mDiscountPercentage;

    public UUID getID() { }
    public OrderStatus getStatus() { }
    public DscountPercentage getDiscountPercentage() { };

    public Money getTotalPriceWithoutDiscount() { 
        // return sum of all line items
    }

    public Money getTotalPrice() { 
        // return sum of all line items + discount percentage
    }

    public void changeStatus(OrderStatus newStatus) { }

    public List<LineItem> getLineItems() {
        return Collections.unmodifiableList(mLineItems);
    }

    public LineItem addLineItem(UUID productID, Quantity quantity, Money price) {
        LineItem item = new LineItem(this.getID(), productID, quantity, price);
        mLineItems.add(item);
        return item;
    }

    public void applyDiscount(DiscountPercentage discountPercentage) {
        mDiscountPercentage = discountPercentage;
    }
}

public class PlaceOrderCommandHandler {

    public void handle(PlaceOrderCommand cmd) {

        Order order = mOrderRepository.getByID(cmd.getOrderID());

        List<OrderDiscountPolicy> discountPolicies =
             mOrderDiscountPolicyRepository.getAll();

        for (OrderDiscountPolicy policy : discountPolicies) { 

            if (policy.canApplyDiscount(order)) {
                order.applyDiscount(policy.getDiscountPercentage());
            }
        }

        order.changeStatus(OrderStatus.Placed);

        mOrderRepository.save(order);
    }
}

public class ChangeOrderDiscountPolicyPercentageHandler {

    public void handle(ChangeOrderDiscountPolicyPercentage cmd) {

        OrderDiscountPolicy policy = 
            mOrderDiscountRepository.getByID(cmd.getPolicyID());

        policy.changePercentage(cmd.getDiscountPercentage());

        mOrderDiscountRepository.save(policy);
    }
}

Вы можете использовать EventSourcing , если считаете, что он подходит для некоторых агрегатов. Книга DDD содержит главу о глобальных правилах и спецификациях .

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

Допустим, у нас есть 2 службы: Служба Orders и OrdersDiscountService .

Есть несколько способов реализовать эту операцию. Мы можем использовать:

  • Хореография с событиями
  • Оркестровка с явным Saga или Process Manager

Вот как мы можем это сделать, если мы используем хореографию с событиями.

  1. CreateOrderCommand -> OrdersService -> OrderCreatedEvent

  2. OrderCreatedEvent -> OrdersDiscountService -> OrderDiscountAvailableEvent или OrderDiscountNotAvailableEvent

  3. OrderDiscountAvailableEvent или OrderDiscountNotAvailableEvent -> Служба заказов -> OrderPlacedEvent

В этом примере для размещения заказа OrdersService будет ожидать OrderDiscountNotAvailableEvent или OrderDiscountNotAvailableEvent , поэтому он может применить скидку перед изменением статуса заказа на OrderPlaced .

Мы также можем использовать явную Saga для выполнения Orchestration между службами.

Эта сага будет содержать последовательность шагов процесса, чтобы она могла его выполнить.

  1. PlaceOrderCommand -> Saga
  2. Сага просит OrdersDiscountService , чтобы узнать, доступна ли скидка для этого Заказ .
  3. Если доступна скидка, Сага звонит Служба заказов , чтобы применить скидку
  4. Saga звонки OrdersService для установки статуса Order в OrderPlaced

Примечание: шаги 3 и 4 можно комбинировать

Возникает вопрос: * "Как OrdersDiscountService получить всю необходимую информацию для заказа для расчета скидок?" *

Этого можно добиться, добавив всю информацию о заказе в Событие , которое получит эта служба, или OrdersDiscountService позвоните Служба заказов , чтобы получить информацию.

Вот Отличное видео от Мартина Фолвера об архитектуре, управляемой событиями, в котором обсуждаются эти подходы.

Преимущество оркестровки с Saga состоит в том, что точный процесс явно определен в Saga и может быть найден, понят и исправлен.

Наличие неявных процессов, как в случае Хореография с событиями может быть сложнее для понимания, отладки и поддержки.

Недостатком саг является то, что мы определяем больше вещей.

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

Вот некоторые дополнительные ресурсы:

https://blog.couchbase.com/saga-pattern-implement-business-transactions-using-microservices-part/

https://blog.couchbase.com/saga-pattern-implement-business-transactions-using-microservices-part-2/

https://microservices.io/patterns/data/saga.html

Архитектура LMAX очень интересно читать. Это не распределенная система, но управляемая событиями и записывает как входящие события / команды, так и исходящие события. Это интересный способ уловить все, что происходит в системе или сервисе.

...