【问题标题】:Event Sourcing - passing events from AR to entites事件溯源——将事件从 AR 传递给实体
【发布时间】:2017-03-16 16:07:07
【问题描述】:

我对更复杂的聚合根结构和事件溯源有疑问。假设我有这段代码:

class AggregateRoot{
    Entity entity1;
    Entity entity2;

    //this is returned to service layer to be persisted to db in event stream
    AggregateRootUpdatedState execCommand(int command){
        entity1 = new Entity();
        EntityUpdatedStateEvent event1 = this.entity1.changeState(command);

        EntityUpdatedStateEvent event2 = null;
        if(entity1.state==1) {  //since we already set sub entity - we can check this
            entity2 = new Entity();
            event2 = this.entity2.changeState(command);
        }

        AggregateRootUpdatedState parentEvent = new AggregateRootUpdatedState(event1, event2);
        //when(parentEvent);    //??? WE ALREADY CHANGED STATE IN TWO LINES ABOVE
        return parentEvent;
    }

    void when(AggregateRootUpdatedState event){ //needed for re-hydrating the event state
        entity1 = new Entity();
        entity1.when(event.event1);
        if(event.event2!=null) {
            entity2 = new Entity();
            entity2.when(event.event2);
        }
    }
}

class Entity{
    int state;

    EntityUpdatedStateEvent changeState(int state){
        EntityUpdatedStateEvent event = new EntityUpdatedStateEvent(state);
        when(event);
        return event;
    }

    void when(EntityUpdatedStateEvent event){
        this.state = event.state;
    }
}

class EntityUpdatedStateEvent{
    int state;

    EntityUpdatedStateEvent(int state) {
        this.state = state;
    }
}

class AggregateRootUpdatedState{
    EntityUpdatedStateEvent event1; //here we are nesting events AR->Entity
    EntityUpdatedStateEvent event2;

    AggregateRootUpdatedState(EntityUpdatedStateEvent event1, EntityUpdatedStateEvent event2) {
        this.event1 = event1;
        this.event2 = event2;
    }
}

如您所见,有一个聚合根AggregateRoot 有两个子实体entity1entity2。当 AR 接收到命令(在本例中为简单的int command)时,它必须调用子实体上的一些方法,以便它们修改其状态。作为对此的反应,他们触发EntityUpdatedStateEvent,通过调用when 方法自动应用到实体内部。此时应用事件保证实体返回时,聚合根将具有正确的状态集,并且我将能够在聚合根中进行比较测试if(entity1.state==1)。根据这个测试,我还更新了其他实体状态。然后将这两个事件组合成AggregateRootUpdatedState 事件,并保存到事件存储中。

现在,我的问题是 - 在 AR 中,我根本没有调用 when 方法,这是第一次发生 AggregateRootUpdatedState (仅在为 AR 补水时)。这是正确的方法吗,因为我看到 AR 状态也应该通过调用when 方法来修改?

还有其他方法可以将事件向下传递到 AR 层次结构吗?

更新

class AggregateRoot{
    List<SubEntityLevel1> subEntityLevel1s;
    int rootNum;

    void command(int x){
        rootNum = x*x;
        for(int i=0; i<x; i++){
            SubEntityLevel1 subEntityLevel1 = new SubEntityLevel1();
            subEntityLevel1.modify1(i, rootNum);
            subEntityLevel1s.add(subEntityLevel1);
        }
    }

    void when(AggregateRootModifiedEvent event){
        //HOW TO REFACTOR METHOD ABOVE TO EVENT?
    }
}

class SubEntityLevel1{
    int id;
    List<SubEntityLevel2> subEntityLevel2s;
    int sum = 0;

    void modify1(int id, int rootNum){
        //HOW TO MAKE EVENT FROM THIS AND THEN APPLY IN WHEN() METHOD?
        this.id = id;
        this.sum = rootNum;
        for(int i=0; i<id; i++){
            if(subEntityLevel2s.stream().noneMatch(se2 -> "0".equals(se2.id))) {
                SubEntityLevel2 subEntityLevel2 = new SubEntityLevel2();
                subEntityLevel2.modify2(i);
                subEntityLevel2s.add(subEntityLevel2);
                sum++;
            }
        }
    }

