Domain Driven Design Context Mapping Created: 10 Jan 2026 Updated: 10 Jan 2026

Anti-Corruption Layer (ACL)

In Domain-Driven Design (DDD), an Anti-Corruption Layer (ACL) is a strategic pattern that acts as a mediator or "translator" between a Downstream Bounded Context and an Upstream context (often a legacy system or a third-party API).

Unlike the Conformist pattern, where the Downstream team simply accepts the Upstream's model, the ACL ensures that the Downstream's internal Domain Model remains "pure" and unpolluted by external concepts.

The "Why": Why build an ACL?

If you are building a modern system that needs to talk to a messy legacy database or a poorly designed third-party API, you don't want those external "ugly" names and structures (e.g., TBL_USR_01_REC) leaking into your beautiful, clean Domain Model.

The ACL protects your Ubiquitous Language. It translates the external "junk" into your internal "clean" terms.

PatternMental Model
Conformist"I will speak your language, even if it's messy."
ACL"I have my own language. I will translate yours into mine so my logic stays clean."

Components of an ACL

An ACL is typically composed of three parts:

  1. Service/Interface: The entry point for your domain to request data.
  2. Adapter: Wraps the external system's technical implementation (e.g., making the actual HTTP/SOAP call).
  3. Translator (Mapper): Maps the external data structure to your internal Domain Entities or Value Objects.

Cinema Example: Legacy Integration

Imagine your new Cinema Booking Context needs to get movie ratings from a Legacy Rating System built in the 90s.

  1. Legacy Model: Movie_Rec { M_ID, RTNG_VAL, CAT_TYPE }
  2. Your Modern Model: MovieRating { MovieId, Score, Category }

.NET Core Implementation

1. Your Clean Domain Model

namespace Cinema.Booking.Domain;

// This is our clean Ubiquitous Language
public record MovieRating(Guid MovieId, decimal Score, string Category);

2. The ACL Interface

namespace Cinema.Booking.Application;

public interface IMovieRatingAcl
{
Task<MovieRating> GetRatingAsync(Guid movieId);
}

3. The Translator (Mapper)

internal class RatingTranslator
{
public MovieRating ToDomain(LegacyRatingResponse legacy)
{
// Translation logic happens here
return new MovieRating(
Guid.Parse(legacy.M_ID),
legacy.RTNG_VAL / 10m, // Normalizing scale
legacy.CAT_TYPE == "A" ? "Premium" : "Standard"
);
}
}

4. The ACL Implementation (Adapter + Translator)

internal class LegacyRatingAcl : IMovieRatingAcl
{
private readonly ILegacyApiClient _client;
private readonly RatingTranslator _translator;

public async Task<MovieRating> GetRatingAsync(Guid movieId)
{
// 1. Call the "dirty" upstream system
var legacyData = await _client.FetchRecord(movieId.ToString());

// 2. Translate to our "clean" downstream model
return _translator.ToDomain(legacyData);
}
}

Pros and Cons

AdvantagesDisadvantages
Domain Isolation: Your core logic stays clean and focused on your business.Extra Effort: You have to write and maintain mapping code.
Easy Refactoring: If the external system changes, you only update the ACL, not your whole domain.Performance: Small overhead for the translation/mapping layer.
Autonomy: You define your own language regardless of external constraints.Complexity: Adds more classes and abstractions to the project.
Share this lesson: