根据定义“域事件是域中发生的事情的表示”,将域对象用作域事件中的字段是很自然的。
使用事件溯源时,域事件是持久的。因此,如果他们使用域对象作为他们的字段,那么域对象也是持久的。这削弱了采用 CQRS 和事件溯源所获得的优势,使域对象更难以更改和发展。
考虑 Eric Evans 的 dddsample 的 CQRS 版本,用户故事是:
Given a cargo has been registered
And I request possible routes for the cargo
And some routes are shown
When I pick up a candidate
Then the cargo is assigned to the route
public class Cargo { // This is an aggregate
private TrackingId trackingId;
private RouteSpecification routeSpecification;
public void assignToRoute(final Itinerary itinerary) {
Delivery delivery = Delivery.derivedFrom(routeSpecification, itinerary);
apply(new CargoAssignedEvent(this.trackingId,
itinerary, delivery.routingStatus()));//sending the domain event
}
}
public class Itinerary { //This is a value object
private List<Leg> legs;
}
public class Leg { //Another value object
private VoyageNumber voyageNumber;
private UnLocode loadLocation;
private UnLocode unloadLocation;
private Date loadTime;
private Date unloadTime;
}
public class CargoAssignedEvent { // This is a domain event
private final String trackingId;
private final RouteCandidateDto route; //DTO form of itinerary containing a List of LegDto s
private final String routingStatus;
public CargoAssignedEvent(TrackingId trackingId, Itinerary itinerary,
RoutingStatus routingStatus) {
this.trackingId = trackingId.getValue(); //transform to primitive
this.route = toRoute(itinerary); ////transform to DTO
this.routingStatus = routingStatus.getCode(); //transform to primitive
}
......
}
如您所见,我使用 DTO 作为 DomainEvent 的字段来将领域模型(行程、路由状态)与事件持久性问题分开。但这可能会在事件处理程序方面造成一些不便和麻烦。如果 CargoAssignedEvent 的某些订阅者需要行程的推导来做出决定怎么办?然后我必须将 RouteCandidateDto 映射到行程。
一个潜在的解决方案是使用域对象作为字段,但在事件存储中引入一些适配器。加载或保存事件时,使用适配器映射域对象和 dto。
我做对了吗?任何想法都值得赞赏。
更新
行程可能是个特例。它被视为一个整体值,因此我无法将这个值对象拆分为一组较小的域事件,如 CargoLegEvent(TrackingId, Leg)。考虑交付案例,交付是货运领域的另一个重要价值对象,它比行程丰富得多:
/**
* The actual transportation of the cargo, as opposed to
* the customer requirement (RouteSpecification) and the plan (Itinerary).
*
*/
public class Delivery {//value object
private TransportStatus transportStatus;
private Location lastKnownLocation;
private Voyage currentVoyage;
private boolean misdirected;
private Date eta;
private HandlingActivity nextExpectedActivity;
private boolean isUnloadedAtDestination;
private RoutingStatus routingStatus;
private Date calculatedAt;
private HandlingEvent lastEvent;
.....rich behavior omitted
}
交付指示货物的当前状态,一旦登记了新的货物装卸事件或更改路线规范,就会重新计算:
//non-cqrs style of cargo
public void specifyNewRoute(final RouteSpecification routeSpecification) {
this.routeSpecification = routeSpecification;
// Handling consistency within the Cargo aggregate synchronously
this.delivery = delivery.updateOnRouting(this.routeSpecification, this.itinerary);
}
/**
* Updates all aspects of the cargo aggregate status based on the current
* route specification, itinerary and handling of the cargo. <p/> When
* either of those three changes, i.e. when a new route is specified for the
* cargo, the cargo is assigned to a route or when the cargo is handled, the
* status must be re-calculated. <p/> {@link RouteSpecification} and
* {@link Itinerary} are both inside the Cargo aggregate, so changes to them
* cause the status to be updated <b>synchronously</b>, but changes to the
* delivery history (when a cargo is handled) cause the status update to
* happen <b>asynchronously</b> since {@link HandlingEvent} is in a
* different aggregate.
*/
public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
this.delivery = Delivery.derivedFrom(routeSpecification(), itinerary(),
handlingHistory);
}
我想到一开始我需要一个 CargoDeliveryUpdatedEvent,比如:
//cqrs style of cargo
public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
apply(new CargoDeliveryUpdatedEvent(
this.trackingId, delivery.derivedFrom(routeSpecification(),
itinerary(), handlingHistory);
}
class CargoDeliveryUpdatedEvent {
private String trackingId;
private DeliveryDto delivery;//DTO ?
}
但最后我发现我可以使用更小的事件来更好地揭示意图,比如:
//cqrs style of cargo
public void deriveDeliveryProgress(final HandlingHistory handlingHistory) {
final Delivery delivery = Delivery.derivedFrom(
routeSpecification(), itinerary(), handlingHistory);
apply(new CargoRoutingStatusRecalculatedEvent(this.trackingId,
delivery.routingStatus());
apply(new CargoTransportStatusRecalculatedEvent(this.trackingId,
delivery.routingStatus());
....sends events telling other aspects of the cargo
}
由于事件更小更具体,不再需要 DeliveryDto 和它所需要的映射器(域对象 <--> DTO):
class CargoRoutingStatusRecalculatedEvent{
private String trackingId;
private String routingStatus;
}
class CargoTransportStatusRecalculatedEvent{
private String trackingId;
private String transportStatus;
}