Cautions applying Repository and Unit of Work Patterns with Dependency Injection in Domain-Driven Design!

And what they are supposed to do…

Icaro Torres
thecodemonks
8 min readJan 26, 2020

--

Photo by Christina Morillo from Pexels

Introduction

Software Engineering is a vast knowledge area, always moving forward. However, developing good software is an arduous task until now. Furthermore, to achieve software of quality easier, engineers keep investigating common problems to design patterns solving then. As results, we have many known Design Patterns and Software Development Principles to make developers focus as much as possible on the client’s needs.

On the other hand, these principles and patterns are not well comprehended and applied by developers at the very beginning of their career, and even experienced ones can sometimes misuse or wrongly pick up some of those patterns.

This article argues three popular patterns often present in .Net solutions with Domain-Driven Design which I’ve seen misunderstood. Showing also, what these patterns solve, how wrong they happened, and how to fix something for the implementation to make sense.

SOLID Principles

You can read this Post by Simon LH explaining way more about SOLID with code examples.

It is an acronym for five important design principles introduced by Robert C. Martin (Uncle Bob) and updated later by Michael Feathers, listed below with definitions from Simon LH’s post:

S — Single responsibility Principle

“In programming, the Single Responsibility Principle states that every module or class should have responsibility for a single part of the functionality provided by the software.”

O — Open-Closed Principle

“In programming, the open/closed principle states that software entities (classes, modules, functions) should be open for extensions, but closed for modification.”

L — Liskov Substitution Principle

“In programming, the Liskov substitution principle states that if S is a subtype of T, then objects of type T may be replaced (or substituted) with objects of type S.

We can formulate this mathematically as

Let ϕ(x) be a property provable about objects x of type T.

Then ϕ(y) should be valid for objects y of type S, where S is a subtype of T.

More generally, it states that objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”

I — Interface Segregation Principle

“In programming, the interface segregation principle states not to force a client to depend on methods it does not use.

Put more simply: Do not add additional functionality to an existing interface by adding new methods.

Instead, create a new interface and let your class implement multiple interfaces if needed.”

D — Dependency Inversion Principle (Dependency injection)

“In programming, the dependency inversion principle is a way to decouple software modules.

This principle states that

* High-level modules should not depend on low-level modules. Both should depend on abstractions.

* Abstractions should not depend on details. Details should depend on abstractions.

To comply with this principle, we need to use a design pattern known as a dependency inversion pattern, most often solved by using dependency injection.”

These principles save us from lots of headaches in software development and are always suitable for fulfilling as much as possible of then in Object-Oriented Programming paradigm.

For this article, we only focus on the last one.

Dependency Injection Pattern

If you are adept to TDD (Test-Driven Development), or as long as you may want to make your code testable, you’ve heard about this pattern.

Concept and benefits

It consists of developing concrete implementations around contract signatures of interfaces, abstracting the real features, allowing alternative behaviours depending on what was injected and making code replaceable by testing mocks.

It provides testability, re-usability and decouples feature’s code from where uses It.

What can a lousy Dependency Injection cause?

Making a wrong configuration of your dependency injection container can cause:

  • Wrongly disposed structures;
  • Heavy load of unused instances;
  • Miss-injected behaviours;
  • Unnecessary complexity;
  • Infinity loops in the container initialization (if there are any cycling dependencies between injected types).

Repository Pattern

This pattern shows off with Object-relational mapping technologies (ORM) and data access layer abstractions over databases. It consists of managing data sources (mostly database tables or contents from web services resources) as in-memory collections of domain objects and to perform operations over It in a more straightforward way than mounting SQL queries or writing HTTP requests by hand whenever data was needed.

What to expect?

What we expect from a repository following this pattern is CRUD operations of the referred abstracted entity source and maybe a variety of specific methods (like filtering and joining a subject table with Its relationships) to bring ease usage of this repository.

A repository of this pattern works as a gatherer of data about a domain object and should only make changes in the referred source and data related to It, like a table and tables in relationships.

It’s preferred to reuse your base repository logic through composition instead of inheritance. Doing this allows the encapsulation of methods from your base implementation and can avoid developers to make calls of unwanted base operations like removing objects which should not allow deletion.

The example following can illustrate this pattern in .Net Core:

