4+2 Layered Architecture

Separation of Concerns Applied to Software Architecture

Ricardo Nogueira
7 min readMar 6, 2022

Intro

Many principles drive software development and Separation of Concerns may be the most fundamental of all, many others derive from it. Functions derive from it. Classes, objects and methods derive from it. Modular and layered architectures derive from it.

In the micro dimension of classes Separation of Concerns implies some “best practices” such as: not to mix abstraction levels in the same method; not to mix responsibilities in the same method and class.

One step higher, Separation of Concerns drives modularity, tells us to separate Business Rules and Domain Models from Data Access and User Interface. Tells us to separate related and reusable code in libraries.

Separation of Concerns is also behind our goal to manage dependencies with Low Coupling using the Interfaces/Implementations strategy that allows modules to be pluggable and testable. Central modules Interfaces define APIs while provider modules implement these APIs making both modules independently testable.

In this article I will revisit layered architectures and propose a polished view to expose only the most important concepts.

Layered Architectures and Dependency Injection

When applied to software architecture this principle was fundamental to a number of layered architectures that evolved from Model View Controller (MVC) to Model View Presenter (MVP), Hexagonal Architecture, Onion Architecture and Clean Architecture among others.

The original Clean Architecture proposal was presented by Robert Martin (Uncle Bob) in 2012 and it pictures layers with software engineering concepts from Ivar Jacobson², Steve Freeman and others. Bob Martin states that all these previous architectures share the same objective: Separation of Concerns to produce systems that are:

  • Independent of the framework
  • Testable
  • Independent of UI
  • Independent of Database
  • Independent of external agency
Original image from Robert Martin paper

But there is a conceptual challenge in these architecture proposals where inner layers “knows nothing” about outer layers. This is in fact a static dependency requirement for proper layering: layers should depend only on inner layer types. It is a “must obey” rule!

On the other hand, at runtime inner layers do depend on implementations provided by outer layers. For example, domain Use Cases do depend on data repositories implementations from the outer persistence layer.

This challenge is “solved” by the Dependency Inversion Principle or more specifically by a Dependency Injection mechanism where inner classes are injected with outer implementations of their dependencies. To be more general we could say: at runtime inner layers are configured with unknown implementations of well-known interfaces”.

This Inversion of Control mechanism using injected implementations was first used by Trygve Reenskaug when he designed the MVC architecture for Smalltalk in 1978 at Xerox PARC¹. It was then extensively used in the Smalltalk-80 language and environment.

Donald Wallace called it the application of Hollywood’s Law — “Don’t call us, we’ll call you” — when developing a component for the Mesa Project in 1980, also at Xerox PARC.

In about 1994 Uncle Bob used the expression Dependency Inversion Principle and in 2004 Martin Fowler coined the term Dependency Injection.

Clean Architecture Revisited

This layered concepted matured with technology and experience, always moving towards less coupling between components, effectively becoming more testable, flexible and adaptable.

I want revisit Clean Architecture and propose a layered view that exposes the most important concepts in what I call the 4+2 Layered Architecture, it contains:

4 Major Layers:

  • Domain Layer (business rules)
  • Data Layer (persistence)
  • Services Layer (external services)
  • Presentation Layer (UI)

2 Accessory Layers:

  • Core Layer (shared library)
  • Dependency Injection Layer (dependency inversion)
4+2 Layered Architecture

This is not a departure from previous layered architectures, it is rather a review that at one hand presents more general layer definitions (no mention to web, controllers and other framework or technological solutions) and on the other hand clearly defines core Domain concepts, the use of Interfaces/Implementations and the necessity of Dependency Injection.

Let’s look at these layers in detail:

Domain Layer

Contains core concepts and rules that are independent of infrastructure and presentation. It represents state and behavior that models the application. It contains the following concepts:

  • Entities
  • Business Logic (Use Cases, Validators, Exceptions)
  • Data Repository Interfaces
  • Service Interfaces

This layer does not depend statically on outer definitions (types), but on runtime it may depend on injected implementations that fulfill its internal interface definitions (Dependency Inversion Principle).

This low coupling structure blinds this central layer against external changes, creating a highly testable and independent component.

Data Layer

Contains persistence infrastructure implementations and adapters. It is likely to contain:

  • Data models
  • Model/Entity mappers
  • Data Repository Implementations (persistence)

Once more, Separation of Concerns brings forward a testable and pluggable module that fulfills the inner Domain Layer interface requirements.

Sometimes there may be alternative implementations in this layer, such as support for different databases. Each implementation can be tested independently. The chosen alternative will be selected by the Dependency Injection mechanism.

