Can it register an domain event in the constructor

2019-08-22 02:05发布

问题:

The aggregate will be created by some application service, not by another aggregate.

Like this

SomeAggregate aggregate = new SomeAggregate();
repo.save(aggregate);

The expectation is that the aggregate is saved and one SomeAggregateCreated event is published when the application service is over. I have tested it, it is not always effective, sometimes the event is not registered immediately after the constructor is executed.

This is the teacher class:

public class Teacher extends AbstractAggregateRoot<Teacher> {

    public Teacher() {
        registerEvent(new TeacherAdded(id, name));
    }
}

This is the TeacherAdded:

@AllArgsConstructor
@Getter
@ToString
@EqualsAndHashCode(callSuper = true)
public class TeacherAdded extends AbstractDomainEvent {
    private TeacherId teacherId;    
    private String name;
}

This is AbstractDomainEvent and DomainEvent

@Getter
@ToString()
@EqualsAndHashCode()
public abstract class AbstractDomainEvent implements DomainEvent {
    protected Date occurredOn;

    public AbstractDomainEvent() {
        this(new Date());
    }

    public AbstractDomainEvent(Date occurredOn) {
        this.occurredOn = occurredOn != null ? occurredOn : new Date();
    }

    @Override
    public Date occurredOn() {
        return occurredOn;
    }    
}

public interface DomainEvent {
    public Date occurredOn();
}

The AbstractAggregateRoot is copied from org.springframework.data.domain.AbstractAggregateRoot<A>, and the hasCapturedEvent method is added for testing.

public boolean hasCapturedEvent(DomainEvent event) {
    return domainEvents.contains(event);
}

If I run this:

// ...
TeacherAdded teacherAdded = new TeacherAdded(teacherId, teacherName);
Teacher teacher = new Teacher();
assertTrue(teacher.hasCapturedEvent(teacherAdded)); 

It sometimes fails and sometimes succeeds.

回答1:

The answer is yes. The domain events registered by the constructor can be published and listened normally. They are the same as the events registered by the usual method.

The test I provided in the problem description is defective. After modifying AbstractDomainEvent.hasCapturedEvent and its associated code, the test can pass.

This is new AbstractDomainEvent.hasCapturedEvent and test method.

    public boolean hasCapturedEvent(DomainEvent event, long occurredOnAdjustment) {
        if (occurredOnAdjustment <= 0) {
            return this.domainEvents.contains(event);
        } else {
            return this.domainEvents.stream().anyMatch(eventOnStream -> {
                return eventOnStream.equalsExcludedOccurTime(event)
                    && System.currentTimeMillis() - eventOnStream.occurredOn().getTime() <= occurredOnAdjustment;
            });
        }
    }

        TeacherAdded teacherAdded = new TeacherAdded(teacherId, teacherName);
        Teacher teacher = new Teacher();
        assertTrue(teacher.hasCapturedEvent(teacherAdded, 1000L)); 

This is new TeacherAdded.

public interface DomainEvent {
    public Date occurredOn();

    public default boolean equalsExcludedOccurTime(DomainEvent other) {
        return false;
    }
}

@lombok...
public class TeacherAdded extends AbstractDomainEvent {
    private TeacherId teacherId;    
    private String name;

    @Override
    public boolean equalsExcludedOccurTime(DomainEvent other) {
        if (!(other instanceof TeacherAdded)) {
            return false;
        } else {
            TeacherAdded other2 = (TeacherAdded)other;
            return Objects.equals(teacherId, other2.teacherId)
                && Objects.equals(name, other2.name);
        }
    }
}