How to properly publish DDD domain events with spring?
Asked Answered
P

2

7

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.

Pratique answered 4/10, 2019 at 19:17 Comment(0)
S
3

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("[email protected]", ...);

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);
}
Styracaceous answered 6/10, 2019 at 10:55 Comment(2)
I like your first idea, actually, that was what I was thinking also. The only "problem" was that my builder class was static(similar thing like Lombok builder) so I didn't want to create instances of UserAccout builder(although when I think now, I couldn't find any real reason why that would be bad). But I think that is completely "DDD way" of thinking and I will go that way. About second idea, also I had it in mind but if we always publish an event when updating user account, updating and publishing shouldn't be separated. There is a big chance that someone will forget to publish an event.Castello
Your second implementation proposal is DEFINETLY the way to do it. Even Spring's AbstractAggregateRoot (docs.spring.io/spring-data/commons/docs/current/api/org/…) works in this way. basically every command could trigger many events (but it's dangerous without event sourcing or sagas) so ther should be published after the aggregate mutation is committedScrumptious
A
1

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.

Alleneallentown answered 12/7, 2020 at 9:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.