The adapter, and DBO is not the VO
Persistence is the outer layer, and its whole job is boring on purpose. An adapter implements a port and does IO: it maps the domain object to a database row, and saves. It does not validate, calculate, or call domain factories. Boring is the goal; interesting persistence code usually means domain logic leaked outward.
public sealed class FlightNoteRepository : IFlightNoteRepository
{
private readonly FusionDbContext _dbContext;
public FlightNoteRepository(FusionDbContext dbContext) => _dbContext = dbContext;
[Trace]
public async Task SaveMany(IReadOnlyCollection<FlightNoteVo> notes, CancellationToken cancellationToken)
{
_dbContext.FlightNotes.AddRange(notes.Select(FlightNote.From));
await _dbContext.SaveChangesAsync(cancellationToken);
}
}The row it saves is a DBO (a database object), and this is a distinction Nest can blur: the DBO is NOT the value object. FlightNoteVo is the domain value object (behaviour, invariants, private ctor). FlightNote is a persistence record: EF attributes, an Id, audit columns, public setters. A mapper, FlightNote.From(vo), copies VO to DBO.
public static FlightNote From(FlightNoteVo note) =>
new()
{
Id = MonotoneUuidV7.Next(),
EjdpUniqueFlightKey = note.EjdpUniqueFlightKey,
Body = note.Body
// Audit fields intentionally omitted, filled by Postgres on INSERT.
};Try it yourself
Trace one note through the layers
Walk a single note from request to row and name the boundary each hop crosses. Use the real files.
- Start in
application/flight-notes/SaveFlightNote.cs: the use case builds aFlightNoteVoviaCreate(domain) - It hands the VO to
IFlightNoteRepository.SaveMany— a port it depends on but doesn't implement - Jump to
persistence/FlightNotes/FlightNoteRepository.cs: the adapter implements that port and maps VO → DBO viaFlightNote.From - Confirm
FlightNote.Fromcopies fields but never callsFlightNoteVo.Create— the domain factory stays in the domain (hard rule 5)
VO versus DBO
Two representations, know the difference.
What's the difference between FlightNoteVo and the FlightNote DBO, and what must the DBO's From mapper NOT do?
Reveal answer
FlightNoteVo is the domain value object: private ctor, Create factory, invariants, no persistence concerns. FlightNote is the DBO: an EF record with Id, audit columns, and public setters. From(vo) copies the VO's fields to a new DBO and must NOT call the domain Create factory or run any domain behaviour, because persistence never invokes domain logic (hard rule 5).
What the adapter may do
Which of these is legitimate work for a persistence adapter?
Show answer
Correct answer: C — Map the VO to a DBO and call SaveChangesAsync
Mapping VO → DBO and saving is the adapter's whole job. Validation (Create), calculation, and business errors all belong in the domain/application; persistence must never invoke domain behaviour (hard rule 5).