DEV Community

Cover image for A brief history of application architecture (of the 21st century)
Igor Bertnyk
Igor Bertnyk

Posted on

A brief history of application architecture (of the 21st century)

Table of contents

  1. N-Tier Architecture
  2. Ports & Adapters
  3. Onion Architecture
  4. CQRS
  5. Beyond

N-Tier Architecture

Admittedly created long before year 2000, this type of architecture became popular in web applications around that time.

Tiers and Layers

Layers are a way to separate responsibilities and manage dependencies. Each layer has a specific responsibility. A higher layer can use services in a lower layer, but not the other way around.
Tiers are physically separated, running on separate machines. A tier can call to another tier directly, or use asynchronous messaging (message queue).

Typical app consist of 3 layers:
Presentation Layer holds the Part that the User can interact with, e.g. WebApi.
Business Logic holds all the logics related to the business requirements.
Data Access Layer usually holds ORMs to access the Database
3 Tiers

More complex implementation

It is not restricted to 3 tiers. A tier can call to another tier directly, or use asynchronous messaging (message queue).
Although each layer might be hosted in its own tier, that's not required. Several layers might be hosted on the same tier.
many tiers

Benefits and Disadvantages

When to use

  • Simple web applications.
  • Migrating an on-premises application to Azure with minimal refactoring.
  • Unified development of on-premises and cloud applications.

Challenges

  • It's easy to end up with a middle tier that just does CRUD operations on the database, adding extra latency without doing any useful work.
  • Monolithic design prevents independent deployment of features.
  • Makes it difficult for developers to change an application and for operations teams to scale the application up and down to match demand.
  • The Database is usually the Core of the Entire Application, i.e. it is the only layer that doesn’t have to depend on anything else. Like in Jenga tower, any small change in the Business Logics layer or Data access layer may prove dangerous to the integrity of the entire application.

Ports & Adapters

History

Ports and Adapters or also known as Hexagonal Architecture, is a popular architecture invented by Alistair Cockburn in 2005.
"Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases."
This is one of the many forms of DDD (Domain Driven Design Architecture)

Motivation

The idea of Ports and Adapters is that the application is central to your system. All the inputs and outputs reach or leave the core of the application through a port. This port isolates the application from external technologies, tools and delivery mechanics.

Benefits

Using this port/adapter design, with our application in the centre of the system, allows us to keep the application isolated from the implementation details like ephemeral technologies, tools and delivery mechanisms, making it easier and faster to test and to replace implementations.
Ports and Adapters

Components

  • Core A place where the business logic of the application happens
  • Ports Ports represent the boundaries of the application (usually interfaces)
  • Inbound ports communication points between the outside and the application core
  • Outbound Ports This are the interfaces the core need to communicate with the outside world
  • Adapter Implementation of ports

Onion Architecture

he Onion Architecture was introduced by Jeffrey Palermo in 2008:
https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/
This is an evolution of Ports & Adapters Architecture.

The idea of the Onion Architecture is to place the Domain and Services Layers at the center of your application, and externalize the Presentation and Infrastructure.
Onion Architecture

Layers (rings) : Core

  • Core Domain and Application Layer form the core of the application. These layers do not depend on any other layers
  • Domain Model This is the center of the architecture. It contains all the domain entities. These domain entities don’t have any type of dependencies
  • Domain Services responsible to define the necessary interfaces to allow to store and retrieve objects.
  • Application Services This layer has the purpose to open the door to the core of the onion. The Service layer also could hold business logic for an entity. In this layer, service interfaces are kept separate from its implementation, keeping loose coupling and separation of concerns in mind. The Core Layers should never depend on any other layer.

Outer Layers

  • UI / Test Layer It's the outermost layer, and keeps peripheral concerns like UI and tests. For a Web application, it represents the Web API or Unit Test project
  • Infrastructure This ring has an implementation for the dependency injection principle because the architecture contains the core interfaces in the center and their implementations are at the edges of the application services ring. Infrastructure can be anything: Entity Framework Core Layer to access DB, Email Notification sender, Messaging System etc.

Benefits and Drawbacks

Benefits

  • Onion Architecture layers are connected through interfaces. Implementations are provided during run time.
  • Application architecture is built on top of a domain model.
  • All external dependency, like database access and service calls, are represented in external layers.
  • No dependencies of the Internal layer with external layers.
  • Couplings are towards the center.
  • Flexible and sustainable and portable architecture.
  • Can be quickly tested because the application core does not depend on anything. Cons
  • Not easy to understand for beginners
  • Sometimes it is not clear how to split responsibilities between layers

Command Query Responsibility Segregation (CQRS)

Context and problem

In traditional architectures, the same data model is used to query and update a database. That’s simple and works well for basic CRUD operations. In more complex applications, however, this approach can become unwieldy. For example, on the read side, the application may perform many different queries, returning data transfer objects (DTOs) with different shapes. Object mapping can become complicated. On the write side, the model may implement complex validation and business logic. As a result, you can end up with an overly complex model that does too much.
Read and write workloads are often asymmetrical, with very different performance and scale requirements.
There is often a mismatch between the read and write representations of the data, such as additional columns or properties.

Solution

CQRS addresses separate reads and writes into separate models, using commands to update data, and queries to read data.
Commands should be task-based, rather than data-centric. (“Book hotel room,” not “set ReservationStatus to Reserved.”) Commands may be placed on a queue for asynchronous processing, rather than being processed synchronously.
Queries never modify the database. A query returns a DTO that does not encapsulate any domain knowledge.

Typical CQRS
typical CQRS

https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs

CQRS write side

MediatR

MediatR is an open source implementation of the mediator pattern. It allows you to compose messages, create and listen for events using synchronous or asynchronous patterns.

See how CQRS implementation with MediatR in ASP.NET Core allows to significantly simplify Controller's code.
Before

[HttpPost]
public async Task<IHttpActionResult> Register (RegisterBindingModel model) {
   var user = new User {
       UserName = model.Email,
       Email = model.Email,
       DateOfBirth = DateTime.Parse (model.DateOfBirth.ToString (CultureInfo.InvariantCulture)),
       PhoneNumber = model.PhoneNumber,
       Name = model.Name
   };
   var result = await UserManager.CreateAsync (user, model.Password);
   if (!result.Succeeded) return GetErrorResult (result);
   return Ok (result);
}
Enter fullscreen mode Exit fullscreen mode

After

[HttpPost]
public async Task<IHttpActionResult> Register ([FromBody] CreateUserCommand command) {
   return Ok (await Mediator.Send (command));
}
Enter fullscreen mode Exit fullscreen mode

The source of the demo can be downloaded from https://github.com/i-b1/OnionArchitectureDemo

Next Steps

Event Sourcing

https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing

Designing Microservices:

https://docs.microsoft.com/en-us/dotnet/architecture/microservices/

Thank you for reading, next step would be to consider how Onion stacks up against CATs (Cloud Application Technologies???) and microservices.
Onion vs Cat
Photo by HalGatewood.com on Unsplash

Discussion (0)