Skip to main content

Layering

Chances are, you are better at dealing with one task at a time, instead of trying to do seven things all at the same time. The same is true about software.

In a typical business application, when we talk about “layering”, we are talking about keeping the following concerns isolated:

  • data storage
  • business logic
  • input data validation (and error handling)
  • user interface structure
  • user interface visuals

Well-defined layers with strict controls of boundaries and ownership tend to make systems robust. The fundamental nature of layers that makes them great building blocks for robust systems, is the natural limits they put on complexity. This is achieved by two properties.

Layers are Single-purpose

This property is encoded in the word itself — a layer is flat, thin, uniform. By declaring that it will only deal with one concern at a time, a layer buys its way out of complexity that is inherent to dealing with multiple concerns at the same time. It also significantly lowers the cognitive overhead of working with code. The author and the reader can focus on one thing at a time, say input data validation, and know that some data storage code won’t suddenly sneak up on them.

Communication Only Between Adjacent Layers

By themselves, layers do not make an application. To bring the application to life, data needs to flow between the layers. In fact, that’s what a working application is — data flowing between layers in response to user-generated (and sometimes system-generated or timer-based) events. So to capitalize on the layered structure, we need to cleanly and strictly define the rules by which communication happens between layers. Three simple rules cover most use cases:

Communication is only allowed between adjacent layers.

When deciding on the public API of a layer, favour default-deny.

Never externally expose the internal details of a layer.

XXX Example (like the “payload spilling into the component” example)

This is one of those cases where the rule does not seem to bring any naively tangible benefits, yet breaking the rule repeatedly blurs the boundary between layers, and over time the system degrades into an amorphous blob, with tangled concerns, bugs, and becomes difficult to reason about. Following this rule is preventive in nature — more like brushing your teeth every day rather than going to a dentist to fix a cavity.

Part of designing the public API of a layer is dealing with errors. The get error handling right section talks about errors in detail, but from the perspective of layering specifically, the important questions are:

  • which layer is responsible for handling which type of error?

  • wrapping errors

  • layering like in protocols

  • there are some cases when more performance can be squeezed out of the system by foregoing layering, but this tends to happen in close-to-hardware, system level use cases. Other methods of harnessing complexity can be employed in these cases.

  • who deals with errors

  • get the types of the public interface right

Next: Composition ⇒

Comments