The idea of decomposing application into layers (logical vertical split) or tiers (physical vertical split) isn't new - it's bread'n'butter of systems architecture. Reasons for such a split are still the same (as they were in 90s, 80s & beyond):

  • to reduce complexity & separate concerns
  • to scale tiers independently
  • to enable / simplify usage of different tech suited for each layer / tier
  • etc.

No-brainer, right? Good.

Problem appears when you consider the boundary between layers / tiers as a boundary between separate systems / applications. Developed & maintained:

  • by separate teams
  • with separate backlogs, ...
  • ... separate priorities, ...
  • ... separate product (service) owners, etc.

What happens is - upper tier is very heavily dependant on lower tier, because pretty much every scenario is a spike (goes through all the layers):

  • this has a very serious impact on quality assurance of upper tier (all errors not detected in QA of lower tiers will cumulate there)
  • downstream propagation of all changes in lower tiers is absolutely crucial & even if done properly, such changes can quite likely impair the development of upper tier
  • lower tiers may have a big problem with business ownership, due to limited visibility (as products)
  • as the access to lower tiers is made mainly via upper tiers, testing it may be a challenge

X-layer / x-tier comm is not an interface (or API)

"Wait, in multi-tier scenario the upper tier just calls lower tier using some sort of communication standard: REST, WebService, MOM, etc. The contract is agreed, data structures are confirmed - doesn't it sound like a typical on-line interface?"

In general, there's some truth in this statement:

  1. such an x-tier contract is a form of abstraction that hides the implementation behind itself
  2. it's also dedicated for the sole purpose of fullfiling upper tier's needs - if there's another 'client', it can easily get another contract
  3. actual usage pattern is very simple & interface-like: you know who's the caller, who's provide the 'service', so it's also quite easy to set up the (theoretical) responsibility

However, there are some differences as well:

  1. there's completely different velocity of change - when there's a functionality to get developed, in most cases all parts (upper tier, lower tier & the contract) get modified
  2. granularity of operations is different - they are very atomic & vivid: this kind of contract gets changed pretty much on daily basis - I don't mean just data structure changes, but also behavior coupling changes - the expectations on the functionality behind the contract that impact the functionality in the upper tier(s)
  3. in the usual cases, when two modules (microservices, separate applications) depend on each other heavily (each action / service calls actions / services of other application / module) it means that we have a problem with coupling. In this particular case it's not high coupling, but rather a high cohesion -> these tiers BELONG together (domain-wise): so you have to leverage that (developer together, test together, deploy together) instead of forcing the decoupling

The lesser evil(s)

Sometimes your reality is just broken & the split is already there: tiers belong to different teams, org units or even companies (sic!). To make it inconvenient:

  • set very clear responsibility areas
  • establish & mantain the communication high-way
  • make these people cooperate as closely as possible
  • use feature toggling for desynced workstreams
  • regardless of local test automation, set automated testing for the communication itself
  • distinguish subset of upper tier automated tests that HAVE to run within lower tier CI loop (cover that in responsibility areas)

But these are just workarounds & anesthetics. In the end there's just one solution to the problem:


... to make sure that the full spike will be fully covered within one, interdisciplinary, cross-functional team.

Regardless of:

  • different skills required
  • different technologies used
  • different composition (a bit of re-factoring won't kill ya, right?)

This will help you to:

  1. enable separate (& independent) workstreams
  2. limit the number of unnecessary dependencies between teams
  3. set proper boundaries between actual subdomains (easy to abstract as products), not within them

Pic: Betty Crocker.

Share this post