简体   繁体   中英

How to properly publish DDD domain events with spring?

I am trying to implement domain driven design in my project. Here is my base Aggregate class:

public abstract class UUIDAggregate {
    private final DomainEventPublisher domainEventPublisher;

    protected void publish(DomainEvent domainEvent) {
        domainEventPublisher.publish(domainEvent);
    }
}

Let's say we have UserAccount aggregate:

public class UserAccount extends UUIDAggregate {
    private String email;
    private String username;
    private String password;
    private String firstName;
    private String lastName;

    public void update() {
        publish(new DomainEventImpl());
    }
}

Here is my DomainEventPublisher :

public interface DomainEventPublisher {
   void publish(DomainEvent event);
}

Here is DomainEventPublisherImpl :

@Component
public class DomainEventPublisherImpl implements DomainEventPublisher{
    @Autowired
    private ApplicationEventPublisher publisher;

    public void publish(DomainEvent event){
        publisher.publishEvent(event);
    }
}

Now, this seems like a good idea, the domain is separated from implementation but this does not work. DomainEventPublisher cannot be Autowired because UUIDAggregate is not a @Component or @Bean . One solution would be to create DomainService and publish event there but that seems like leaking of domain to domain service and if I go that way, I am going to anemic model. Also what I can do is to pass DomainEventPublisher as a parameter to every aggregate but that also does not seems like a good idea.

One idea would be to have a factory for domain objects:

@Component
class UserAccountFactoryImpl implements UserAccountFactory {
    @Autowired
    private DomainEventPublisher publisher;

    @Override
    public UserAccount newUserAccount(String email, String username, ...) {
        return new UserAccount(email, username, ..., publisher);
    }
}

Then your code creating a domain object is "publisher-free":

UserAccount userAccount = factory.newUserAccount("john@example.com", ...);

Or you might slightly change the design of the event-publishing:

public abstract class UUIDAggregate {
    private final List<DomainEvent> domainEvents = new ArrayList<>();

    protected void publish(DomainEvent domainEvent) {
        domainEvents.add(domainEvent);
    }
    public List<DomainEvent> domainEvents() {
        return Collections.unmodifiableList(domainEvents);
    }
}

@Component
class UserAccountServiceImpl implements UserAccountService {
    @Autowired
    private DomainEventPublisher publisher;

    @Override
    public void updateUserAccount(UserAccount userAccount) {
        userAccount.update();

        userAccount.domainEvents().forEach(publisher::publishEvent);
    }
}

This is different from your proposal: the service publishes the events, but doesn't create then - the logic stays in the domain object.

Further, you can change your publisher to minimize the boiler-plate code:

public interface DomainEventPublisher {
   void publish(UUIDAggregate aggregate);
}

Vaughn Vernon in his book IDDD just uses singleton like this:

DomainEventPublisher.instance().register(...);

DomainEventPublisher.instance().publish(...);

I know this approach doesn't use spring injection but it's much simplier than passing publisher to every aggregate and not that hard to test.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM