Professional Documents
Culture Documents
English Class Presentation - Arturo Ruvalcaba (Sohnen) Onion Architecture
English Class Presentation - Arturo Ruvalcaba (Sohnen) Onion Architecture
English Class Presentation - Arturo Ruvalcaba (Sohnen) Onion Architecture
Onion Architecture was introduced by Jeffrey Palermo to provide a better way to build
applications in perspective of better testability, maintainability, and dependability.
Onion Architecture
Domain entities are the core and center part. Onion architecture is built on a domain model in which
layers are connected through interfaces. The idea is to keep external dependencies as far outward as
possible where domain entities and business rules form the core part of the architecture.
Dependency
The circles represent different layers of responsibility. In general, the deeper we dive, the closer we get to
the domain and business rules. The outer circles represent mechanisms and the inner circles represent
core domain logic. The outer layers depend on inner layers and the inner layers are completely unaware
of outer circles. Classes, methods, variables, and source code in general belonging to the outer circle depends
on the inner circle but not vice versa.
Data formats/structures may vary from layers. Outer layer data formats should not be used by inner layers. E.g.
Data formats used in an API can vary from those used in a DB for persistence. Data flow can use data transfer
objects. Whenever data crosses layers/boundaries, it should be in a form that is convenient for that layer. E.g.
API’s can have DTO’s, DB layer can have Entity Objects depending on how objects stored in a database vary
from the domain model.
Data encapsulation
Each layer/circle encapsulates or hides internal implementation
details and exposes an interface to the outer layer. All layers
also need to provide information that is conveniently consumed by
inner layers. The goal is to minimize coupling between layers
and maximize coupling within a vertical slice across layers. We
define abstract interfaces at deeper layers and provide their
concrete implementation at the outermost layer. This ensures we
focus on the domain model without worrying too much about
implementation details. We can also use dependency injection
frameworks, like Spring, to connect interfaces with implementation
at runtime. E.g. Repositories used in the domain and external
services used in Application Services are implemented at the
infrastructure layer. Data Encapsulation in Onion Architecture
Separation of concerns
Application is divided into layers where each layer has a set of responsibilities and addresses separate
concerns. Each layer acts as modules/package/namespace within the application.
Coupling
Low coupling in which one module interacts with another module and does not need to be concerned with
the other module’s internals. All the internal layers need not be concerned about internal implementation
of external layers.
Onion Architecture Layers
Domain Model/Entities
Domain Entities are the fundamental building block of Domain-Driven
Design and they’re used to encapsulate attributes and entity behavior. It
is supposed to be independent of specific technologies like databases or web
APIs. E.g. In the Orders domain. Order is an entity and has attributes like
OrderId, Address, UserInfo, OrderItems, PricingInfo and behavior like
AddOrderItems, GetPricingInfo, ValidateOrder, etc. Order entity class
Domain services
Domain services are responsible for holding domain logic and business rules. All the business logic should
be implemented as a part of domain services. They are NOT typically CRUD services and are usually
standalone services. Domain services are responsible for complex business rules like computing pricing and tax
information when processing order, Order repository interface for saving and updating order, Inventory
Interface for updating information about items purchased, etc.
It consists of algorithms that are essential to its purpose and implement the use cases that are the heart of
the application.
Application services
Also referred to as “Use Cases”, are services responsible for just orchestrating steps for requests and
should not have any business logic. Application Services interact with other services to fulfil the client’s
request. Let’s consider the use case to create an order with a list of items. We first need to calculate the price
including tax computation/discounts, etc., save order items and send order confirmation notification to the
customer. Pricing computation should be part of the domain service, but orchestration involving pricing
computation, checking availability, saving order and notifying users should be part of the application service.
The application services can be only invoked by Infrastructure services.
Infrastructure services
Also referred to as Infrastructure adapters. These services are responsible for interacting with the
external world and do not solve any domain problem. These services just communicate with external
resources and don’t have any logic. E.g. External notification Service, GRPC Server endpoint, Kafka event
stream adapter, database adapters.
When receiving a create order request, we would like to validate the order, save the order in the database,
update inventory for all order items, debit order amount and lastly send a notification to the customer about
order completion.
Onion Architecture Implementation/Testing
Implementation
No direction is provided by the Onion Architecture guidelines about how the layers should be implemented.
The architect should decide the implementation and is free to choose whatever level of class, package, module,
or whatever else is required to add in the solution.
Testing Strategy
Different layers of onion architecture have a different set of responsibilities and accordingly, there are different
testing strategies.
Conclusion
Onion architecture might seem hard in beginning but is widely accepted in the industry. It is a powerful
architecture and enables easy evolution of software. By separating the application into layers, the system
becomes more testable, maintainable and portable. It helps easy adoption of new frameworks/technologies
when old frameworks become obsolete.