聚合根片段代码
介绍
聚合根很好地组织关联的实体和值对象,形成一个相对独立的业务领域,让用户写出来的代码相对内聚。
但随着业务不断深入以及随着时间的推移,业务规模和复杂度也可能增长,所以越来越多的实体和值对象需要和聚合关联,并在最终导致聚合根的代码体积越来越庞大。
在不拆分聚合的情况下,阅读一个聚合根的业务逻辑十分复杂。因此 Phoenix 提供了能够拆分聚合根代码并同时保持内聚的特性:聚合根片段代码特性
需要注意的是,Phoenix 只支持一个命令只能被一个聚合根处理, 如果某个 SubAggregate 想要被多个不同类型聚合根共享,请使用对象组合 + 委派模式(Delegation Pattern)而不是本文的特性。
聚合根片段代码能够保持聚合根原有业务逻辑不变,并且在代码的组成方式上,支持将命令处理器的方法写到关联的类中(成员、父类、接口):
- 配置
quantex.phoenix.server.method-lookup-strategy-class-name
也就是聚合根扫描策略为com.iquantex.phoenix.server.aggregate.cls.SegmentAggregateRelatedStrategy
(默认已配置此实现) 或其他自定义实现 - 在聚合根中,按 1 提供的策略类支持的方式组合一个聚合根类(默认情况下,支持成员、父类、接口)
- 在片段代码的类头部增加
@AggregateSegment
注解 - (可选)在需要被递归扫描的成员上使用
@Inject
注解标识
如果用户觉得 @Inject 的方式太过于繁琐, 也可以在 quantex.phoenix.server.method-lookup-strategy-class-name
实现自己的片段代码扫描特性, 只需要实现 com.iquantex.phoenix.server.aggregate.cls.AggregateRelatedStrategy
并返回需要扫描的类即可, Phoenix 会检索并过滤不包含 @AggregateSegment
的类.
注意,事务聚合根处理时会使用序列化深拷贝一个副本用于 "try" 执行用户代码,因此当使用业务对象组合、片段代码等特性时,需要特别注意一些对象的序列化。
Spring 支持
使用片段代码特性的聚合根片段支持 Spring 的依赖注入,并且在聚合根单元测试中也支持了 Mock 一个 Bean 的能力。
目前 Phoenix 的依赖注入需要使用注解 @Inject 标识成员,并且只支持注入根聚合根的成员,当嵌套层次较深时暂不支持. 如:
public class Parent {
@Inject private ChildrenSegment businessObj;
}
public class ChildrenSegment {
@Inject private GrandChildSegment businessObj;
}
public class GrandChildSegment {
@Autowired private MockService service;
}
下面是聚合根依赖注入的正确示例:
@EntityAggregateAnnotation(aggregateRootType = "test")
public class Parent implements Serializable {
/** 这里不序列化该类, 转而使用 Phoenix 的自选快照存储数据, 实际使用时按需实现 */
@Inject private transient ChildrenSegment businessObj;
}
@AggregateSegment
public class ChildrenSegment {
/** Bean 和 配置都支持注入 */
@Autowired private MockService mockService;
@Value("${some.spring.properties}")
private String propertiesValue;
@CommandHandler(aggregateRootId = "id")
public ActReturn act(Boolean cmd) {
return ActReturn.builder().build();
}
}
使用说明
SegmentAggregateRelatedStrategy 不会重复扫描同一个类, 且不会扫描成员的接口, 只有被 @Inject
的成员及其也被 @Inject
的成员才会被扫描, 未被 @Inject
标识的成员并不会被扫描.
下面的代码中 Parent
聚合根同时拥有 ImplementationSegmentA
,ImplementationSegmentB
,InheritanceSegment
,CompositionSubOneSegment
,CompositionSubTwoSegment
,CompositionSubTwoSubSegment
的 act()、on() 方法, 示例代码如下:
import java.io.Serializable;
@EntityAggregateAnnotation(aggregateRootType = "test")
public static class Parent extends InheritanceSegment
implements ImplementationSegmentA, ImplementationSegmentB {
@Inject private CompositionSubOneSegment subOneAggregate;
@Inject private CompositionSubTwoSegment subTwoAggregate;
// 不会被扫描, 该类不是片段代码.
@Inject private CompositionSubThreeSegment subThreeAggregate;
private String notnull;
}
// ========= 接口
@AggregateSegment
public interface ImplementationSegmentA {
@CommandHandler(aggregateRootId = "id")
default ActReturn act(Boolean cmd) {
return ActReturn.builder().build();
}
}
@AggregateSegment
public interface ImplementationSegmentB {
@CommandHandler(aggregateRootId = "id")
default ActReturn act(Short cmd) {
return ActReturn.builder().build();
}
}
// ========== 继承
@AggregateSegment
public static class InheritanceSegment {
@CommandHandler(aggregateRootId = "id")
public ActReturn act(String cmd) {
return ActReturn.builder().build();
}
}
// ========= 下级(成员)聚合根, Three 不会被扫描.
@AggregateSegment
public static class CompositionSubOneSegment {
// 该类不会被扫描.
private CompositionSubOneSubSegment subOneSubAggregate;
@QueryHandler(aggregateRootId = "id")
public ActReturn act(Integer cmd) {
return ActReturn.builder().build();
}
}
@AggregateSegment
public static class CompositionSubTwoSegment {
@Inject private CompositionSubTwoSubSegment subTwoSubAggregate;
@QueryHandler(aggregateRootId = "id")
public ActReturn act(Long cmd) {
return ActReturn.builder().build();
}
}
public static class CompositionSubThreeSegment {
@QueryHandler(aggregateRootId = "id")
public ActReturn act(Character cmd) {
return ActReturn.builder().build();
}
}
// ========= 下级的下级(只有 Two 会被扫描)
@AggregateSegment
public static class CompositionSubOneSubSegment {
@QueryHandler(aggregateRootId = "id")
public ActReturn act(Byte cmd) {
return ActReturn.builder().build();
}
}
@AggregateSegment
public static class CompositionSubTwoSubSegment {
@QueryHandler(aggregateRootId = "id")
public ActReturn act(BigInteger cmd) {
return ActReturn.builder().build();
}
}