I am new to DDD, and I have ran into a problem with unique constraints. I have a problem where one of the fields (a value object) on my aggregate root cannot be a duplicate value. For example, a user has a unique username.
My domain layer contains:
public class User {
private UUID id;
private Username username;
private User(UUID id, Username username) {
this.id = id;
this.username = username;
}
public void rename(Username username) {
if (!username.equals(username)) {
this.username = username;
EventBus.raise(new UserRenamedEvent(username));
}
}
public UUID getId() {
return id;
}
public Username getUsername() {
return username;
}
public static User create(UUID id, Username username) {
User user = new User(id, username);
EventBus.raise(new UserCreatedEvent(user));
return user;
}
}
Username:
public record Username(String name) {
// Validation on username
}
As well as a simple CRUD repository interface, implemented in the infrastructure layer.
My application layer contains:
UserSerivce:
public interface UserService {
UUID createUser(Username username);
// Get, update and delete functions...
}
And UserServiceImpl:
public class UserServiceImpl implements UserService {
public UUID createUser(Username username) {
// Repository returns an Optional<User>
if (userRepository.findByName(username).isPresent()) {
throw new DuplicateUsernameException();
}
User user = User.create(UUID.randomUUID(), username);
repsitory.save(user);
return user.getId();
}
}
This solution doesn't feel right, as preventing duplicate usernames is domain logic, and should not be in the application layer. I have also tried creating a domain service to check for duplicate usernames, but this also feels wrong as the application service has access to the repository and can do this by itself.
If the user was part of an aggregate I would do the validation at the aggregate root level, but as user is the aggregate this isn't possible. I would really like to know the best place to validate the unique constraint.
EDIT: I decided to take VoiceOfUnreasons advice and not worry about it too much. I put the logic to check for duplicates in the application service, as it makes for readable code and works as expected.
This solution doesn't feel right, as preventing duplicate usernames is domain logic, and should not be in the application layer.
There are at least two common answers.
One is to accept that "domain layer" vs "application layer" is a somewhat artificial distinction, and to not get too hung up on where the branching logic happens. We're trying to ship code that meets a business need; we don't get bonus points for style.
Another approach is to separate the act of retrieving some information from the act of deciding what to do with it.
Consider:
public UUID createUser(Username username) {
return createUser(
UUID.randomUUID(),
username,
userRepository.findByName(username).isPresent()
);
}
UUID createUser(UUID userId, Username username, boolean isPresent) {
if (isPresent) {
throw new DuplicateUsernameException();
}
User user = User.create(userId, username);
repository.save(user);
return user.getId();
}
What I'm hoping to make clear here is that we actually have two different kinds of problems to address. The first is that we'd like to separate the I/O side effects from the logic. The second is that our logic has two different outcomes, and those outcomes are mapped to different side effects.
// warning: pseudo code ahead
select User.create(userId, username, isPresent)
case User(u):
repository.save(u)
return u.getId()
case Duplicate:
throw new DuplicateUsernameException()
In effect, User::create
isn't returning User
, but rather some sort of Result
that is an abstraction of all of the different possible outcomes of the create operation. We want the semantics of a process, rather than a factory.
So we probably wouldn't use the spelling User::create
, but instead something like CreateUser::run
or CreateUser::result
.
There are lots of ways you might actually perform the implementation; you could return a discriminated union from the domain code, and then have some flavor of switch statement in the application code, or you could return an interface, with different implementations depending on the result of the domain logic, or....
It largely depends on how important it is that the domain layer is "pure", how much additional complexity you are willing to take on to get it, including how you feel about testing the designs, which idioms your development team is comfortable with, and so on.
It should be noted that one can reasonably argue that the definition of "unique" itself belongs in the domain, rather than in the application.
In that case, the design is mostly the same, except that instead of passing "the answer" to the domain code, we pass the capability of asking the question.
select User.create(
userId,
username,
SomeServiceWrapperAround(
userRepository::findByName
))
Or we define a protocol, where the domain code returns representations of questions, and the application code does the I/O and passes representations of the answers back to the domain model.
(and, in my experience, begin questioning whether all this ceremony is actually making the design "better")