0

Assume an application architecture with three layers (presentation, domain, data access - though presentation is irrelevant to this question) that follows dependency inversion:

  • The domain layer specifies domain models and repository interfaces
  • The data access layer has a reference to the domain layer, implements repositories for a given storage provider, and uses data models internally
  • Repository interfaces use domain models, the domain layer does not reference the data access layer and is not aware of data models
  • The data access layer maps domain objects to data objects and the other way around

One goal of the architecture is to be able to exchange storage provider implementations by just reconfiguring the DI container, without any changes in the domain layer. The domain layer should not hold a reference to the data access layer.

My question: How do you deal with implementation details of specific data access providers that are required for the data layer to do its work correctly, without the domain layer having to know about data access implementations?

Example: Azure Cosmos DB uses ETags for optimistic concurrency control. When the data object is mapped to a domain object, the ETag is lost (because the domain model doesn't know about the ETag, it's an implementation detail of the data access layer). If the domain layer changes properties on the domain object and passes it back to the repository's update method, concurrency control is broken because the ETag value has been lost. Other storage providers use different mechanisms for concurrency control (like DB row locking). My goal is for the domain layer to not have to know about such implementation details.

1
  • 1
    Remember, if you don't construct your objects inside their consumers, but elsewhere (dependency injection), you can supply provider-specific details through the constructor (either directly or through factories that the constructor takes as a parameter), and then use the abstract interface in consumers. Commented Mar 16, 2022 at 0:02

1 Answer 1

2

For such cases, a typical solution is to to store provider specific information in a generic way inside or alongside your data objects.

For example, you may give your data object a generic Tag attribute of type "object", which is intended for such use cases. How your repositories will make use of this tag (or if they use it at all) is up to them. Your repo may decide to use this attribute for storing the ETag (whatever that exactly is). The data object itself still does not know the type and content of the Tag attribut, and it does not get coupled to the specific data provider, which is usually the goal you are trying to achieve.

In case you have objections to add such a generic, but technical attribute to your data objects, an alternative is to make your repository hold a dictionary, where the keys are (unique) IDs of objects they pulled from the storage provider, and the values are the specific information one wants to keep. That way, a repo can access the object-related data at a later point in time just by getting it from the dictionary. However, this kind of solution may result in a more complex life time management for the values in the dictionary, since the repo might not get automatically informed when the object is not longer needed or disposed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.