0

I model a User as an aggregate root and a User is composed of an Identifier value object as well as an Email value object. Both value objects can uniquely identify a User, however the email is allowed to change and the identifier cannot.

In most examples of DDD I have seen, a repository for an aggregate root only fetches by identifier. Would it be correct to add another method that fetches by email to the repository? Am I modeling this poorly?

Zachary Kuhn
  • 1,152
  • 6
  • 13

5 Answers5

2

I would say yes, it is appropriate for a repository to have methods for retrieving aggregates by something other than the identity. However, there are some subtleties to be aware of.

The reason that many repository examples only retrieve by ID is based on the observation that repositories coupled with the structure of aggregates cannot fulfill all query requirements. For instance, if you have a query which calls for some fields from an aggregate as well as some fields for a referenced aggregate and some summary data, the corresponding aggregate classes cannot be used to represent this data. Instead, a dedicated read-model is needed. Therefore, querying responsibilities are decoupled from the repository. This have several advantages (queries can be served by a dedicated de-normalized store) and it is the principal paradigm of CQRS. In this type of architecture, domain classes are only retrieved by the repository when some behavior needs to execute. All read-only use cases are served by a read-models.

The reason that I think it appropriate for a repository to have a GetByEmail method is based on YAGNI and battling complexity. You an allow your application to evolve as requirements change and grow. You don't need to jump to CQRS and separate read/write stores right away. You can start with a repository that also happens to have a query method. The only thing to keep in mind is that you should try to retrieve entities by ID when you need to invoke some behavior on those entities.

eulerfx
  • 36,769
  • 7
  • 61
  • 83
0

I would put this functionality into a service / business layer that is specific to your User object. Not every object is going to have an Email identifier. This seems more like business logic than the responsibility of the repository. I am sure you already know this, but here is good explanation of what I am talking about.

I would not recommend this, but you could have a specific implementation of your repository for a User that exposes a GetByEmail(string emailAddress) method, but I still like the service idea.

Community
  • 1
  • 1
DDiVita
  • 4,225
  • 5
  • 63
  • 117
0

I agree with what eulerfx has answered:

You need to ask yourself why you need to get the AR using something other than the ID.

I think it would be rather obvious that you do not have the ID but you do have some other unique identifier such as the e-mail address.

If you go with CQRS you need to first determine whether the data is important to the domain or only to the query store. If you require the data to be 100% consistent then it changes things slightly. You would, for instance, need 100% consistency if you are checking whether an e-mail address exists in order to satisfy the unique constraint. If the queried data is at any time stale you will probably run into problems.

Remember that a repository represents a collection of sorts. So if you do not need to actually operate on the AR (command side) but you have decided that where you are using your domain is appropriate then you could always go for a ContainsEMailAddress on the repository; else you could have a query side for your domain data store also since your domain data store (OLTP type store) is 100% consistent whereas your query store (OLAP type store) may only be eventually consistent, as is typical of CQRS with a separate query store.

Eben Roux
  • 12,983
  • 2
  • 27
  • 48
0

Would it be correct to add another method that fetches by email to the repository? - I would not do that. In my opinion a repository should have only methods for getting by id, save and delete.

I'd rather ask why you don't have user id in the command handler in which you want to fetch the user and call a domain method on it. I don't know what exactly you are doing, but for the login/register scenario, I would do following. When a user logs in, he passes an email address and a password, and you do a query to authenticate the user - this would not use domain or repository (that is just for commands), but would use some query implementation which would return some UserDto which would contain user id, from this point you have the user id. Next scenario is registration. The command handler to create a new user would create a new user entity, then the user needs to log in.

xhafan
  • 2,140
  • 1
  • 26
  • 26
0

In most examples of DDD I have seen, a repository for an aggregate root only fetches by identifier.

I'd be curious to know what examples you've looked at. According to the DDD definition, a Repository is

A mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects.

Search obviously includes getting a root or a collection of roots by all sorts of criteria, not only their ID's.

Repository is a perfect place for GetCustomerByEmail(), GetCustomersOver18(), GetCustomersByCountry(...) and so on.

guillaume31
  • 13,738
  • 1
  • 32
  • 51
  • Which means you cannot pass a repository into a command handler in a generic way (IRepository), but it needs to be a specialized version IUserRepository. So you would need to remember which entity has specialized repository and which has generic. Recently I was looking at this and found this blog about 'finder pattern' which might be a better generic solution for repository search: http://russelleast.wordpress.com/2008/09/20/implementing-the-repository-and-finder-patterns/ – xhafan Dec 11 '12 at 08:03
  • I fail to see how it would be a more generic solution. `IEntityFinder` implementations as they are described in the blog post still contain specific methods (`ByLoginName()`, `ByToken()`...) that do not appear at the interface level. So a command handler knowing only about `EntityRepository` wouldn't know about these methods anyway. – guillaume31 Dec 11 '12 at 09:22
  • It seems to me the article is about "Providing a fluent interface to our repositories", not about pairing generic command handlers with generic repositories. Actually, I don't believe repositories should systematically expose a generic interface. More about this here : http://codebetter.com/gregyoung/2009/01/16/ddd-the-generic-repository/ – guillaume31 Dec 11 '12 at 10:12
  • You are right, that still needs specialized repository as well. Sorry for wasting your time :) So far I think it might be not that bad idea using generic `IRepository` for a command handler when you don't need special query, and using `IUserRepository : IRepository` with query methods when you need them. – xhafan Dec 13 '12 at 23:04
  • No problem ;) Sure, why not, as far as generic queries are concerned. – guillaume31 Dec 14 '12 at 09:19