The Road Out of the Monolith
Which of ModularMonolith's three modules could become a service tomorrow, what blocks the other two, and the hardening bill the sample never pays - a retrospective on the modular monolith as a station, not a destination.
Newest first, grouped by year. Search or filter by topic.
Which of ModularMonolith's three modules could become a service tomorrow, what blocks the other two, and the hardening bill the sample never pays - a retrospective on the modular monolith as a station, not a destination.
Conferences and Speakers disagree on layout, naming, id policy, exception visibility, mapping style and route constraints - plus a 200 that returns nothing and a broker that threw NotImplementedException for four months. The honest ledger.
One middleware turns ConferenceNotFoundException into the wire code conference_not_found via Humanizer - a naming convention promoted to API contract, with a 400-versus-404 tension and Error-level logs it never resolved.
ModularMonolith's entities are public-setter data bags, its one immutability rule is a fixup with a comment, and its hardest domain question is asked in a comment and answered by nothing - a reading of domain modelling by annotation.
Conferences registers in-memory and EF repositories for the same interfaces and lets last-registration-wins pick - a live lesson in DI as strategy selector, dead registrations as fossils, and a no-op UpdateAsync nobody deleted.
A single boolean in appsettings decides whether ModularMonolith's events run inside your HTTP request or on a background pump - and each setting fails differently. Delivery semantics, read honestly from a ternary.
One Postgres database, one schema per module, and entity configurations that are all empty bodies - what ModularMonolith's data layer declares about future service boundaries, and what its migrations quietly gave away.
The last commit of ModularMonolith renames Name to ConferenceName in the subscriber's copy of its one integration event - and the JSON translator has delivered null into that field ever since. Anatomy of a silent contract break.
ModularMonolith builds its message broker from System.Threading.Channels and one BackgroundService - a textbook Message Channel and Event-Driven Consumer in miniature, with every missing enterprise-integration pattern visible by its absence.
ModularMonolith routes events between modules by class name and converts them by JSON round-trip - a one-line Message Translator that lets modules share contracts without sharing types, at a price the next part collects.
The last commit of ModularMonolith moves one line of Startup from first to last - and the whole cross-module event system depends on it. Assembly scanning, throwaway service providers and migrations inside ConfigureServices.
Every module in ModularMonolith plugs in through the same Add/Use extension pair - a recipe small enough to memorise, complete enough to be a mini-framework, and visible even where a module registers nothing at all.
ModularMonolith makes every controller internal and teaches MVC to find them anyway - compile-time module isolation from one feature provider, an InternalsVisibleTo chain, and the one place the wall has a hole.
DevMentors' ModularMonolith sample packs three conference-system modules, a mini-framework and one deliberately broken contract into eighty-four C# files - opening a series that reads all of it, defects included.
Redis registered in nine services and read in one, discovery stacks in services that never call anyone, an outbox package referenced but never wired, and versions that float by construction - an inventory of everything Pacco wires up and never uses.
The retrospective, with the ledger totalled - seven byte-identical MessageBrokers, one AggregateRoot with three meanings, what Convey genuinely optimises, where a Convey.Bootstrap meta-layer should have stood, and what a consumer should copy from this estate.
A refactoring.guru walk through Pacco's real lines - a flags-enum search that can never match, twin guards, a dead password check, mappers that return null, and an Id that is never assigned - each smell named and each paired with the technique that cures it.
The self-styled uber AI order maker resolves to FirstOrDefault behind an in-source confession, and its reservation heuristic structurally out-bids every earlier booking - the estate's data-science audit, written up as the short honest piece its eleven lines deserve.
OrderMaker drives a whole order through a Chronicle saga and reports its progress in a single RabbitMQ header - stamped on every publish, forwarded by seven copied MessageBrokers, read back by Operations, and backed by a saga store that forgets everything on restart.
Deliveries maps domain events into integration events that carry facts while Vehicles publishes bare ids straight from its handlers - two event philosophies in one estate, and a vehicle-to-resource mirror that syncs deletes but never creates.
Operations subscribes to eighty estate messages from one hand-maintained JSON file - a shadow copy of every contract in the system that nothing validates, with ghost entries nobody publishes and real rejections it never lists.
Orders and Parcels keep the estate's only cross-service contract honest with Pactify - a pact written to a relative path six directories above the repository, verified against a seeded Huge Weapon, and checked for shape only. Consumer-driven contracts with no broker, read from source.
Nine Pacco repositories run dotnet test against zero test projects and stay vacuously green, while the one repo with a real unit-integration-e2e-performance pyramid is the only one whose Travis config omits the test step.
The fair part of the ledger - how UseDispatcherEndpoints, self-identifying commands, afterDispatch hooks and snake_case conventions let every Pacco service put its whole HTTP surface in one readable file.
Pacco models aggregates-per-document honestly across ten Mongo databases, and then creates exactly one index in the whole estate - fire-and-forget, in a scope that has already been disposed - while natural-key scans sit on the message hot path.