Domain-Driven Design – Using Value Objects in Repository

domain-driven-design

I have a Product entity with a Category value object (contrived example).

There's an API endpoint /products?category=Keyboards,Mice

The classes at play here are the:

  • ListProductsController (interfaces layer)
  • ProductsRetriever (application layer)
  • ProductRepository (domain / infrastructure layer)
  • Product (entity / aggregate root)
  • Category (value object)

Within the domain model, categories are modelled as a list of objects.

I'm a bit confused with regards to the repository, however. Should it have a method:

   public List<Product> findByCategoriesIn(List<Category> categories)
or
   public List<Product> findByCategoriesIn(List<String> categories)

If it should be queried using a ValueObject (i.e. Category) who should convert the list of strings from the API request into the list of categories?

Should it happen within the controller or should the application service take care of the transformation?

Best Answer

I'm a bit confused with regards to the repository

Not your fault.

It may help to review chapter six of the blue book. Evans writes

The REPOSITORY retrieves the requested object, encapsulating the machinery of database queries and metadata mapping.

In other words, the REPOSITORY api is expressed in the domain specific language, and the domain agnostic concerns of "how do I get this data to/from the database" are enclosed within the implementation.

From this, you can infer that the consumer of the repository API should be speaking in domain specific values, not domain agnostic primitives.

This is consistent with a theme that Evans comes back to repeatedly; that you want your developers to be spending most of their time in the domain specific language, not manipulating primitive data types.

If it should be queried using a ValueObject (i.e. Category) who should convert the list of strings from the API request into the list of categories?

So the capability for this conversion will normally be provided by the domain model, in the form of value object constructors, factories, message builders, and so on. These tools will normally be invoked by the application code, before everything moves into the domain specific view.

That said... there's been a sort of push back against that approach. It's really kind of weird to start from a string, and now we have to convert it into a MumbleId, that gets passed to a repository that will convert it back into a string for use as a query parameter, and then some document/recordset/bytes are returned, and we reconstitute that into a domain model, that we then turn around and convert into a json document that we send back over the wire as bytes.

CQRS is, in part, an answer to the question: can't we just bypass the entire model and copy the bytes that we need? Yes, sometimes that makes a lot of sense.