处理 Axon 中的重要命令和事件有效负载
Dealing with non-trivial command and event payloads in Axon
每当我看到 Axon Bank 时,我就开始怀疑我是否应该遵循一套事件和命令的设计规则。
在 Axon Bank 中,事件和命令都完全由原语组成。在我的应用程序中,我倾向于尽可能避免使用原始用法,主要是为了构建一个富有表现力的域并在任何可以获得的地方都具有类型安全性。
Axon 本身带有一些 DDD 参考,但无论我浏览哪些文档,都没有一个示例使用复合对象作为 event/command 有效负载的一部分。
这让我很困惑。对成熟的 xml 和 json 序列化有内置支持,不仅仅是一些键值对。
我理解特别是事件往往是小而简单的结构,因为它们只反映增量状态变化,但复杂的领域模型和事件(条目)之间总会存在某种差距。
在我的域中,我可以有一堆 类,例如 OverdraftLimit
、CurrentBalance
、Deposit
和 AccountIdentifier
。
现在有两种可能的方式来设计事件和命令:
1。原语和广泛的转换
- 将事件视为带有漂亮标签的原始数据
- 一旦 "enters" 应用程序
就将原始数据转换为强大的对象
创建事件时,只需将它们再次剥离即可。
public class BankAccountcreatedEvent {
private final String accountIdentifier;
private final int overdraftLimt;
// ...
}
还有其他地方:
public void on (BankAccountCreatedEvent event) {
this.accountIdentifier = AccountIentifier.fromString(event.getAccountIdentifier());
this.overdraftLimit = new OverdraftLimit(event.getOverdraftLimit());
}
优点:
- 简单 command/event API 没有任何奇怪的依赖关系
- 使分发更容易
- 仅当实际事件结构发生变化时才需要 Upcasters,因此可以轻松预测。
缺点:
- 需要编写和维护一个巨大的转换层
- 解耦 events/commands 和域模型的其余部分主要出于技术原因引入了一个新的、人为的上下文差距
2。表达有效载荷
直接使用复杂的类型作为属性
public class BankAccountCreatedEvent {
private final BankAccountIdentifier bankAccountIdentifier;
private final OverdraftLimit overdraftLimit;
//..
}
优点:
- 少写,易读
- 把自然属于一起的东西放在一起
缺点:
- 域逻辑间接影响事件结构,将更频繁地需要向上转换并且更不可预测。
我需要第二个意见。有推荐的方法吗?
首先要记住的是,事件的序列化形式是您的正式合同。最后,您如何在 Java 类 中表示取决于每个应用程序。例如,如果您将序列化程序配置为忽略未知字段,则可以将您不关心的字段留在外面。
就我个人而言,我不介意事件中的原语。但是,我确实理解为某些字段使用显式值对象的价值,因为它们允许您表达每个字段所涉及的 "mathematics"。在标识符的情况下,它们可以防止 "mix-up" 使用标识符意外地尝试识别另一种类型的对象。
说到底,没那么重要。通过一些简单的 Jackson 注释,您可以将这些值对象转换为 JSON 中的简单值。例如,查看 @JsonValue。
public class BankAccountCreatedEvent {
private final BankAccountIdentifier bankAccountIdentifier;
private final OverdraftLimit overdraftLimit;
//..
}
将映射到:
{
"bankAccountIdentifier": "abcdef1234",
"overdraftLimit" : 1000
}
如果 BankAccountIdentifier 和 OverdraftLimit 类 都有一个 @JsonValue 注释方法,该方法将 return 它们的 'simple' 值。
每当我看到 Axon Bank 时,我就开始怀疑我是否应该遵循一套事件和命令的设计规则。
在 Axon Bank 中,事件和命令都完全由原语组成。在我的应用程序中,我倾向于尽可能避免使用原始用法,主要是为了构建一个富有表现力的域并在任何可以获得的地方都具有类型安全性。
Axon 本身带有一些 DDD 参考,但无论我浏览哪些文档,都没有一个示例使用复合对象作为 event/command 有效负载的一部分。
这让我很困惑。对成熟的 xml 和 json 序列化有内置支持,不仅仅是一些键值对。
我理解特别是事件往往是小而简单的结构,因为它们只反映增量状态变化,但复杂的领域模型和事件(条目)之间总会存在某种差距。
在我的域中,我可以有一堆 类,例如 OverdraftLimit
、CurrentBalance
、Deposit
和 AccountIdentifier
。
现在有两种可能的方式来设计事件和命令:
1。原语和广泛的转换
- 将事件视为带有漂亮标签的原始数据
- 一旦 "enters" 应用程序 就将原始数据转换为强大的对象
创建事件时,只需将它们再次剥离即可。
public class BankAccountcreatedEvent { private final String accountIdentifier; private final int overdraftLimt; // ... }
还有其他地方:
public void on (BankAccountCreatedEvent event) { this.accountIdentifier = AccountIentifier.fromString(event.getAccountIdentifier()); this.overdraftLimit = new OverdraftLimit(event.getOverdraftLimit()); }
优点:
- 简单 command/event API 没有任何奇怪的依赖关系
- 使分发更容易
- 仅当实际事件结构发生变化时才需要 Upcasters,因此可以轻松预测。
缺点:
- 需要编写和维护一个巨大的转换层
- 解耦 events/commands 和域模型的其余部分主要出于技术原因引入了一个新的、人为的上下文差距
2。表达有效载荷
直接使用复杂的类型作为属性
public class BankAccountCreatedEvent { private final BankAccountIdentifier bankAccountIdentifier; private final OverdraftLimit overdraftLimit; //.. }
优点:
- 少写,易读
- 把自然属于一起的东西放在一起
缺点:
- 域逻辑间接影响事件结构,将更频繁地需要向上转换并且更不可预测。
我需要第二个意见。有推荐的方法吗?
首先要记住的是,事件的序列化形式是您的正式合同。最后,您如何在 Java 类 中表示取决于每个应用程序。例如,如果您将序列化程序配置为忽略未知字段,则可以将您不关心的字段留在外面。
就我个人而言,我不介意事件中的原语。但是,我确实理解为某些字段使用显式值对象的价值,因为它们允许您表达每个字段所涉及的 "mathematics"。在标识符的情况下,它们可以防止 "mix-up" 使用标识符意外地尝试识别另一种类型的对象。
说到底,没那么重要。通过一些简单的 Jackson 注释,您可以将这些值对象转换为 JSON 中的简单值。例如,查看 @JsonValue。
public class BankAccountCreatedEvent {
private final BankAccountIdentifier bankAccountIdentifier;
private final OverdraftLimit overdraftLimit;
//..
}
将映射到:
{
"bankAccountIdentifier": "abcdef1234",
"overdraftLimit" : 1000
}
如果 BankAccountIdentifier 和 OverdraftLimit 类 都有一个 @JsonValue 注释方法,该方法将 return 它们的 'simple' 值。