我正在构建我的第一个事件源系统。它将使用以发布生命周期为核心的项目来具有多个域。如何有效地将两个域的事件重播或重新应用到第三个域内的新聚合中?
更具体。想象一下4个域,每个域都有自己的受限上下文和目的。这些上下文的简短描述:
project
是系统核心的复杂对象,几乎每个域都需要项目数据才能运行。一个项目具有一个或多个包含有限数量产品的ProductType。发布状态遵循生命周期:概念(尚未发布)>已宣布(可选)>销售>售罄(发布已结束)。在我的描述中,我重点关注已宣布的状态。对于发布领域,概念实际上并不是一个稀疏的东西,因为如果发布尚不了解,则项目始终在概念上。
我的第一个尝试是设置一个正常的聚合,该聚合处理传入的事件AnnouncementPublishedEvent
。这要求项目满足一些基本要求,例如“它具有名称”,“它具有描述”,“它至少具有一个图像”等等。这意味着我需要在应用事件之前验证此信息,因此我需要以某种方式在命令中提供一个project
实例。
在执行此操作时,我怀疑此方法违反了CQRS的目的,我应该查看真正的数据源:事件。我的下一个尝试是创建一个在事件AnnouncementPublicationRequestedEvent
时开始的Saga。这个故事需要检查在给定projectId周围发生了哪些事件,并将这些事件应用于新的“已发布项目”投影,以便(至少)验证是否可以接受请求。
我对跟踪处理器进行了研究和实验,但没有一个很好的例子说明如何在Axon的第4版中完成此任务。我还开始阅读有关Stackoverflow的其他几个问题,这些问题使我认为我可能需要重新考虑我的方法。
不幸的是,确切的代码无法共享,因为它不是开源的,即使我可以远离工作状态。我可以使用示例代码来说明我要做什么。
@Saga
@ProcessingGroup("AnnouncementPublication")
public class AnnouncementPublicationSaga {
private static int NUMBER_OF_ALLOWED_IMAGES
private PublicationId publicationId;
private ProjectId projectId;
private int numberOfImages = 0;
//...other fields
@StartSaga
@SagaEventHandler(associationProperty = "projectId")
public void handle(AnnouncementPublicationRequestedEvent event) {
publicationId = generatePublicationId();
//set parameters from event for saga to use
projectId = event.getProjectId();
targetPublicationStatus = event.getPublicationStatus();
date = event.getDate();
//initialize the 'publicated project' aggregate
//start a replay of associated events for this @ProcessingGroup
}
...
@SagaEventHandler(associationProperty = "projectId")
public void handle(ProjectCreatedEvent event) {
//Verify the project exists and has a valid name
}
...
/* Assumption* on how AssociationResolver works: */
@SagaEventHandler(AssociationResolver=MediaProjectAssociator.class )
public void handle(ProjectImageAdded event) {
numberOfImages += 1;
}
/* Assumption* on how AssociationResolver works: */
@SagaEventHandler(AssociationResolver=MediaProjectAssociator.class )
public void handle(ProjectImageRemoved event) {
numberOfImages -= 1;
}
...
/* In my head this should trigger if all events have been played
up to the PublicationRequestedEvent. Or maybe
*/
@SagaEventHandler(associationProperty = "publicationId")
public void handle(ValidationRequestCompleted event) {
//ValidationResult result = ValidationResult.builder();
...
if (numberOfImages > NUMBER_OF_ALLOWED_IMAGES) {
//reason to trigger PublicationRequestDeniedEvent
//update validationResult
}
...
if (validationResult.isAcceptable()) {
//Trigger AnnouncementPublicationAcceptedEvent
} else {
//Trigger AnnouncementPublicationDeniedEvent
}
}
...
@EndSaga
@SagaEventHandler(associationProperty = "publicationId")
public void handle(AnnouncementPublicationDeniedEvent event) {
//do stuff to inform why the publication failed
}
@EndSaga
@SagaEventHandler(associationProperty = "publicationId")
public void handle(AnnouncementPublicationAcceptedEvent event){
//do stuff to notify success to user
//choice: delegate to delivery for actual sharing of data
// or delivery itselfs listens for these events
}
}
* associationResolver代码是对它实际工作的假设,因为我还没有接近该部分。我的媒体上下文使用文件ID作为聚合标识符,因为并非每个事件都绑定到一个项目。但是,这个传奇需要重播的所有媒体事件都将在其中包含一个projectId作为字段。欢迎对此提供任何反馈,但现在这不是我的主要问题。
最后的结果应该是:出版物的记录或尝试的记录以及失败的原因。
发布的记录包含来自project
或media
事件的与发布相关的所有数据。这主要是潜在买家需要做出决定的信息。
出于这个问题的目的,我并不希望上述问题能够完全解决。我只想知道我在事件思考方面是否走正确的路,我重播相关事件的方法是否正确,以及是否可以在Axon4中完成。
答案 0 :(得分:0)
根据您的问题描述Martin,我假设您有几个与众不同的有界上下文。遵循边界上下文的定义:
明确定义应用模型的上下文。
明确设定团队组织的界限, 在应用程序特定部分的使用情况, 和物理表现形式,例如代码库和数据库模式。
在这些范围内保持模型严格一致, 但不要被外界的问题分散注意力或困惑。
由此,我想强调的是,在给定的绑定上下文中,您使用任何组件都使用相同的语言/ API。 但是,在上下文之间,您将非常有意识地进行共享,使用专用的上下文映射(例如反腐败层)来确保另一个域不会进入您的域。
如上所述,事件是特定有界上下文的一部分。 因此,理想情况下,应该使用来自其他上下文的多个事件流来在另一个上下文中重新创建/重放聚合。
最重要的是,在Axon中,只能根据 已发布自身的事件来重新创建聚合。
要想找到一个解决方案,其中给定的应用程序从其他应用程序中吸收事件以重新水化聚合,我将采取以下步骤:
最后,我想指出的是,Axon提供的有关与TrackingEventProcessor
绑定的重播的任何详细信息均用于CQRS应用程序的查询侧的事件处理。
希望这可以为您澄清一下马丁!如果没有,请在此答案下发表评论,我将相应地更新我的回复。