Eleven Claims, and Most of Them True
Four articles of defects earn an audit. Inflow's README makes fifteen checkable claims and eleven of them hold - including the strongest one, checked edge by edge across all twenty project files. Here is the ledger, and why a short README is so hard to falsify.
Four articles into a series about a repository's documentation and I have written about a broken request file, an undocumented happy path, a route that never existed and an escalation path with a caption. That is a pattern, and left there it would be an unfair one. So this part does the opposite exercise: take the README as a set of assertions, check each against the code, and publish the score whichever way it goes.
Inflow's README is sixty-five lines. I extracted fifteen claims that the repository can settle, plus two it cannot. Eleven of the fifteen hold, including the strongest and least likely one.
Method
A claim counts as checkable if reading this repository can make it true or false. “Comprehensive course” is marketing; skip. “There's no reference between the modules at all” is a statement about twenty .csproj files; check it.
Where a claim is settled by a search, I state the search. Where it is settled by reading files, I read all of them rather than a sample — for the reference graph that meant all twenty project files, every ProjectReference element, no exceptions.
The ledger
| # | Claim | Verdict |
|---|---|---|
| 1 | Each module is an independent vertical slice with its custom architecture | True |
| 2 | Integration between modules is mostly event-driven | True |
| 3 | docker-compose up -d starts the infrastructure — only PostgreSQL |
True |
| 4 | cd src/Bootstrapper/Inflow.Bootstrapper then dotnet run starts the API |
True |
| 5 | The Bootstrapper initialises the modules — loads configuration, runs DB migrations, exposes public APIs | True |
| 6 | There is no reference between the modules at all | True |
| 7 | Synchronous communication and asynchronous integration are based on local contracts | True |
| 8 | Customers — create, complete, verify, browse | True |
| 9 | Wallets — virtual wallets and money transfers between them | True |
| 10 | Users — register, login, permissions | True |
| 11 | The module-to-microservice transition is on the microservices branch |
True |
| 12 | Payments — deposits and withdrawals to and from an actual bank account | False |
| 13 | Shared is split so that Abstractions contains public abstractions and Infrastructure their implementation | False |
| 14 | Each module contains its own .rest file |
Contested |
| 15 | The Saga handles business processes transactionally across modules | Contested |
Two further claims are unverifiable from this estate and I record them as such rather than counting them: that the shared components “can be also found in a modular framework”, and that the finished microservices solution lives in the Inflow-micro repository. Both are statements about other repositories.
The strongest claim, checked edge by edge
Claim 6 is the one the whole project stands on:
Autonomous modules with the different set of responsibilities, highly decoupled from each other - there's no reference between the modules at all (such as shared projects for the common data contracts)
Absolute claims like this are usually false. “No X at all” survives a code review and dies quietly eighteen months later when somebody needs one DTO. I extracted every ProjectReference from all twenty .csproj files on master — thirty-four edges — and drew the graph:
Shared.Abstractions -> (nothing)
Shared.Infrastructure -> Shared.Abstractions
Users.Core -> Shared.Infrastructure
Users.Api -> Users.Core
Customers.Core -> Shared.Infrastructure
Customers.Api -> Customers.Core
Payments.Shared -> Shared.Abstractions
Payments.Core -> Shared.Infrastructure, Payments.Shared
Payments.Api -> Payments.Core
Wallets.Core -> Shared.Abstractions
Wallets.Application -> Shared.Abstractions, Wallets.Core
Wallets.Infrastructure -> Shared.Infrastructure, Wallets.Application
Wallets.Api -> Shared.Infrastructure, Wallets.Infrastructure
Saga.Api -> Shared.Infrastructure
Bootstrapper -> Customers.Api, Payments.Api, Saga.Api,
Wallets.Api, Users.Api,
Shared.Abstractions, Shared.Infrastructure
Not one edge crosses from one module to another. Every module reaches downward into Shared or sideways within itself; the Bootstrapper is the only fan-in point, and it references the five .Api projects and nothing deeper. The parenthetical is honoured too — there is no shared contracts project, because contracts are re-declared locally in each consuming module and reconciled at boot. Even Inflow.Modules.Payments.Shared, the one project whose name would let you smuggle a cross-module dependency in, is scoped strictly inside Payments and references only Shared.Abstractions.
The claim is literally true, in the strictest reading, verified exhaustively. That is rare, and it deserves to be said as loudly as anything else in this series.
Claim 1 holds the same way. Users is two layers with entities and DAL side by side; Customers is two layers with an internal Domain/{Entities,Repositories,ValueObjects} split; Payments is three projects with Core divided by aggregate; Wallets is a full onion with Core, Application and Infrastructure and genuine dependency inversion. Four modules, four architectures, in one host, on purpose — which is also, as part 3 showed, why GET /payments returns a 404.
Claim 5 is three sub-claims and all three land. ConfigureModules() in Program.cs recursively scans the content root for module.*.json and layers module.*.{Environment}.json on top. DbContextAppInitializer finds every DbContext in the app domain by reflection and calls Database.MigrateAsync on each, then runs every registered IInitializer. And AddModularInfrastructure registers the module assemblies as MVC application parts, which is what makes the modules' controllers routable from a host project that references only five .Api assemblies.
Claim 2 deserves credit for the word mostly. The four business modules hold thirteen handlers under Events/External/, plus the Saga's four-event handler, against exactly two synchronous request paths — customers/get and users/get, dispatched through IModuleClient. “Mostly” is precisely the right quantifier for that ratio. Documentation that qualifies itself accurately is rarer than documentation that is right.
The two that fail
Claim 12 — "Payments - managing the money deposits & withdrawals (to/from actual bank account)". There is no bank. Deposit completion is this:
private static (bool isCompleted, IEvent @event) TryComplete(Deposit deposit, string secret)
{
// This could be refactored to an application service with checksum validation etc.
return secret == "secret"
? (true, new DepositCompleted(...))
: (false, new DepositRejected(...));
}
A string comparison against the literal "secret". And in fairness this is the best-behaved simplification in the estate — it is the only one anywhere with a comment saying what it stands in for. The claim is false about the code and true about the domain being modelled, and the code says so out loud. I would fix the sentence, not the handler.
Claim 13 — “it's split into the separate Abstractions and Infrastructure, where the former does contain public abstractions and the latter their implementation”. Of the 53 files in Inflow.Shared.Abstractions, 25 declare a concrete type: Amount, Currency, Email, FullName, Nationality, AggregateRoot, AggregateId, EntityId, TypeId, Paged, PagedQuery, JsonWebToken, ExceptionResponse, InflowException, seven kernel exception types, and an Extensions static class. That is a shared kernel, not an abstractions package, and the two are different things with different coupling consequences.
The .csproj makes it sharper:
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
The project marketed as the pure abstraction seam takes a framework reference on the whole of ASP.NET Core, so it cannot be consumed by a non-web host — a worker service, a console tool, a test rig. Whether that matters here is arguable; whether the sentence describes it is not.
The two contested
Claim 14 — "Each module contains its own HTTP requests definitions file (.rest)." Four of the five project folders under src/Modules/ have one; Saga does not. But the sentence sits directly under the Modules heading, after the four-item list of Customers, Payments, Wallets and Users, and before the separate Saga heading. Scoped to the four it names, it is true. Scoped to src/Modules/, it is false. I would not file this as a defect, and I would not call it clean either.
Claim 15 — "Sample Saga pattern implementation for transactional handling the business processes spanning across the distinct modules." The saga does span modules; NewCustomerBonusFundsSaga starts on CustomerVerified from Customers and ends on FundsAdded from Wallets, with Payments in the middle. “Transactional”, though, is doing work the code does not do — every one of its four CompensateAsync methods is => Task.CompletedTask. The companion series Five Modules, One Database Each takes that apart properly; here it is enough to say the noun is right and the adjective is optimistic.
Why the score is this high
Eleven of fifteen is a good result and I want to be honest about the reason, because it is not entirely a compliment.
This README is accurate largely because it is short. Sixty-five lines, of which roughly fifteen are operating instructions and roughly fifteen are links out. A document that small has very little surface to drift against. It never describes a request flow, so it cannot describe one wrongly. It never lists endpoints, so it cannot list a stale one. It makes six architectural assertions and they are all assertions the author could hold in his head while writing the code.
Everything detailed lives somewhere else. The estate's only architecture diagram is a PNG on a CDN — outside version control, undiffable, unreviewable, replaceable without a commit. The explanations are on a knowledge-base site, a YouTube playlist and a paid course. README.md is the only Markdown file in the entire repository, on every branch; there are no ADRs, no docs/ folder, no decision log, no changelog.
So the finding is not that the documentation is inaccurate. It is that there is not enough documentation in this repository for inaccuracy to be possible — and the twenty per cent that misses is the twenty per cent that reaches furthest past what the sixty-five lines can carry.
Which raises an obvious question: if the intent was never written down here, where did it go? It went into a room, on a Saturday. Next, an afternoon in March.