EmailService::Normalizer strips and downcases the email UserService::Authenticator checks credentials SessionService::Creator creates a session"
Don't know how to say this without sounding arrogant. WTF?
I get the reaction. For something like a simple login flow, this probably looks like a lot of ceremony. The goal isn’t to over-engineer it, though. Each piece handles a single concern that may evolve independently over time (email normalization, authentication, session creation). Keeping those parts separate makes it easier to test, extend, and reuse.
And while the example is intentionally simple, the benefits show up more clearly in complex scenarios, like when workflows start branching, new auth methods are added, or the same logic needs to run in different contexts (web, API, background job, etc.). Having composable, consistent services keeps that complexity from leaking everywhere else.
It’s not the only way to structure things, but it works well for me.
Guardrails
- Size and complexity limits. Lints for max lines/ABC/branching. If a service starts growing branches, that’s a signal we may need to extract a new one.
- Naming and layout. Stable namespaces and file locations reduce drift and make the right home more obvious.
Culture / Habits
- Docs with real examples. Short “what / why / how” write-ups and a few canonical services to copy. Explain the composition rules, why services should do one unit of work, and why only orchestrators compose them. Point to real examples in the codebase. You want to describe and show the golden paths. That doesn’t mean new golden paths can’t appear later, but when they do, it should be discussed and intentional. I’ve heard this described as an “architecture menu”: choose from the menu, or explain why you need to do something off-menu and how it’ll work to get buy-in.
- Onboarding. As part of onboarding new engineers, go over the architecture and the patterns. Explain why the team does these things, walk through examples, and answer questions.
- PR templates with checklists. Reenforce golden paths / patterns. Ask: is this one responsibility or two? Is this becoming orchestration?
- Stewardship. Senior engineers model the pattern and call out deviations early.
- Fast-follow cleanups. If we cut a corner for a deadline, we open a follow-up ticket and actually do the cleanup right after shipping.
Linting rules can be overridden, so culture has to carry it in the end. People need to care, and new folks need to understand why they should care.
Engineering teams may also run up against the business side pushing back on time spent cleaning up or paying down tech debt. The key is showing that consistency and well-defined patterns reduce bugs and actually make the team faster over time (data helps prove the point here). A cut corner might seem harmless, but if you keep cutting them, the square product you thought you were shipping slowly becomes a circle that’s hard to keep in place.
Terretta•4mo ago