Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where should I put a unique check in DDD?

I'm working on my first DDD project, and I think I understand the basic roles of entities, data access objects, and their relationship. I have a basic validation implementation that stores each validation rule with it's associated entity. This works fine for rules that apply to only the current entity, but falls apart when other data is needed. For example, if I have the restriction that a username must be unique, I would like the IsValid() call to return false when there is an existing user with the current name.

However, I'm not finding any clean way to keep this validation rule on the entity itself. I'd like to have an IsNameUnique function on the entity, but most of the solutions to do this would require me to inject a user data access object. Should this logic be in an external service? If so, how do I still keep the logic with the entity itself? Or is this something that should be outside of the user entity?

Thanks!

like image 741
Shane Fulmer Avatar asked Jun 15 '09 20:06

Shane Fulmer


2 Answers

I'm going to say that this is simply beyond the scope of DDD.

What you have with DDD is an aggregation of events that produce a useful model. However, data relationships between such models are not necessarily possible.

What consistency model are you using?

If you can commit multiple events in an ACID transaction you can ensure that changes to a group of aggregates happen atomically.

If you use an eventual consistency model you might not be able to actually validate these things until later. And when you do, you might need to compensate for things that have supposedly happened but are no longer valid.

Uniqueness must be answered within a context. If you model is small (in the thousands) you can have an aggregate represent the set of values that you want to be unique. Assuming a group aggregate transaction is possible.

If not, well, then you simply project your model to a database that support an uniqueness constraint. If this projection fails you have to go back to your aggregate and somehow mark it as invalid. All the while you need to consider failure. This is where a distributed long running process, like the saga pattern can be useful but also require that you think about additional things.

In summary, if you cannot use a storage model with a strong consistency guarantee everything becomes a lot more complicated. That said, there are good, well through out patterns for managing failures in a distributed environment but it turns the problem on it's head a bit because now you need to consider failure, at every point along the way, which is a good thing but it will require a bigger time investment.

like image 156
John Leidegren Avatar answered Sep 19 '22 09:09

John Leidegren


I like Samuel's response, but for the sake of simplicity, I would recommend implementing a Specification. You create a Specification that returns a boolean to see if an object meets certain criteria. Inject an IUserRepository into the Specification, check if a user already exists with that name, and return a boolean result.

public interface ISpecification<T>
{
  bool IsSatisfiedBy(TEntity entity);
}

public class UniqueUsernameSpecification : ISpecification<User>
{
  private readonly IUserRepository _userRepository;

  public UniqueUsernameSpecification(IUserRepository userRepository)
  {
    _userRepository = userRepository;
  }

  public bool IsSatisfiedBy(User user)
  {
    User foundUser = _userRepository.FindUserByUsername(user.Username);
    return foundUser == null;
  }
}

//App code    
User newUser;

// ... registration stuff...

var userRepository = new UserRepository();
var uniqueUserSpec = new UniqueUsernameSpecification(userRepository);
if (uniqueUserSpec.IsSatisfiedBy(newUser))
{
  // proceed
}
like image 37
Kevin Swiber Avatar answered Sep 19 '22 09:09

Kevin Swiber