Skip to content
kc@kumarChandrachooda.com:~$ cd /blog/nine-repos-and-no-map && read --section="top" 0%
Microservices

Nine Repos and No Map

The DevMentors Trill estate is a nine-service microservices sample spread over eleven sibling repositories with no README, no clone script and no bootstrap tooling - and the only artefact that names the whole system is a Visual Studio solution file.

By Kumar Chandrachooda 09 Dec 2025 7 min read
Nine scattered repository squares beside one empty frame where the map should be

You clone a microservices sample expecting to learn something and the first thing you learn is that you cannot start it. The repository you landed on has four files in it. Its README.md contains one word. The solution file opens with twenty-seven missing-project errors, because every project it references lives in a different repository that nothing told you to clone.

That is the honest first ten minutes with Trill, the DevMentors microservices estate — MIT-licensed, © DevMentors (Piotr Gankiewicz and Dariusz Pawlukiewicz), eleven sibling repositories built in late 2020 and frozen since August 2021. It is a Twitter-shaped teaching system: stories, users, ads, analytics, timelines, a gateway, a push service, a saga, a Blazor client, and — this is the part that makes the estate genuinely unusual — a second complete implementation of the same product as a modular monolith.

To be clear up front: I did not write Trill. I am a source-reader. Everything I quote is real code from these repositories, cited by file and line, and everything I criticise I have opened and confirmed. The register matters because this is teaching code by capable authors, and reading it fairly means naming what it gets right as loudly as what it gets wrong. Trill is also a consumer of Convey, the DevMentors service chassis this corpus has already read end to end; where the framework itself is the subject I will point at that series rather than re-derive it.

The estate, counted

Eleven repositories sit as siblings under one parent directory. Ten of them were git init-ed on 2020-10-17 inside a four-minute window and mass-migrated to net5.0 on 2021-04-22 inside a nine-minute window. Each carries between two and four commits, and the commit messages, in full, are: init, updated prometheus, Tye, Docker compose update, decorators, net5.0 update, Readme, refactoring, domain, cleanup.

Repository Role
Trill Umbrella: services.yml, tye.yaml, compose\, Trill.sln. No README, no LICENSE
Trill.APIGateway YARP reverse proxy plus a hand-rolled HTTP-to-AMQP bridge
Trill.Pusher gRPC server-streaming push, bus to socket
Trill.Saga Chronicle process manager for the ad-publication flow
Trill.Services.Stories The core domain, four layers
Trill.Services.Users Identity, funds, refresh tokens
Trill.Services.Ads Paid highlighted stories
Trill.Services.Analytics Counters and “trending”
Trill.Services.Timeline Redis fan-out
Trill.Web Blazor WebAssembly with a gRPC-Web client
Trill-modular-monolith The same product, rebuilt

Every one of the ten service repositories has a README.md. Every one of those READMEs contains exactly one word: Trill. The only real run-book anywhere in the estate belongs to the monolith, written in August 2021 — after the microservice build had already gone quiet.

The architecture diagram is a .sln file

Trill\Trill.sln is 538 lines. It declares fifty-five entries, of which twenty-five are solution folders and thirty are actual projects, every one of them reached by a relative path that climbs out of the repository:

Trill.Services.Ads.Api  ..\Trill.Services.Ads\src\Trill.Services.Ads.Api\Trill.Services.Ads.Api.csproj
Trill.Pusher            ..\Trill.Pusher\src\Trill.Pusher\Trill.Pusher.csproj
Trill.Saga              ..\Trill.Saga\src\Trill.Saga\Trill.Saga.csproj

Those solution folders are the interesting part, because they are the only place in the entire estate where anybody states the intended taxonomy. They are named Microservice.Ads, Microservice.Analytics, Microservice.Stories, Microservice.Timeline, Microservice.Users, Gateway.API, Gateway.Pusher, Transactions.Saga and UI.Web.

Read that list slowly. The Pusher — a gRPC streaming service that subscribes to RabbitMQ — is classified as a gateway, not a microservice, which is a defensible and rather sophisticated call: it holds no domain state and exists only to translate one transport into another. The Saga is filed under Transactions, which is the author telling you that the process manager is where distributed consistency is supposed to live. The estate's design intent survives in a Visual Studio grouping and nowhere else — not in a README, not in a diagram, not in a commit message, not in a comment.