    void when(SubEntityLevel1Modified event){
        this.id = event.id;
        this.sum = event.sum;
        for(SubEntityLevel2Modified subEvent : event.subEntity2Events){
            when(subEvent);
        }
    }

    void when(SubEntityLevel2Modified event){
        SubEntityLevel2 subEntityLevel2 = new SubEntityLevel2();
        subEntityLevel2.when(event);
        subEntityLevel2s.add(subEntityLevel2);
    }

    void when(SubEntityLevel2Created event){
        //??????
    }
}

class SubEntityLevel2{
    String id;

    SubEntityLevel2Modified modify2(int x){
        SubEntityLevel2Modified event = new SubEntityLevel2Modified(String.valueOf(x));
        when(event);
        return event;
    }

    void when(SubEntityLevel2Modified event){
        this.id = event.id;
    }
}

//----- EVENTS

class AggregateRootModifiedEvent{
    int rootNum;
    List<SubEntityLevel1Modified> subEntity1Events;

    public AggregateRootModifiedEvent(int rootNum, List<SubEntityLevel1Modified> subEntity1Events) {
        this.rootNum = rootNum;
        this.subEntity1Events = subEntity1Events;
    }
}

class SubEntityLevel1Modified{
    int id;
    List<SubEntityLevel2Modified> subEntity2Events;
    int sum;

    public SubEntityLevel1Modified(int id, List<SubEntityLevel2Modified> subEntity2Events, int sum) {
        this.id = id;
        this.subEntity2Events = subEntity2Events;
        this.sum = sum;
    }
}

class SubEntityLevel2Created{}

class SubEntityLevel2Modified{
    String id;

    SubEntityLevel2Modified(String id){
        this.id = id;
    }
}

由于第一个示例对我的实际问题更加不真实,因此我用这个新的问题更新了问题,其中我实际上有 3 级嵌套,并且在它们中都有列表和一些额外的逻辑。我基本上在努力如何重构这些方法(例如SubEntityLevel1modify1 中的方法)以使用事件溯源(首先创建事件然后应用它)。

更新 2

好的,我似乎有一些解决方案,但似乎比没有事件溯源要复杂得多......

class AggregateRoot{
    List<SubEntityLevel1> subEntityLevel1s = new ArrayList<>();
    int rootNum;

    List<Object> command(int x){
        List<Object> tempEvents = new ArrayList<>();
        //rootNum = x*x;
        int tempRootNum = x*x;
        for(int i=0; i<x; i++){
            SubEntityLevel1 subEntityLevel1 = new SubEntityLevel1();
            List<Object> subEvents = subEntityLevel1.modify1(i, tempRootNum);
            //subEntityLevel1s.add(subEntityLevel1);
            SubEntityLevel1Added event = new SubEntityLevel1Added(subEvents);
            when(event);
            tempEvents.add(event);
        }
        AggregateRootModifiedEvent event = new AggregateRootModifiedEvent(tempRootNum);
        when(event);
        tempEvents.add(event);
        return tempEvents;
    }

    void when(AggregateRootModifiedEvent event){
        this.rootNum = event.rootNum;
    }

    void when(SubEntityLevel1Added event){
        SubEntityLevel1 subEntityLevel1 = new SubEntityLevel1();
        for(Object subEvent : event.events){
            subEntityLevel1.when(subEvent); //list of SubEntityLevel2Added AND SubEntityLevel1Initialized
        }
        subEntityLevel1s.add(subEntityLevel1);
    }
}

class SubEntityLevel1{
    int id;
    List<SubEntityLevel2> subEntityLevel2s = new ArrayList<>();
    int sum = 0;

    List<Object> modify1(int id, int rootNum){
        //this.id = id;
        //this.sum = rootNum;
        int tempSum = rootNum;
        List<Object> tempEvents = new ArrayList<>();
        for(int i=0; i<id; i++){
            if(subEntityLevel2s.stream().noneMatch(se2 -> "0".equals(se2.id))) {

                SubEntityLevel2 subEntityLevel2 = new SubEntityLevel2();
                SubEntityLevel2Initialized event = subEntityLevel2.initialize(i);
                //subEntityLevel2s.add(subEntityLevel2);
                SubEntityLevel2Added event2 = new SubEntityLevel2Added(event);
                when(event2);
                tempEvents.add(event2);

                //sum++;
                tempSum++;
            }
        }
        SubEntityLevel1Initialized event3 = new SubEntityLevel1Initialized(id, tempSum);
        when(event3);
        tempEvents.add(event3);
        return tempEvents;
    }

