假定服务需要某种全局配置来处理某些请求的情况。
例如,当用户想要做某事时,它需要一些全局配置来检查是否允许该用户这样做。
我意识到,在轴突中,我可以拥有无需指定目标集合即可处理命令的命令处理程序,因此处理部分不是问题。
问题是我希望在尝试更改配置时在此之上具有永久存储以及一些不变性的地方。配置的整个想法是,它应该像轴突中的聚合一样保持一致。
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服务成为命令处理程序。
答案 0 :(得分:2)
您在这里使用Axon而不采购事件吗?
在Axon框架中,通常最好的做法是仅更改带有事件的聚合的状态。如果要将混合存储库中加载的状态或配置与事件存储中的状态进行混合,那么如何保证在重播相同事件时所得到的状态相同?下次加载聚合时,configRepository中可能存在不同的状态,从而导致聚合的状态和行为不同。
为什么这样不好?嗯,这些相同的事件可能已经由事件处理器处理过,它们可能已经填充了查询表,它们可能已经将消息发送到其他系统,或者根据系统当时的状态进行了其他工作。您的查询数据库和集合之间将存在分歧。
一个具体示例:想象您的聚合处理了一个打开电子邮件服务的命令。聚合通过应用EmailServiceEnabledEvent并将其自身的状态更改为“ boolean emailEnabled = true”来实现此目的。一段时间后,聚合将从内存中卸载。现在,您可以更改configurationRepository以禁用打开电子邮件服务。当再次加载聚合时,将应用事件存储中的事件,但是这次它将从您的存储库中加载配置,该配置指示不应打开电子邮件服务。 'boolean emailEnabled'状态保留为false。您向集合发送了一个禁用电子邮件服务命令,但是集合中的命令处理程序认为该电子邮件已被禁用,并且不应用EmailServiceDisabledEvent。电子邮件服务保持打开状态。
简而言之:我建议使用命令来更改聚合的配置。
答案 1 :(得分:1)
在我看来,您的全局配置是specification或rules engine中的一组规则。
与GOF book中描述的模式不同,在DDD中,某些构造块/模式更为通用,可以应用于您拥有的不同类型的对象。
例如, 实体 具有生命周期并具有标识。生命周期的各个阶段通常是:创建,持久化,从存储中重建,修改,然后通过删除,归档,完成等来结束生命周期。
值对象 是不带标识的(大多数情况下)是不可变的,可以通过两个实例的属性相等来比较它们。 值对象代表了我们领域中的重要概念,例如:系统中的 Money ,用于处理会计,银行业务等, Vector3 和 Matrix3 在进行数学计算和仿真的系统中,例如建模系统(3dsMax,Maya),视频游戏等。它们包含重要的行为。
因此,您需要跟踪并具有身份的所有内容都可以是 实体 。
您可以拥有作为实体的 规范 ,而具有实体的 Rule strong>事件,如果为其分配了唯一的ID,也可以是一个实体。在这种情况下,您可以像对待其他实体一样对待它们。您可以形成聚合,具有存储库和服务,并在必要时使用 EventSourcing 。
另一方面, 规范 , 规则 , 事件< / em> 或 Command 也可以是 Value Objects 。
规范和 规则 也可以是 域服务 。
这里重要的一件事也是 边界上下文 。更新这些规则的系统可能与应用这些规则的系统处于不同的 有界上下文 。情况并非如此。
这是一个例子。
我们有一个系统,其中 客户 可以购买东西。此系统还将在具有特定 规则的 订单 上具有 折扣 。
假设我们有一条规则说:如果 客户 做出的 订单 5 LineItems ,他得到了折扣。如果该 Order 的总价为某个金额(例如1000美元),他将获得折扣。
销售团队 可以更改折扣百分比。 销售系统 具有可以修改的 OrderDicountPolicy 聚合。另一方面, 订购系统 仅读取 OrderDicountPolicy 聚合,因此无法对其进行修改是 销售团队 的责任。
销售系统 和 订购系统 可以是两个单独的 受限上下文 : 销售 和 订单 。 订单绑定上下文 取决于 销售绑定上下文 。
注意:我将跳过大多数实现细节,仅添加相关内容以简化和简化此示例。如果意图不明确,我将编辑并添加更多详细信息。 UUID , DiscountPercentage >和 Money 是我将跳过的值对象。
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 book有一章介绍全局规则和规范。
让我们看看在使用微服务的分布式应用程序中我们该怎么做。
假设我们有2个服务: OrdersService 和 OrdersDiscountService 。
有两种方法可以实现此操作。我们可以使用:
如果将“舞蹈与事件”结合使用,这就是我们的方法。
CreateOrderCommand -> OrdersService -> OrderCreatedEvent
OrderCreatedEvent -> OrdersDiscountService -> OrderDiscountAvailableEvent 或 OrderDiscountNotAvailableEvent
OrderDiscountAvailableEvent 或 OrderDiscountNotAvailableEvent -> OrdersService -> OrderPlacedEvent
在此示例中,放置订单 OrdersService 将等待 OrderDiscountNotAvailableEvent 或 OrderDiscountNotAvailableEvent ,因此它可以在将订单状态更改为 OrderPlaced 之前应用折扣。
我们还可以使用显式的 Saga 在服务之间进行编排。
此Saga将包含该过程的步骤顺序,以便它可以执行。
注意:第3步和第4步可以合并使用
这引发了一个问题:*“ OrdersDiscountService 如何获取订单所需的所有必要信息以计算折扣?” *
这可以通过在此服务将收到的 Event 中添加订单的所有信息,或通过具有 OrdersDiscountService < / em> 调用 OrdersService 以获取信息。
这里是事件驱动架构的Great video from Martin Folwer,讨论了这些方法。
带有 Saga 的编排的优点是确切的过程在 Saga 中明确定义了 ,并且可以找到,理解和调试。
像带事件编排这样的隐式过程可能更难以理解,调试和维护。
拥有 Sagas 的缺点是我们确实定义了更多东西。
就个人而言,我倾向于使用明确的 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
The LMAX Architecture非常有趣。它不是分布式系统,而是事件驱动的,并记录传入的事件/命令和发出事件。这是一种捕获系统或服务中发生的一切的有趣方法。