考虑我有这两个模型,它们位于不同的微服务中。我可以用两种方式写它们:
public class Order
{
public Guid Id { get; set; }
public Guid ItemId { get; set; }
public int Quantity { get; set; }
public DateTime CreatedAt { get; set; }
public OrderStatus Status { get; set; }
}
public class Delivery
{
public Guid OrderId { get; set; }
public string Address { get; set; } = null!;
public Guid WarehouseId { get; set; } = null!;
public DeliveryStatus Status { get; set; }
}
如果我们需要来自 DeliveryMicroservice 的特定订单详细信息,我们将使用 Rest 或其他方式(高耦合、同步查询)从 OrderMicroservice 查询它。或者我们也可以这样写
public class Order
{
public Guid Id { get; set; }
public Guid ItemId { get; set; }
public int Quantity { get; set; }
public DateTime CreatedAt { get; set; }
public string Address { get; set; } = null!;
public string WarehouseName { get; set; } = null!;
public OrderStatus Status { get; set; }
public DeliveryStatus DeliveryStatus {get;set;}
}
public class Delivery
{
public Guid OrderId { get; set; }
public Guid ItemId { get; set; }
public int Quantity { get; set; }
public DateTime CreatedAt { get; set; }
public string Address { get; set; } = null!;
public Guid WarehouseId { get; set; } = null!;
public DeliveryStatus Status { get; set; }
}
现在我们不需要从不同的微服务中查询任何内容,因为每个微服务都包含其运行所需的所有必要信息。如果共享属性(如 DeliveryStatus)发生变化,执行此操作的微服务将使用新的 DeliveryStatus 发布 OrderDeliveryDetailsChanges,其他微服务将复制它(所有操作都通过代理异步发生)。
我看到的问题是:在发布的事件无法交付和数据重复的情况下可能会出现数据不一致(我们将 WarehouseId 存储在交付服务中,它的内存效率很高,但在 OrderService 中我们存储它的名称,但它的效率不如仓库)只是数据库字典条目)
在我看来,您似乎正在纠结两个截然不同的问题(这可能是真正的问题,或者可能只是意味着您对实际问题的描述引入了不相关的问题)。
第一个要审查的参考文献是外部数据与内部数据(Pat Helland,2005)。 特别是,我们要仔细考虑“锁定”数据(我们正在使用逻辑结构来防止其他人在我们处理数据时更改数据)与“解锁”数据(我们正在使用过时的副本进行工作)数据,自我们制作副本以来,权威副本可能已更改)。 如果您从另一个(微)服务中提取数据,那么通常这将是解锁数据(我们不会因为我们正在处理订单而阻止交付处理取得进展,对吗?)。 对于解锁的数据,您通常不关心它来自哪里,而是关心您的副本有多旧。 因此,“同步获取未锁定的副本,或失败”、“从异步更新的缓存获取未锁定的副本,或失败”、“从缓存获取,回退到同步调用,或失败”、“使用同步获取”调用、回退到异步缓存或失败”可能是“正确”的设计,具体取决于您如何评估不同的权衡。
但这与“我们应该如何在本地内存中对这些信息进行建模”是一个非常不同的问题。
public class Order
{
public Guid Id { get; set; }
public Guid ItemId { get; set; }
public int Quantity { get; set; }
public DateTime CreatedAt { get; set; }
public string Address { get; set; } = null!;
public string WarehouseName { get; set; } = null!;
public OrderStatus Status { get; set; }
public DeliveryStatus DeliveryStatus {get;set;}
}
public class Delivery
{
public Guid OrderId { get; set; }
public Guid ItemId { get; set; }
public int Quantity { get; set; }
public DateTime CreatedAt { get; set; }
public string Address { get; set; } = null!;
public Guid WarehouseId { get; set; } = null!;
public DeliveryStatus Status { get; set; }
}
我发现这种设计并不令人满意,因为我们可以在服务中更改的值与我们无法更改的值没有明显区别(因为其他一些服务对该信息具有权限)。
非常粗略地说:订单处理不应该与我们用来获取交付信息的解锁副本的策略紧密耦合;因此,我们可能不
想要暗示(解锁)交付信息是作为(锁定)订单信息的数据实体的一部分的代码。
因此,如果我们的订单处理确实需要交付信息的未锁定副本,那么在常见情况下,我期望的方法签名如下:
// Please use better naming than this example!
Order::verb(Value<UnlockedDeliveryInformation> delivery)
另一方面,如果您关心的是
报告(例如:在用户界面中显示信息),那么所有信息都已解锁,您真正关心的是模式设计。
在这种情况下,您可能应该研究一下我们所学到的有关创建向前和向后兼容的消息模式的知识。 对 XML 词汇进行版本控制(David Orchard,2003)是一个不错的起点。
我猜您仍然希望将订单信息与交货信息区分开来
{
"order": {},
"delivery": {}
}
但不难想象不同的设计具有更好的权衡的情况(又名:“这取决于”)。