Add yaks 01 (before: fake EF DbBase inheritance) and 02 (after: DTOs + mappers) for the next agent

This commit is contained in:
2026-09-10 18:04:46 +01:00
parent 9d0079cbf0
commit 42b3c502a1
2 changed files with 153 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
# Yak 01 — C# solution: the BEFORE situation (DB-backed domain classes)
## Context
This exercise illustrates the transition from domain classes that inherit a DB
base class (EF-style, or active-record-like) to plain domain objects + DTOs.
See `before-after-dto.mmd` (and `before-after-dto-sketch.png`) for the diagram
this yak implements in C#:
- `DbContext` — the database context (EF fake)
- `DbBase` — base class with an `Id`; depends on `DbContext`
- `Client`, `Order` — domain classes **inheriting `DbBase`**
- `WebApp` — consumes the domain classes directly
Yak 02 will implement the "after" situation in the same solution.
## Tech constraints
- .NET SDK: target **net8.0** (adjust if only another SDK is installed; note it
in the commit message).
- **Fake the EF magic — do NOT add real EF Core.** The point of the exercise is
the *shape of the dependencies*, not EF's behavior. A fake `DbContext` keeps
the project pure managed code: no native binaries, no SQLite, no provider
quirks → guaranteed cross-platform (Windows/macOS/Linux).
- Tests: **xUnit**, one test project.
- Solution layout:
```
db-subclass-to-dto.sln
src/
Before/ (net8.0 class library, namespace Before.*)
After/ (net8.0 class library, namespace After.*) ← created in Yak 02
tests/
BeforeAfter.Tests/ (net8.0 xUnit project)
```
Yak 01 only creates the solution, the `Before/` project, and the test project
(plus an empty `After/` project so the solution shape is final).
## "Magic" to fake on DbBase / DbContext
Simulate EF's change tracker with a minimal fake:
- `DbContext` holds a registration dictionary of entities it knows about.
- `DbContext.Save()` iterates tracked `DbBase` entities: assigns a fresh `Guid`
`Id` to any entity whose `Id` is `Guid.Empty`, and records it.
- `DbBase` carries a back-reference to its owning `DbContext`
(active-record-style "I know which context created me") — this is the
dependency leak the exercise wants to expose.
- `Client` has an `Orders` collection; `Order` has a `Client` back-reference.
Adding an order to `client.Orders` sets `order.Client` (mimic EF navigation
fix-up).
- A `WebApp` type with methods that consume `Client`/`Order` directly,
including one that demonstrates the leak: reaching the `DbContext` *through*
a domain object (e.g. `client.DbContext`).
Keep the fake small; this is an illustration, not an ORM.
## Required tests (BeforeTests)
At minimum:
1. `Client` and `Order` derive from `DbBase`
(`typeof(DbBase).IsAssignableFrom(typeof(Client))` etc.).
2. New `Client`/`Order` instances have `Id == Guid.Empty`.
3. `DbContext.Save()` assigns fresh, distinct `Guid` ids to saved entities
(the faked EF magic).
4. Saved entities are registered with their `DbContext` (reachable via the
back-reference).
5. Relation fix-up: `client.Orders.Add(order)` sets `order.Client == client`.
6. The leak: from a bare `Client`, you can reach the `DbContext`
(`client.DbContext` is non-null after save) — assert this explicitly,
comment that this is exactly what the "after" situation removes.
7. `WebApp` methods work on the domain classes directly.
## Acceptance criteria
- `dotnet build db-subclass-to-dto.sln` succeeds with 0 warnings.
- `dotnet test db-subclass-to-dto.sln` succeeds; all tests pass.
- No NuGet packages beyond xUnit's default set (no EF Core, no SQLite,
no AutoMapper).
- Code + tests committed with a message referencing this yak.
## Open questions (answer before starting, defaults in parens)
1. Target framework net8.0 or something else? (net8.0)
2. Fake DbContext only, or do you also want a variant using real EF Core +
`Microsoft.Data.Sqlite` (bundled native lib — still no install needed on
Windows, but adds NuGet/ABI surface)? (fake only)
3. Any naming/style preference (file-scoped namespaces, records vs classes)?
(file-scoped namespaces; plain classes)