The solution also silently encodes the clone layout. Those ..\ paths only resolve if all eleven repositories are checked out as siblings under a single parent with their exact GitHub names. There is no clone script, no repo manifest, no git submodule, no dotnet new template and no sentence anywhere saying so. You discover it by opening the .sln, watching it fail, and reading the paths in the error list.

Time to first request, traced rather than assumed

I walked the path a newcomer walks, and it is worth writing down because “how long to a working request” is the only onboarding metric that cannot be faked.

  1. Clone Trill. You get four files and no README. Trill.sln will not open. This is a hard blocker; the only exit is to browse the GitHub organisation and infer that ten more repositories exist.
  2. Clone the other ten into the correct sibling layout, which you deduced from ..\ paths.
  3. docker-compose -f compose/infrastructure.yml up -d. This works. Ten infrastructure containers — Consul, Fabio, Grafana, Jaeger, Mongo, Prometheus, RabbitMQ, Redis, Seq, Vault — come up on a trill-network.
  4. Choose a runner. Three exist and none is documented. Part 2 is about how badly they disagree.
  5. Sign up, then sign in, to get a JWT. This is the only route to an authenticated request, and it depends on a signing certificate that expired on 2021-03-01.
  6. Issue a request. The eight .rest files work directly against services on ports 5030–5070. Through the gateway, the authenticated read path is broken by a CORS policy that omits GET, and the asynchronous write path has no authentication at all.

For someone who has never seen a DevMentors repository, that is hours, almost all of it spent discovering layout and runner. For someone who has, it is about ten minutes. That gap is the onboarding finding: the estate is perfectly navigable if you already carry the map in your head, and impenetrable if you do not.

Why this estate is worth fifteen articles

Because it fails in ways that are unusually legible. Trill is not sloppy code — the correlation-identifier propagation spanning HTTP and AMQP is better than most production systems I have worked on, and the { code, reason } error envelope shared across HTTP, the message bus and gRPC is a genuinely good piece of API design. What Trill has instead is a consistent shape: configuration and package references describe a system considerably more complete than the code that exists. Redis is configured in seven units and read by none. Consul health-checks point at a /ping endpoint the Pusher never maps. Vault issues dynamic Mongo credentials with automatic lease renewal, disabled in every runnable profile. Nine dockerize.sh scripts branch on $TRAVIS_BRANCH for a CI system that never existed here.

That inversion — config as aspiration, code as ledger — is the thread this series pulls, and it is a thread that runs through a great many real estates. Trill just makes it readable, because it is small enough to read all of.

Where the series goes

  1. Nine repos and no map — this post.
  2. Three runners, four inventories — three ways to start the estate, four manifests that disagree about what it contains.
  3. The config is a promise the code never made — twenty-one components implied by settings and absent from source.
  4. Durable queues on an ephemeral disk — Guaranteed Delivery configured at the broker and cancelled at the container.
  5. A gateway that publishes what it cannot name — a Channel Adapter with zero compile-time knowledge of any contract.
  6. The route values it throws away — a route matcher whose output is used as a boolean.
  7. The token becomes a body field — Content Enricher at the edge, and four defects in fifty lines.
  8. The policy that was never applied — one authorisation policy, declared once, applied nowhere.
  9. A channel is not a topic — publish-subscribe implemented as a point-to-point queue, in eleven lines.
  10. Every disconnect eats a message — cancellation checked one statement too late.
  11. The filter that drops the future — a Message Filter that silently discards scheduled stories.
  12. A saga that cannot compensate — five compensation overrides, zero of them reachable.
  13. Two contracts drifted, twelve did not — copy-paste contract governance, and exactly how it failed.
  14. The second architecture, switched off by comment — a complete typed RPC integration, disabled by three commented lines.
  15. Everything observed, nothing survivable — the retrospective: heavy on observing, light on surviving.

If you have ever inherited an estate where appsettings.json promised Redis, Consul and Vault and the code contained none of them, the next fourteen parts will feel like a diagnosis. We start with the simplest question a newcomer can ask and the estate cannot answer: how do I run this?