Standardise mapping (DTOs/contracts/persistence models) using source-generated mappers and explicit boundary conversions.
Standardise mapping between DTOs, domain models, and persistence models using source-generated mappers. Mappings live at explicit boundaries (API, infrastructure) with deterministic, testable conversion paths and no runtime reflection magic.
dotnet-domain-primitives).If a mapper is injected into a constructor (controller/handler/service), reviewers must check whether the mapper implementation itself has injected dependencies.
If the mapper has no injected dependencies (pure/stateless mapping), require refactor to a static mapper and remove DI registration.
Injection is permitted only when mapping depends on external collaborators and those collaborators are injected into the mapper.
"Inject for testability" is not sufficient when mapping is deterministic; test the mapper directly.
Reject PRs introducing runtime reflection mapping unless justified per dotnet-source-generation-first.
Require mapping tests for critical boundary conversions (typed IDs and value objects).
public class OrderMapperTests
{
[Fact]
public void ToEntity_WithValidDto_MapsAllProperties()
{
// Arrange
var dto = new CreateOrderRequest
{
CustomerId = "cust-123",
Amount = 99.99m,
Currency = "GBP"
};
// Act
var entity = OrderMapper.ToEntity(dto);
// Assert
entity.CustomerId.Value.Should().Be("cust-123");
entity.Amount.Value.Should().Be(99.99m);
entity.Amount.Currency.Should().Be(Currency.GBP);
}
[Fact]
public void ToDto_RoundTrip_PreservesData()
{
// Arrange
var original = new Order(
CustomerId.From("cust-123"),
Money.From(99.99m, Currency.GBP));
// Act
var dto = OrderMapper.ToDto(original);
var roundTripped = OrderMapper.ToEntity(dto);
// Assert
roundTripped.Should().BeEquivalentTo(original);
}
}
When reviewing mappers registered in DI:
These statements indicate mapping anti-patterns:
| Thought | Reality |
|---|---|
| "AutoMapper conventions will handle it" | Explicit mapping prevents runtime surprises; be deliberate |
| "Mapping logic belongs in the entity" | Keep entities clean; mappers handle boundary concerns |
| "Inject the mapper for testability" | Pure mappers don't need DI; test static mappers directly |
| "Runtime reflection is fine" | Source-generated mappers are faster and fail at compile time |
| "Implicit conversions are convenient" | Explicit conversions make boundary crossings visible |
| "Round-trip tests aren't worth it" | Round-trip tests catch subtle mapping bugs; always include them |