Implementing the Anti-Corruption layer using a well-known DDD pattern: Repository.
Today, it is hard to imagine writing an application without accessing some form of storage at runtime. This includes
not only writing application code but also deployment scripts, which often need to access configuration files, which
are also a type of storage in a sense. When developing applications to solve real-world business problems, connecting
to databases, external APIs, caching systems, or other forms of storage is practically unavoidable. It’s no surprise,
then, that Domain-Driven Design (DDD) includes patterns like the Repository pattern to address these needs. While DDD
didn’t invent the Repository pattern, it added more clarity and context to its usage.
The Anti-Corruption Layer
Domain-Driven Design (DDD) is a principle that can be applied to various aspects of software development and in
different parts of a software system. However, its primary focus is on the domain layer, which is where our core
business logic resides. While the Repository pattern is responsible for handling technical details related to
external data storage and doesn’t inherently belong to the business logic, there are situations where we need to
access the Repository from within the domain layer.
Since the domain layer is typically isolated from other layers and doesn’t directly communicate with them, we define
the Repository within the domain layer, but we define it as an interface. This interface serves as an abstraction
that allows us to interact with external data storage without tightly coupling the domain layer to specific technical
details or implementations.
The interface that defines method signatures within our domain layer is referred to as a “Contract.” In the example
provided, we have a simple Contract interface that specifies CRUD (Create, Read, Update, Delete) methods. By defining
the Repository as this interface, we can use it throughout the domain layer. The Repository interface always expects
and returns our Entities, such as Customer and Customers (collections with specific methods attached to them,
as defined in Go).
It’s important to note that the Entity Customer doesn’t contain any information about the underlying storage type,
such as Go tags for defining JSON structures, Gorm columns, or anything of that
sort. This kind of low-level storage configuration is typically handled in the infrastructure layer.
In the example above, you can observe a snippet of CustomerRepository implementation. Internally, it utilizes
Gorm for smoother integration, but you can also use pure SQL queries if preferred. Lately, I’ve been using the
Ent library extensively. In this example, you encounter two distinct structures: Customer
The first structure serves as an Entity, intended for housing our business logic, domain invariants, and rules.
It remains oblivious to the underlying database. The second structure functions as a
Data Access Objects (DAO, responsible
solely for mapping data to and from the storage system. This structure doesn’t have any other role aside from
facilitating the mapping of database data to our Entity.
The separation of these two structures is a fundamental aspect of using the Repository pattern as an Anti-Corruption
layer in our application. It ensures that technical details related to table structure don’t contaminate our business
logic. What are the implications of this approach? Firstly, it necessitates the management of two types of structures:
one for business logic and one for storage. Additionally, a third structure is often introduced, which serves as a
Data Transfer Objects (DTO
for our API. This approach introduces complexity into our application and entails the creation of multiple mapping
functions, as exemplified in the code snippet below. It’s essential to thoroughly test such methods to prevent common
However, despite the additional maintenance involved, this approach adds significant value to our codebase. It allows
us to represent our Entities within the domain layer in a manner that best encapsulates our business logic. We are
not restricted by the storage solution we employ. For instance, we can use one type of identifier within our business
logic (such as UUID) and a different one for the database (unsigned integer). This flexibility extends to any data we
wish to use for the database and business logic.
When modifications are made in either of these layers, it is likely that we will need to make corresponding adjustments
in mapping functions, while the rest of the layer remains untouched (or at least minimally impacted). We can opt to
switch to a different database system like MongoDB or Cassandra, or even switch to an external API, all without
affecting our domain layer.
The Repository primarily serves for querying purposes and integrates seamlessly with another DDD pattern known as
Specification, as you may have observed in the
examples. While it can be used without Specification, it often simplifies our workflow. The second key function of
the Repository is Persistence. It encompasses the logic for persisting our data in the underlying storage,
ensuring its permanence, facilitating updates, and even enabling deletion when necessary.
In some scenarios, we opt for generating unique identifiers within an application. In such cases, the Repository is
the appropriate location for this task. In the provided example, you can observe that we generate a new UUID before
creating the database record. We can employ a similar approach with integers if we aim to avoid relying on
auto-incrementing database keys. Regardless of the method chosen, when we prefer not to depend on database-generated
keys, it is advisable to create identifiers within the Repository.
Another important function of the Repository is managing transactions. When we need to persist data and perform
multiple queries that operate on the same extensive set of tables, it is a suitable situation to establish a
transaction, which should be managed within the Repository.
In the provided example, we are verifying the uniqueness of a Person or Company. If they already exist, we
return an error. All of these operations can be defined as part of a single transaction, and if any part of it fails,
we can roll it back. In this context, the Repository serves as an ideal location for such code. It’s worth noting
that, in the future, we might simplify our inserts to the extent that transactions are no longer required.
In that case, we won’t need to change the Repository’s contract, only the internal code.
Types of Repositories
It is a mistake to think that we should use the Repository pattern exclusively for databases. While we frequently
use it with databases since they are the primary choice for storage, alternative storage options have gained
popularity today. As previously mentioned, we can utilize MongoDB or Cassandra as alternatives. Repositories can
also be employed to manage our cache, where Redis, for instance, would be a suitable choice. Repositories can even
be applied to REST APIs or configuration files when necessary.
Now we can truly appreciate the advantage of separating our business logic from technical details. By maintaining
the same interface for our Repository, our domain layer remains unchanged. However, as our application expands, we
may find that MySQL is no longer the ideal solution for our distributed application. In the event of a migration, we
can transition without concern for how it will impact our business logic, as long as we maintain consistent interfaces.
Therefore, your Repository Contract should always revolve around your business logic, while your Repository
implementation can use internal structures that can later be mapped to Entities.
The Repository is a well-established pattern responsible for querying and persisting data in the underlying storage.
It serves as the primary point for Anti-Corruption within our application. We define it as a Contract within the
domain layer and house the actual implementation within the infrastructure layer. It is where we generate
application-specific identifiers and manage transactions.