namespace Domain.Repositories
{
public interface IInvestorRepository
{
Investor GetWithKeys(long id);
IEnumerable<Investor> GetAll(bool readOnly = false);
IEnumerable<Investor> Query(Expression<Func<Investor, bool>> predicate = null, bool readOnly = false);
Investor Insert(Investor entity);
IEnumerable<Investor> InsertMany(IEnumerable<Investor> entities);
}
}

namespace Data.Repositories
{
public interface IRepository<T> where T : BaseEntity
{
T GetWithKeys(params object[] keys);
IQueryable<T> GetAll(bool readOnly = false);
IQueryable<T> Query(Expression<Func<T, bool>> predicate = null, bool readOnly = false);
IQueryable<S> Query<S>(Expression<Func<S, bool>> predicate = null, bool readOnly = false) where S : T;
T Insert(T entity);
IEnumerable<T> InsertMany(IEnumerable<T> entities);
T Remove(T entity);
T Remove(params object[] keys);
IEnumerable<T> RemoveMany(IEnumerable<T> entities);
IEnumerable<T> RemoveMany(IEnumerable<object[]> keys);
}

public class InvestorRepository : IInvestorRepository
{
private readonly IRepository<Investor> _baseRepository;

public InvestorRepository(IRepository<Investor> baseRepo)
{
_baseRepository = baseRepo;
}

public Investor GetWithKeys(long id) => _baseRepository.GetWithKeys(id);

public IEnumerable<Investor> GetAll(bool readOnly = false) => _baseRepository.GetAll(readOnly).ToList();

public IEnumerable<Investor> Query(Expression<Func<Investor, bool>> predicate = null, bool readOnly = false)
{
return _baseRepository.Query(predicate, readOnly, included).ToList();
}

public Investor Insert(Investor enterprise) => _baseRepository.Insert(enterprise);

public IEnumerable<Investor> InsertMany(IEnumerable<Investor> enterprises) => _baseRepository.InsertMany(enterprises);
}

A Base Repository interface, a specific repository interface encapsulating exclusion methods from base interface, and a concrete repository made by composition instead of inheritance.

What should a Repository Pattern Implementation never do?

Knowing the pattern’s unique and straightforward responsibility of abstracting real data sources (example: databases) into collections of in-memory data, and keeping It doing only and exactly this, there are things to avoid entirely on the implementation to prevent an unmaintainable code, as listed below:

  • Messing with data from outside of Its intended source table and relationship — as a violation of the Single Responsibility Principle, generating side-effects;
  • Usage of other repositories within the implementation — coupling treatment of unrelated tables, misplacing program logic and also violating the Open-closed and Single Responsibility Principle;
  • Exposing the real source of data (example: DbContexts in Entity Framework and Sessions in NHibernate) to access from out of Its inheritance, which sooner or later would result in poorly made code violating this very pattern;
  • Commit and Rollback methods — as a common mistake while applying this pattern, forgetting to keep this for example to Unit of Work implementations, to avoid unclear code and too many database round-trips as long as repositories are not supposed to handle transactions and finish them.

Also, there are other lesser evils to avoid too, but for now, let’s take only the above list before showing the real threat which motivates this article.

Unit of Work

Another Enterprise Pattern widely used together with the foretold one intended to manage transactions and control the submission of commands to real databases. If made wrong, It can quickly become a piece of disaster together with repositories and be kindly called Unit of Rework due to massive violation of other principles like DRY.

Again, this pattern has one job and is all about handle database connections to manage transactions running, so any other responsibility put inhere needlessly misplaced is also violations of software principles.

A base interface of Unit Of Work is short, and the concrete logic depends on your infrastructure set of technologies and packages, often having a signature with methods to do the following:

