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("[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);
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With