Three Boxes That Were Never Built
Three of the ten contexts on the context map have no code behind them - and reading the absences carefully separates a design placeholder from a forgotten promise.
The hardest thing to see in a codebase is what is not in it. A missing index announces itself in a slow query; a missing bounded context announces itself never, because the diagram that promises it renders identically whether the code exists or not. Diagrams have no compile step. That is their whole appeal and their whole problem.
Part 4 derived GroupFlights' real context map from the project graph and diffed it against ADR 03's drawing. Four of nine edges matched exactly. This part is about the boxes rather than the edges — specifically, the three that produced no edges at all, because there is nothing behind them.
Counting the boxes
The drawn map has twelve shapes: ten rounded boxes for bounded contexts and two gear icons for external systems.
| On the map | In src/ |
Status |
|---|---|---|
| Sales | Sales/ — 6 projects, 5,056 LOC |
built |
| Post-Sales | Postsale/ — 6 projects, 3,548 LOC |
built |
| Inquiries | Inquiries/ — 2 projects |
built |
| Time Management | TimeManagement/ — 3 projects |
built |
| Finance | Finance/ — 3 projects |
built |
| Workload Management | WorkloadManagement/ — 3 projects |
built |
| Backoffice | Backoffice/ — 3 projects |
built |
| Communication | Communication/ — 3 projects, 106 LOC |
stub |
| Customer Service | — | not built |
| IAM | — | not built |
| Payment Gate (external) | _ExternalSystems/FakePaymentGateway |
faked, deliberately |
| Email Service (external) | — | not built |
Seven of ten contexts are real. One is a stub. Two do not exist in any form — no project, no namespace, no interface, no //TODO. And one of the two external systems, the one the map protects with an anti-corruption layer, was never wired to anything.
The two missing contexts are not accidents of scope. Both appear on the map with a specific relationship label. Customer Service and IAM each carry a red CONFORMS line into Sales, alongside Backoffice's. That is a design statement: when these contexts arrive, they will adopt Sales' model wholesale rather than negotiate. Whoever drew that line had thought about it.
The stub that is honest and the ACL that is not
Communication is the interesting middle case. It exists — Communication.Api, Communication.Core, Communication.Shared, 106 lines total — and it holds one of the estate's most-referenced published types, the Message record that four other modules build. It has no database schema and no endpoints. Its entire implementation is this:
public Task SendMessageToRegisteredUser(UserId userId, CommunicationChannel channel, Message message,
CancellationToken cancellationToken)
{
_logger.LogInformation(
$@"Sent message:
{message.Content}
to user: {userId}
through channel: {channel.ToString()}");
return Task.CompletedTask;
}
Communication.Core/ModuleApi/CommunicationApi.cs:17-27, GroupFlights at commit a19b337.
This is a declared omission and therefore not a defect. README bullet eight says it outright: the system implements no e-mail gateway and no notifications, only an example of sending e-mail by writing the message to the console. A reader following the fourteen-step walkthrough sees “Sent message: …” scroll past in the terminal and understands that a notification happened. The stub exists to make a mechanism visible, which is the highest use of a stub in teaching code.
What is not declared is the shape of the missing thing. The map draws an ACL between Communication and Email Service, and an anti-corruption layer is a structure, not an intention: a port owned by the downstream context, an adapter that translates the vendor's model into it, and a boundary type that never leaks. Finance builds exactly that against the payment gateway — IPaymentGatewayFacade is the port, FakePaymentGatewayFacade is the adapter, and SetUpPaymentWithMetadata is deliberately duplicated on both sides of the process boundary so that neither side owns the other's model.
Communication has none of it. No IEmailGateway, no adapter project, no vendor DTO. When a real provider arrives, the ACL will be designed then, by whoever is holding the ticket, with the map's promise as their only guidance. Contrast that with the payment side, where the seam already exists and swapping the fake gateway for a real one means writing one adapter class.
The lesson generalises past this repository. A stub behind a port is a decision; a stub with no port is a to-do item wearing a decision's clothes. If your architecture diagram promises an anti-corruption layer, the cheapest possible honouring of that promise is the interface — even with one implementation that logs. It costs an afternoon and it converts a drawing into a compile-checked commitment.
Reading absences without over-reading them
There is a failure mode on the other side of this analysis, and it is worth naming because it is the more common one among people who enjoy finding gaps. Not every empty box is a broken promise.
IAM is the clearest example. GroupFlights declares, in README bullet six, that it implements neither authentication nor authorisation, and that a naive imitation based on the X-UserId and X-CashierId headers stands in for both. The IAM box on the map is therefore consistent with the README: the context is drawn because a real system would have one, and it is not built because the README says the system does not do that. The relationship label even survives the omission — a future IAM would be a Conformist, adopting Sales' notion of a user identity, and the naive header-based context accessor is a placeholder that behaves exactly that way.
Customer Service is weaker. Nothing in the README mentions it, no ADR discusses it, and no code alludes to it. It is a box that came out of the modelling session and stayed on the board. That is completely normal — a context map produced during discovery should contain contexts you have not built, because that is half of what strategic design is for. The map is a picture of the target state, not the current one.
The problem is that nothing on the artefact distinguishes the two. Sales, which is 5,056 lines of code, and Customer Service, which is an idea, are rendered as the same grey rounded rectangle. Anyone reading the map without the README beside them will assume both exist. A target-state map and a current-state map are different documents, and this one is trying to be both. The fix is unglamorous and free: a legend, a dashed border for “not built yet”, a date in the corner. This series' part 15 is about exactly that class of problem — design records with no lifecycle metadata — and the three empty boxes are its sharpest example.
The fossils in the solution file
One more absence, and this one is a fossil rather than a gap.
GroupFlights.sln declares solution folders named Offers and Reservations, pointing at src\Offers and src\Reservations. Neither directory exists on disk. Solution folders are virtual — Visual Studio does not require the path to resolve, and these two hold the .http scenario files that physically live under src/_Scenarios/Offers and src/_Scenarios/Reservations — so nothing is broken and nothing fails to build. But the names are a trace.
Offers and Reservations are the two aggregate families inside the Sales module. They have their own folders under Sales.Domain, their own repositories, their own factories, and their own .http scenario sets. At some point in the modelling process they were plausibly candidates for their own contexts, and the solution structure still carries their shape at the top level next to Sales, Finance and Postsale. The course this repository accompanies teaches subdomain distillation and bounded-context design as a process, and consolidating two candidate contexts into one is a normal, healthy outcome of that process.
I like that the fossil survived. A repository that shows only the final structure teaches you the answer; a repository that leaves a trace of the discarded structure teaches you that there was a decision. The estate does this in one other place too — src\Tests appears three times as a solution folder and does not exist on disk either, which is the residue of a test layout that was reorganised into per-module *.UnitTests projects.
Absence, in short, comes in at least four flavours here: declared (Communication's email gateway, IAM's authentication), drawn but unbuilt (Customer Service), structurally unhonoured (the missing ACL port), and fossilised (the solution folders). Only the third is a finding you would act on. Sorting them correctly is most of the skill, and it is the difference between an architecture review and a list of complaints.
Next, the promise that was kept: a gentleman's agreement across thirty-five projects, where ADR 01 chose verbal enforcement over an automated test and got away with it.