  • Begin a transaction;
  • Save a state within a transaction;
  • Rollback a state within a transaction;
  • Commit and End a transaction;
  • Rollback the transaction.

We can sometimes see this pattern implementations inheriting from a base one and holding references to repositories, to gather the whole group of sources used in processes of that unit as a single dependency to pass on services resolving It’s logic.

The gist below represents an example of these structures:

namespace Domain.Unities
{
public interface IUnitOfWork : IDisposable
{
/// <summary>
/// start a transaction with the context
/// </summary>
IUnitOfWork Begin();

/// <summary>
/// Save state of changes in the context.
/// </summary>
void Save();

/// <summary>
/// Rollback all states saved under the transaction.
/// </summary>
void RollbackStates();

/// <summary>
/// Confirm all changes under transaction.
/// </summary>
void Commit();

/// <summary>
/// Rollback all changes under the transaction.
/// </summary>
void RollbackTransaction();
}

public interface IUnitOfEnterprises : IUnitOfWork
{
public IEnterpriseRepository Enterprises { get; }
public IInvestorRepository Investors { get; }
public IEnterpriseTypeRepository Types { get; }
}
}

Example of a Unit of Work base Interface Signature and a derived Unit holding repositories

Under this pattern, let’s list a bit of thing to be aware:

  • Do not confuse UnitOfWork as a repository factory;
  • In Microsoft.Net platforms, do not forget to implement IDisposable interface to let the garbage collector get rid of It after Its lifetime (a request scope for example) and free tons of memory of your servers;
  • If holding references to repositories, return interfaces and do not create concrete instances manually to allow developers to mock dependencies and repositories returned;
  • Do not make queries or call methods from repositories to return data from your units of work code;
  • Derive from a Base Unit of Work interface if you want to represent a specific group of processes as a different Unit of Work.
  • Do not expose the real source of data (example: DbContexts in Entity Framework and Sessions in NHibernate) to access from out of Its inheritance, which leads to disaster letting developers control transactions outside your units manually.

On Microsoft.Net platforms is also possible to efficiently use Unit Of Work inside Decorator Attributes over Controller Actions of APIs or MVCs projects allowing developers to do more straightforward logic and less code.

To do so, inside your Decorator Attribute, you can get the Unit Of Work instance as a service within the scope created in the request, through HttpContext like the demonstrated following:

namespace API.FilterAttributes
{
public class UnitOfWorkAttribute : Attribute, IActionFilter
{
public IUnitOfEnterprises UnitOfWork { get; private set; }

public void OnActionExecuting(ActionExecutingContext context)
{
UnitOfWork = context.HttpContext.RequestServices.GetService<IUnitOfEnterprises>();

UnitOfWork.Begin();
}

public void OnActionExecuted(ActionExecutedContext context)
{
UnitOfWork ??= context.HttpContext.RequestServices.GetService<IUnitOfEnterprises>();

if (context.Exception == null)
{
UnitOfWork.Commit();
}
else
{
UnitOfWork.RollbackTransaction();
}
}
}
}

namespace API.Controllers
{
[ApiController, Route("api/v1/[controller]"), Produces("application/json")]
[ValidateModelState]
public class EnterprisesController : ControllerBase
{
private readonly IEnterpriseFacade _service;

public EnterprisesController(IEnterpriseFacade service) => _service = service;

[HttpGet]
public IActionResult Get([FromQuery]EnterpriseIndexFilterInput input) => Ok(_service.ListEnterprises(input));

[HttpGet("{id}")]
public IActionResult Get(long id) => Ok(_service.GetEnterprise(id));

[HttpPost, UnitOfWork]
public IActionResult Post([FromBody] CreateEnterpriseInput input)
{
var enterprise = _service.CreateEnterprise(input);

return CreatedAtAction($"{nameof(Get)}", new { id = enterprise.Enterprise.Id }, enterprise);
}

[HttpPut("{id}"), UnitOfWork]
public IActionResult Put(long id, [FromBody] UpdateEnterpriseInput input) => Ok(_service.UpdateEnterprise(id, input));

[HttpDelete("{id}"), UnitOfWork]
public IActionResult Delete(long id)
{
_service.DeleteEnterprise(id);

return NoContent();
}
}
}

Unit of Work usage within a decorator Action Filter Attribute in .Net Core API Controller

Conclusion

This post represents my experience seeing these concepts misunderstood out there, what I’ve learned facing these problems and also my contribution as a way to help those who still face situations working with implementations as fore described.

Hope to help you and your co-workers in this journey, and save you time maintaining messy code often present in legacy projects.

Feel free to comment, disagree, contribute and help in the discussion, always in a friendly way.

--

--

Icaro Torres
thecodemonks

Software Engineer since 2016. Love music, anime, learning and challenges.