All articles

Layering That Teammates Can Understand—and the Build Can Enforce

Define stable layer roles, constrain optional upper layers, and automate checks only when a team needs stronger enforcement.

Source and AI note: This article is based on Gemini’s Devpractice on YouTube. It was generated and edited with the gpt-5.6-sol model.

A codebase becomes hard to navigate when Controller calls FacadeImpl, which calls a service, which calls another service, while a neighboring feature uses a workflow or wrapper for similar work. Each local decision may look reasonable. Together, they leave a new teammate unable to answer a basic question: where should the next piece of business logic go?

The problem is not the number of layers by itself. Some business flows genuinely need composition above individual services. The problem is adding and removing layers without a shared rule that explains their roles.

Start with a stable standard path

A team should define the minimum layers that ordinary work follows. The names depend on the project; the useful part is the consistent path and the meaning assigned to each position. If a business layer is part of that standard, even a simple use case should pass through it rather than skipping it because the current method only delegates one call.

That small amount of ceremony can look wasteful in isolation. Across the project, it buys predictability. A developer can open an unfamiliar feature and know where requests enter, where business behavior lives, and which dependencies are allowed. The convention makes code easier to read and change because people do not have to infer a new architecture for every package.

Consistency also has a maintenance purpose. The code is a company asset that future teammates must extend. A structure understood only through oral explanation is not yet a dependable team rule.

Add an upper layer only for a defined kind of composition

A standard path does not prohibit every additional layer. Suppose one business component handles members and another handles products. A use case that combines member rules with member-specific product rules may deserve a coordinating layer above both. A team might call it a facade, workflow, or wrapper.

The label matters less than the admission rule. For example: create a facade only when a flow composes two distinct business capabilities. If the code contains only one business responsibility, renaming it as a facade does not create a meaningful upper layer.

This rule must be grounded in the team’s actual domain. The team first needs to identify its major business concepts and what counts as composition between them. Without seeing the code and domain, no universal layer diagram can settle that question. What can remain stable is the process: define the standard path, define the exceptional condition, and use the same terminology across features.

Optional composition layers may appear only where their condition exists. The standard layers beneath them should not expand and contract with each requirement. If the team decides a layer truly adds no value, remove it from the project-wide standard through an explicit change rather than bypassing it case by case.

Write rules that answer implementation questions

A useful convention should let a newcomer answer concrete questions:

  • What is each standard layer responsible for?
  • Which direction may dependencies flow?
  • What is the single name for a coordinating upper layer?
  • Under which business condition may that layer appear?
  • Are standard layers mandatory even for a one-line delegation?

Examples help reveal the boundary. Show one ordinary feature, one legitimate composition, and one structure the team rejects. The rejected case matters because words such as “service” and “facade” are broad enough to support incompatible interpretations.

The rule should serve the project, so it can change when the team learns something new. Until it is deliberately revised, however, similar code should remain structurally symmetrical. Otherwise the documented architecture and the running code describe different systems.

Automate enforcement when social checks are insufficient

Documentation and review may be enough for a small team. If the same violations keep recurring, the convention can become an executable constraint. Marker annotations or component types can identify layers. Architecture tests, lint rules, or analysis in the build can then reject forbidden dependencies or missing standard components.

This stronger enforcement has a cost. It adds tooling that the team must understand and maintain, so it is not the default answer for every project. Use it when the value of consistent structure justifies the mechanism—particularly when manual review no longer keeps the code aligned.

Automation cannot decide whether two pieces of code represent distinct business capabilities. People still define the domain meaning; the build can only enforce rules expressed in code. That division is healthy: the team explains why the boundary exists, and the system catches mechanical drift before it becomes another local convention.