Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 997
Spasoje Petronijević Avatar asked Oct 04 '19 19:10

Spasoje Petronijević


2 Answers

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);
}
like image 166
ttulka Avatar answered Oct 17 '22 00:10

ttulka


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.

like image 36
3zmo Avatar answered Oct 17 '22 00:10

3zmo