Minimal APIs, Written by Hand
Trill's monolith implements typed Get, Post, Put and Delete extensions over IEndpointRouteBuilder on .NET 5 - the framework .NET shipped eighteen months later, complete with query binding by string replacement.
Newest first, grouped by year. Search or filter by topic.
Trill's monolith implements typed Get, Post, Put and Delete extensions over IEndpointRouteBuilder on .NET 5 - the framework .NET shipped eighteen months later, complete with query binding by string replacement.
Two lines in Trill's module client serialise and immediately deserialise every cross-module message, buying a future network hop at the cost of a round trip that never leaves the heap - and there is a benchmark project defending the choice.
Trill's monolith puts the envelope fields on the message itself, then uses one sentinel value to mean this-is-a-synchronous-call in three unrelated subsystems that never mention each other.
The Trill monolith ships a startup-time validator that compares independently declared copies of the same message and crashes the app on a mismatch - the most original idea in either build, adopted by four declarations out of twenty-two.
Trill's hand-rolled framework follows the ASP.NET Core registration convention with unusual discipline, then breaks it twice on purpose - and the exceptions turn out to be more instructive than the rule.
Trill's saga microservice references twenty-four third-party packages; the same saga as a module references one - because the monolith deleted Convey and rewrote 4,600 lines of it by hand.
Four physical Mongo databases collapse into one when Trill is rebuilt as a monolith - and the only thing keeping the modules apart afterwards is a private const string copied into eleven files.
Trill's monolith makes every module type internal and gives each one an explicit friend list, so a cross-module reach is not a code-review conversation - it is a build error.
DevMentors shipped Trill as nine microservice repos, then rebuilt the identical product as a modular monolith three months later - giving us a controlled experiment in architecture that almost never exists in public.
Trill's Blazor WebAssembly client has twenty Razor components, thirty-seven inline style attributes, two authored class names and one stylesheet containing a single rule - and the thinness is not an oversight, it is the most precise measurement of what a teaching repository is.
An honest retrospective on Trill's five services and its client - twelve xunit scaffolds with no test files and no project references, a green test script over nothing, and a defect list where almost every entry would have died to a four-line assertion.
Trill's entire advertising product resolves to one boolean on somebody else's entity - a published ad is a Stories-service story with Highlighted set true - and the flag is priced at a hundred units a day while the same field is free on the public endpoint.
Trill's Timeline service stores each story body once and fans out only the identifier into every follower's sorted set - the right hybrid design, executed as a sequential foreach with one Redis round trip per follower and no batching anywhere.
Trill's entire ranking engine is thirty-nine lines of OrderByDescending().Take(10) over counters that only ever increase - no time window, no decay, no visibility filter - so once the top ten stabilises it can never change again.
The same .NET format specifier means thirty-two hex digits on a Guid and a grouped decimal on a long - and Trill's timeline writes its sorted-set members one way then reads them the other, so every non-empty timeline throws.
Trill's User constructor stores the name trimmed but not lower-cased, and the repository looks it up lower-cased. Any account with a capital letter in its name can never be found, which means it can never sign in - and the test fixture is all lowercase.
Trill's Stories service ships a complete caller-identity abstraction - context, factory, claims, an IsAdmin flag - registers it in the container, and injects it into nothing, while every endpoint reads the user id out of the request body instead.
Trill's browse endpoint loads every rating document for every story on the page into process memory and sums them in LINQ - while the same codebase already has a server-side SumAsync that returns the number without moving a single document.
Trill creates one compound index on its ratings collection, keyed userId then storyId, and every read query in the service filters on storyId alone - so the only index that exists cannot serve the only queries that run.
Trill wraps every command handler in logging, metrics, tracing and an outbox using Scrutor's TryDecorate - and the whole arrangement only works because a single attribute keeps the decorators out of the assembly scan that registers handlers.
Trill's last commit built a complete domain-event pipeline - dispatcher, mapper, handlers - and migrated one of the two command handlers onto it. The other still constructs its aggregate directly, so one domain event is never raised and a reputation gate can never fire.
Trill extracts story-text validation into an IStoryTextFactory and leaves the value object's constructor public - plus an implicit conversion from string, so any assignment builds an unvalidated StoryText in one keystroke.
StoryDocument.ToEntity passes the body where the title goes and the title where the body goes. It compiles because of one implicit conversion operator, and it has survived since the first commit because the only code path that would notice is dead.
Trill's Stories service is four projects with a dependency-free domain at the bottom. Its Users service is two projects with everything in one assembly - same author, same week, same estate, opposite answers to what a layer is.
Trill ships Jaeger, Prometheus, Grafana, Seq, Consul and Vault, and contains no circuit breaker, no dead-letter queue, no health check and no alert rule - the closing retrospective on what this estate gets right, what it does not, and when not to build a system this way.