Here's a failure mode nobody puts on a roadmap. You have ten microservices. They're all "clean architecture." And every single one is laid out a little differently - folders named by whoever started it, logging wired up five ways, validation here in one service and there in another. Each is fine on its own. Together, they're ten dialects of the same language, and every engineer who moves between them pays a translation cost.
The instinct is to fix it with a wiki page: "here's how we structure a service." Documentation doesn't enforce anything. Six months later you have eleven dialects and a stale wiki.
A standard you have to remember isn't a standard. A standard you inherit by referencing a package is just how things are.
Make the architecture a dependency
The move that actually worked: package the architecture itself as a shared library - a single internal NuGet package every service depends on. It defines the layers, the base abstractions, and the cloud-infrastructure plumbing once, so a new service doesn't describe the standard, it imports it.
# every service, same shape - because the framework defines it Service.Api # handlers / endpoints Service.Application # use-cases, validation, CQRS Service.Domain # entities, business rules Service.Infrastructure # persistence, messaging ↑ references Framework.* # layering + AWS layer, shared # DynamoDB, search, events, secrets, queues - wrapped once
The infrastructure layer is the quiet hero. Data access, search, event publishing, secret handling, queues - wrapped once in the framework, consumed everywhere. A new service gets battle-tested plumbing for free instead of re-implementing it (subtly wrong) for the eleventh time.
What it buys you
- Uniformity by construction. Services look the same because they literally share the same skeleton - not because someone policed a convention.
- Onboarding collapses. Learn one service and you've learned all of them. An engineer is productive in a new repo in hours, not weeks.
- Cross-cutting concerns ship once. Logging, validation, resilience, security - fix or improve them in the framework and every consumer inherits it.
- New services start at the finish line. The boring, error-prone 60% is already done and proven.
The bill comes due: version discipline
Here's the trade nobody warns you about. The moment your architecture is a dependency, it's also a single point of change. A breaking tweak in the framework's infrastructure layer doesn't break one service - it ripples to every consumer at once. And in real life, services drift: one team is on the latest, another is three versions behind, and now "shared" is doing some heavy lifting in quotes.
The uniformity that makes onboarding easy is the exact same coupling that makes a bad release dangerous. So a shared framework only works if you treat it like a product, not a folder:
- Semantic versioning that you actually honor. Breaking changes are loud, rare, and migration-pathed.
- A version policy across services so the estate moves together instead of fragmenting.
- Release discipline. The framework needs the most testing of anything you own, because its blast radius is everything.
When not to do this
Standardize the things that should be boring - layering, plumbing, cross-cutting concerns. Do not standardize the things that are genuinely different across domains, or you'll force a shape that fights the problem. The framework should remove decisions nobody benefits from re-making, and stay out of the way of the ones that matter.
A shared framework is one of the highest-leverage things a senior engineer can build - it raises the floor for everyone. Just go in clear-eyed: you're trading the chaos of ten dialects for the responsibility of owning the one language they all speak.
Scaling a team's architecture?
I build the platform standards and frameworks that let many teams ship consistently. Let's talk.
Get in touch →