It is not always so straightforward to alternate implementations as specific solutions may impact definitions in inner layers APIs. This against the proposal of inner layers independence but we may have to deal with it. For example, some persistence frameworks use asynchronous invocations while others are synchronous, such a change is likely to require redefinitions in the whole application. But since we are well organized our points of impact are well defined, usually at interfaces and use cases level.

Service Layer

This is an optional layer to model external dependencies beyond data persistence, such as access to web services, remote devices, hardware, etc.

This layer would be omitted when the application does not depend on external services, becoming a 3+2 Layered Architecture variant.

Presentation Layer

This is the layer to present information and interact with the user. It depends only on types defined in inner layers, mostly on Domain Use Cases, Entities and Exceptions. It is the “magic” of interfaces and implementation injections that make it possible for this layer to use at runtime code provided by Data and Service layers.

The inner organization of this is layer is very framework dependent, the main concept here is that no other layer depends on this one and multiple Presentation alternatives may be implemented and run independently. For example, the same application may offer a Web UI, a Mobile UI and a Desktop UI all using the same Domain Use Cases.

Core Layer

This accessory layer is also optional, it is a module to organize common interfaces and utility classes and functions that do not belong to the Domain Layer but must be made available widely to the application.

Dependency Injection Layer

This is the outermost layer; it will contain the Dependency Injection mechanism. Depending on the language and libraries this may be just configuration files (XML, JSON, …). On other platforms this layer may contain DI code to configure inner layers programmatically.

Being the outermost layer, it would have the undesired capability to see to all application code. Some languages do provide means to control internal layer visibility allowing each layer to expose only the desired APIs and implementations (the principle “make the right thing easy and the wrong thing hard”).

Inter-Layer relations

We have already mentioned that inner layers should not depend on types of outer layers and that at runtime inner layers may be configured with outer implementations of well-known interfaces.

And here Separation of Concerns reminds us that we should minimize the knowledge that one layer has over other layers using mechanisms to promote layer independency. Newspeak language by Gilad Bracha³ uses nested classes and consequently nested namespaces with no global scope, a beautiful implementation of modularity and dependency provision.

In a class or object-based language it would be interesting to apply Separation of Concerns by implementing a layer manager object for each Layer, this object would explicitly expose its layer dependencies and configure its module when injected with the required implementations. I have also produced a complementary document and video tutorial for a Flutter implementation of this 4+2 Layered Architecture that applies this principle with AppLayer objects for interlayer interactions; a structured implementation is available as a public github template project

Larger Applications

In large domains our 4+2 Layered Architecture proposition would address a certain context of the domain universe. Separation of Concerns indicates that large applications should be divided into cohesive interconnected modules. Each module would then be separately developed with a 4+2 Layered Architecture approach.

Testing

Testing is fundamental and has become standard in many software development practices (Agile, Test Driven, Continuous Integration). Application architecture is a key component to allow and promote efficient testability.

There is a general desire to get max code coverage in our tests but this is often a double-edged sword — high volume of tests incur high effort to refactor code; high volume of tests usually drives a lot of copy/paste development (mindless testing).

We need first and foremost good test coverage of user stories, exploring business rules and border cases, usually against mock implementations or configured dependencies (well-known behavior).

Second, we need to test implementation APIs (black box testing). And again, using border cases and well-known dependencies.

This layered architecture does promote testability by enforcing that most inter layer interactions do rely on Use Cases (business rules) and implementation APIs.

Conclusion

Layered Architectures are consequence of applying Separation of Concerns to application structure. This 4+2 Layered Architecture proposal is a refinement of Clean Architecture exposing just the most relevant concepts of Domain Business Rules and the use of Interface APIs with Injected Implementations.

References

  1. Trygve Reenskaug — MVC at Xerox PARC, 1978–79:
    https://folk.universitetetioslo.no/trygver/themes/mvc/mvc-index.html
  2. Ivar Jacobson — Object-Oriented Software Engineering — A Use Case Driven Approach, 1992:
    https://www.ivarjacobson.com/publications/books/object-oriented-software-engineering-1992
  3. Gilad Bracha — The Newspeak Programming Language, 2006:
    https://newspeaklanguage.org/
  4. Jeffrey Palermo — Onion Architecture, 2008:
    https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/
  5. Robert C. Martin — Clean Architecture, 2012:
    https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
  6. Ricardo Nogueira — 4+2 Layered Architecture: A Flutter Implementation, 2022:
    https://github.com/cc-nogueira/flutter_layered_template

--

--

Ricardo Nogueira

Full stack software developer, currently dedicated to Flutter Development.