    void when(SubEntityLevel1Initialized event){
        this.id = event.id;
        this.sum = event.sum;
    }

    void when(SubEntityLevel2Added event){
        SubEntityLevel2 subEntityLevel2 = new SubEntityLevel2();
        subEntityLevel2.when(event.event);
        subEntityLevel2s.add(subEntityLevel2);
    }
}

class SubEntityLevel2{
    String id;

    SubEntityLevel2Initialized initialize(int x){
        SubEntityLevel2Initialized event = new SubEntityLevel2Initialized(String.valueOf(x));
        when(event);
        return event;
    }

    void when(SubEntityLevel2Initialized event){
        this.id = event.id;
    }
}

//----- EVENTS

class AggregateRootModifiedEvent{
    int rootNum;

    public AggregateRootModifiedEvent(int rootNum) {
        this.rootNum = rootNum;
    }
}

class SubEntityLevel1Added{
    List<Object> events;

    public SubEntityLevel1Added(List<Object> events) {
        this.events = events;
    }
}

class SubEntityLevel1Initialized {
    int id;
    int sum;

    public SubEntityLevel1Initialized(int id, int sum) {
        this.id = id;
        this.sum = sum;
    }
}

class SubEntityLevel2Added{
    SubEntityLevel2Initialized event;

    public SubEntityLevel2Added(SubEntityLevel2Initialized event) {
        this.event = event;
    }
}

class SubEntityLevel2Initialized {
    String id;

    SubEntityLevel2Initialized(String id){
        this.id = id;
    }
}

【问题讨论】:

    标签: java events domain-driven-design cqrs event-sourcing


    【解决方案1】:

    这两个事件随后被组合成 AggregateRootUpdatedState 事件,该事件被保存到事件存储中。

    在我看来,这就像一个反模式;您可以使其适用于模型的版本 1,但是当您想要开始重构或响应事件时,它会引入大量额外的工作。您可能应该考虑返回事件集合,而不是单个事件。

    您的聚合代码将如下所示

    History execCommand(int command){
        entity1 = new Entity();
        EntityUpdatedStateEvent event1 = this.entity1.changeState(command);
    
        EntityUpdatedStateEvent event2 = null;
        if(entity1.state==1) {  //since we already set sub entity - we can check this
            entity2 = new Entity();
            event2 = this.entity2.changeState(command);
        }
    
        // ALL of the state changes have already happened, so no need to
        // re-process the events.
        return History.of(event1, event2)
    }
    

    另外需要注意的是,在本例中,您一直在更改根实体而不产生事件,特别是在创建子事件时

        entity1 = new Entity();
    

    为什么实体生命周期的开始在您的模型中不是一个明确的事情?应该有一个事件,确保实体引用(this.entity1,this.entity2)始终以相同的方式分配。

    EntityOneCreatedEvent createEntityOne() {
        EntityOneCreatedEvent e = new EntityOneCreatedEvent();
        when(e);
        return e;
    }
    

    还有其他方法可以将事件向下传递到 AR 层次结构吗?

    一种方法是使用 Meyer 的command query separation 模式,并将创建事件的逻辑与更新实体状态的逻辑分开。 基本思想是,在查询中,您永远不会更改自己的状态(您可能会复制您的状态,然后更改副本);在命令中,您将更改应用到本地状态。

    如果您将状态视为不可变的值类型,那么这种模式真的很自然地从中流露出来。

    【讨论】:

    • 感谢您的回复。您能否检查我更新的问题,因为它现在更真实地反映了我的问题。我同意我可能应该有这个 CreatedEvent (但在我上面的新问题中,我不知道以后如何引用它)。此外,正如您在第一个代码 sn-p 中回答的那样 - 最终聚合根在第一次执行时不需要应用事件 - 仅在重新水合时才需要。有没有关于事件溯源的 Meyer 模式示例?
    • 我会看一下——不过,作为一般性提示:无论何时您询问 DDD,都应该尽可能接近您的实际问题。具有通用约束的通用域确实很难智能建模。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-06
    • 2020-05-27
    • 2021-06-24
    • 2022-10-23
    • 2021-07-25
    • 1970-01-01
    相关资源
    最近更新 更多