Across a platform of many long-lived services, how much wiring would you leave to conventions and auto-configuration versus explicit declarations?
answer
- sort wiring by consequence, not keystrokes
- lend out boring decisions only
- pin user-visible and security seams
- own the house unit as a product
- goal is explainable, not minimal
basics
~20 sSplit by consequence: let conventions own the boring, uniform wiring every service wants, and declare explicitly anything user-visible or security-relevant. Conventions buy consistency and a single upgrade point; they cost traceability and make the dependency graph part of your behaviour contract.
solid answer
~50 sTreat it as a **decision-ownership** question rather than a style preference. Conventions and conditional defaults are the framework deciding on your behalf: excellent for wiring that is uniform across services and that nobody will need to explain under pressure — encoding, pooling, plumbing. They are a poor trade for anything **user-visible or security-relevant**, because there the cost of a default changing quietly is measured in incidents, not keystrokes. My usual shape is a **house extension unit** carrying the conventions, versioned and tested as a product, plus a short **explicit block per service** pinning the seams that matter: the error and response shape, the authentication and authorisation wiring, serialization settings, and anything with a user-facing contract. The measurable goal is operability: if an engineer cannot answer "which unit wired this and why" in a couple of minutes, the implicit layer has grown past its value.
go deeper
Notice that defaults you did not write are a real part of how your service behaves, and that reading a service's own source does not always tell you the whole wiring story.
Be able to argue both directions concretely: defaults cut repeated setup, explicit declarations keep the wiring visible in a diff. Give an example of each from a service you have worked on.
Bring operations into the answer: which defaults you would pin because a silent change would reach a user, and how you would detect one that moved during an upgrade.
Own the implicit layer as a product with a version, tests that assert outcomes, a supported override and a deprecation path, and name the signal you would watch to know the balance has drifted.
## The real question behind the question Every convention and every conditional default moves a decision from your codebase into the framework's. That is a trade, not a virtue and not a vice. The judgment being tested is whether you can say **which decisions you are willing to lend out**, and what you will do when the lender changes its mind during an upgrade. ## What each side actually buys | | Convention and conditional defaults | Explicit declaration | |---|---|---| | Onboarding | New service starts working in minutes | Every service repeats the same setup | | Consistency across a fleet | High, and it drifts slowly | Depends entirely on review discipline | | Traceability | Low: the wiring is not in the source you read | High: the answer is in the file | | Upgrade blast radius | Wide: behaviour can move without a source change | Narrow: changes are visible in a diff | | Cost of divergence | A service needing something else fights the defaults | None; every service already states its own case | Neither column is the safe one. A fleet with no conventions pays for the same wiring hundreds of times and drifts anyway; a fleet where everything is implicit cannot answer basic questions during an incident. ## A usable split I sort wiring by **consequence of a silent change**, not by how much typing it saves: - **Leave to convention** — wiring that is uniform, unremarkable and self-announcing when it breaks: connection pooling shapes, thread or task plumbing, encoding, metric and log plumbing, health surfaces, development conveniences. - **Declare explicitly** — anything with an external contract or a security consequence: authentication and authorisation wiring, the error and response body shape, serialization settings that affect the wire format, request size and timeout limits, anything that changes what a client sees. - **Decide case by case** — persistence and messaging defaults, which are uniform until the moment a service has an unusual load profile and then are not. The test for the middle column is a question: *if this default changed under us in a release, how would we find out?* If the answer is "a customer tells us", it belongs in the explicit list. ## Owning the implicit layer If the platform is going to have conventions, someone must own them as a product: 1. **One house unit, versioned.** Services adopt a version rather than inheriting whatever their dependency graph produced. A version bump is then a reviewable event. 2. **Tests that assert outcomes, not inputs.** The unit's test suite boots a representative application and asserts which components serve which roles, so a conditional change is caught in the platform's build, not in a service's incident. 3. **A visible report in every service.** Engineers must be able to print which units matched and why. Without that, the conventions are folklore. 4. **A documented escape hatch.** Overriding one role must be a supported, narrow operation. When the only way out is to disable the whole unit, teams disable it and the consistency benefit evaporates. 5. **A deprecation path.** Changing a default across a fleet is a migration: announce, make the new behaviour opt-in, flip it with the version, and keep the override available. ## Signals that the balance is off - Too implicit: incident reviews keep finding "nobody knew that was wired"; onboarding engineers cannot draw the request path; the same upgrade produces different behaviour in two services with identical source. - Too explicit: the same forty lines are copied into every new service and drift version by version; a cross-cutting fix requires a pull request per service; teams stop upgrading because upgrading means re-deriving the wiring. Both failures are visible in ordinary telemetry — time to answer a provenance question, and the number of services touched by one platform change. ## What I would say in an interview That the goal is not minimal configuration but **explainable configuration**. I would rather a service repeat six lines that name the thing they configure than inherit six defaults nobody can point to. Conventions are worth having for the wiring below that threshold, and the threshold I use is whether a silent change would be noticed by us before it is noticed by a user.
- How do you change a convention for a whole fleet without breaking services that relied on the old default?Treat it as a migration: ship the new behaviour behind an opt-in setting, give teams a release to adopt it, then flip the default in a new version of the house unit while keeping the override available. The version bump makes each service's adoption a reviewable event rather than a surprise.
- Which single metric best tells you the implicit layer has grown too large?Time to answer "which unit wired this, and why" for an arbitrary component. When that is minutes, the conventions are carrying their weight; when it needs an expert or a bisect, the platform has traded operability for boilerplate savings it can no longer justify.
- Does a monorepo with one shared build change this calculus?It narrows it. A single dependency graph makes conventions safer, because a change is visible in one place and can be tested against every service at once. It does not remove the traceability cost: engineers still read a service and see wiring that is not there.
saying these in an interview costs you the question
- Argues explicit wiring everywhere with no account of fleet-wide drift
- Treats minimal configuration as the goal in itself
- Ships conventions with no version, tests or deprecation path
- Leaves authentication or response shape to an inherited default
- Claims explicit wiring removes upgrade risk entirely