Reputation: 15807
Update
My research is telling me that I should be using a Data Mapper: https://martinfowler.com/eaaCatalog/dataMapper.html. Are Data Mappers injected into repositories like this: http://www.rantdriven.com/post/2009/09/01/Using-the-Repository-Pattern-with-the-Command-and-DataMapper-Patterns.aspx and this: Repository and Data Mapper pattern or are they used as an alternative to a repository? All the examples I find seem to use Data Mappers to map DataReader objects to lists of domain objects. I am wanting to map Persistent objects to Domain objects.
Original Question
I am trying to build a Domain Model that is completely isolated from the Data Model after reading articles like this: http://blog.sapiensworks.com/post/2012/04/07/Just-Stop-It!-The-Domain-Model-Is-Not-The-Persistence-Model.aspx. There are only six of us working on this system at the moment, however this could increase in future to 9+. However, I am beginning to think that this is not the right approach. I read a lot of questions on here, which appear to tell me to map the ORM directly to the Domain Model. I recently asked this question: https://softwareengineering.stackexchange.com/questions/365643/should-the-data-model-be-identical-to-the-domain-model-for-mapping-purposes. One of the answerers says: "I believe the mapping in between both should be within a (persistence-oriented) repository". How do you do this mapping? I don't believe I should be using AutoMapper in the repository because of the reasons stated here: Repository pattern and mapping between domain models and Entity Framework and here: http://enterprisecraftsmanship.com/2016/02/08/specification-pattern-c-implementation/ i.e. I cannot simply do this in the repository:
public Customer getId()
{
CustomerData customerData = customerRepository.getById(id);
return Mapper.Map<CustomerDomain>(customerData);
}
I cannot do this because the invariants of the domain object will not be considered. How can I return a domain object from the repository. Do I inject a factory into the repository, which will take parameters from the data model and return a domain model? Is this even the right approach or is there another pattern for mapping data objects to domain objects?
Upvotes: 3
Views: 1007
Reputation: 14064
ORM and Data Mapper
Most .NET ORM frameworks use a data mapper internally, or can be considered a kind of data mapper themselves. Some ORMs in other tech stacks may use the alternative approach to mapping: Active Record. The difference between the two is that in Active Record, the business class knows how to persist itself while a Data Mapper makes it agnostic from any persistence mechanism.
This is a different distinction than the one between having a Data Model + a Domain Model and mapping straight to the Domain Model. Regardless if you map directly to the Domain or if you have an intermediate Data Model, you will always have a Data Mapper. Both approaches imply it. The Data Mapper can be an ORM tool or custom code.
It's not clear from your question if you intend to use both an ORM and an additional Data Mapper on top, but I wouldn't recommend trying that or calling your Data Model <=> Domain Model mapping logic a "Data Mapper" in the PoEAA sense of the term.
Object hydration and invariants
Oftentimes, domain object hydration by a Data Mapper goes through another path than normal use-case driven code in order to bypass all validation that would otherwise happen. This can be done via parameterless constructors with restricted scope (protected
, internal
with internalsVisibleTo
) that the ORM or custom code will have access to. Some newer reflection-based frameworks can also access private fields.
With a Data Model in addition to your Domain Model, this is all simpler since you don't need to restrict access to a Data Model object the way you would a Domain entity. Data Models don't have invariants, you can safely leave their parameterless constructor and properties publicly accessible and settable for rehydration. The only thing you need to take care of is have some FromData()
method or constructor on your domain entity that takes a data model as an input and instantiates the entity. Again, the technique is made safe thanks to accessibility level restriction - the hydration code can typically be in an assembly granted internalsVisibleTo
by the domain.
Upvotes: 2
Reputation: 57249
Quick review of repositories, as defined by Evans
REPOSITORIES (provide) the means of finding and retrieving persistent objects while encapsulating the immense infrastructure involved.
(REPOSITORIES) provide the illusion of an in memory collection...
So, the API interface should normally be expressed in a domain specific vocabulary; neither the application layer nor the domain model should need to know any specifics beyond what is expressed in the API.
Commonly, the immense infrastructure means a domain agnostic persistence appliance; we don't persist domain objects, we persist bytes. With some appliances, we work very closely with the byte representation -- think streaming data to and from files. In other cases, the appliance provides a abstraction of those bytes - an RDBMS gives us an API that understands rows in tables, and encapsulates the details of how the bytes are arranged.
What this means is that somewhere we need a transformation from a domain agnostic representation to a domain specific representation, and vice versa.
Normally, these take the form of functions.
toDomainModel: JsonDocument -> DomainModel
toJson: DomainModel -> JsonDocument
These functions are normally defined by, and invoked by, the repository implementation -- they are part of the immense infrastructure that Evans describes.
I cannot do this because the invariants of the domain object will not be considered.
There are a couple possibilities here.
One, of course, would be to get a smarter mapper.
A second possibility is to model the unvalidated representation of the model as a distinct thing from a validated representation.
Example: consider a model for Money
, that requires an Amount
and a CurrencyCode
; and the semantics of your model requires that Amount
be a positive number and CurrencyCode
be an entry in a fixed collection of tokens. There's nothing wrong with having an UnvalidatedMoney
type, that has no semantic restrictions, and a function that converts UnvalidatedMoney
to Money
(enforcing the invariant).
This is analogous to what Scott Wlaschin describes for modeling a verified email address.
Note that this isn't necessarily an undue burden; if you have an invariant on some domain concept like money, you probably already have validation to do when passing new input from the world into your model. So the work is mostly about making that validation element re-usable.
A third possibility is to look into the Builder pattern. Persistent objects are, from one point of view, messages that a model wrote in the past so that it could read it in the future. So it is often the case that looking at common messaging patterns is useful.
In this approach, we would load an instance of the builder (created for us by a factory, implemented by the domain model), which has an API that allows the repository to pass the domain agnostic data to the domain model. Again, the message builder, being provided by the domain model, knows the domain invariant and can validate it.
All the validation for the domain object is done in the constructor. Should I just inject a factory into the repository?
That should be fine. You'll want to keep the coupling as small a you can manage (think interface), and you'll want to think about the fact that the factory is now part of the public API for the model (think about how to change the factory so that it remains backwards compatible).
Upvotes: 2