Skip to content

Commit 9f4e186

Browse files
authored
Merge pull request iluwatar#487 from iluwatar/hexagonal-blog
Hexagonal blog
2 parents 4a55649 + cca4034 commit 9f4e186

5 files changed

+160
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
---
2+
layout: post
3+
title: Build Maintainable Systems With Hexagonal Architecture
4+
author: ilu
5+
---
6+
7+
![Hexagonal Architecture]({{ site.url }}{{ site.baseurl }}/assets/hexagonal-architecture.png)
8+
9+
## The fallacies of layered architecture
10+
11+
This blog post is about implementing Alistair Cockburn's [Hexagonal Architecture](http://alistair.cockburn.us/Hexagonal+architecture). To have something familiar to start with let's first talk about Layered Architecture. It is a well known architectural pattern that organizes application into layers each having their specific purpose. The database layer takes care of data transactions, the business layer is responsible for business logic and the presentation layer deals with the user input. The Layered Architecture implements so called separation of concerns principle which leads to more maintainable applications. Changes to one area in the software do not affect the other areas.
12+
13+
![Layers]({{ site.url }}{{ site.baseurl }}/assets/layers.png)
14+
15+
This way of building applications can be considered simple and effective. But it also has several drawbacks. When you see an application implemented with Layered Architecture, where is the application core? Is it the database? Maybe it's the business logic with some little things scattered over to the presentation layer. This is the typical problem with layers. There is no application core, there are just the layers and the core logic is scattered here and there. When the business logic starts to leak over to the presentation layer, the application can no longer be tested without the user interface.
16+
17+
## Core, ports and adapters
18+
19+
Hexagonal Architecture tackles this issue by building the application around the core. The main objective is to create fully testable systems that can be driven equally by users, programs and batch scripts in isolation of database.
20+
21+
However, the core alone may not be very useful. Something has to drive this application, call the business logic methods. It may be a HTTP request, automatic test or integration API. These interfaces we call the primary ports. Also, the core has its dependencies. For example, there may be a data storage module that the core calls upon to retrieve and update data. The interfaces of these modules that are driven by the core are called the secondary ports of the application.
22+
23+
Each of the ports may have one or more implementation. For example there may be a mock database for testing and a real database for running the application. The port implementations are called adapters. Thus the alias name Ports and Adapters for Hexagonal Architecture. Other architectural patterns describing the same concept are Uncle Bob's [Clean Architecture](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html) and Jeffrey Palermo's [Onion Architecture](http://jeffreypalermo.com/blog/the-onion-architecture-part-1/).
24+
25+
The name of the pattern comes from the following hexagonal drawing.
26+
27+
![Hexagon]({{ site.url }}{{ site.baseurl }}/assets/hexagon.png)
28+
29+
The diagram shows how the domain is in the middle surrounded by ports on each side of hexagon. The actual amount of ports does not have to be exactly six, it can be less or it can be more depending on the application needs.
30+
31+
Naked Objects design pattern is considered an implementation of Hexagonal Architecture. Naked Objects is utilized by [Apache Isis](https://isis.apache.org/) framework. User defines the domain objects and the framework automatically generates user interface and REST API around it.
32+
33+
## Lottery system
34+
35+
Next we will demonstrate Hexagonal Architecture by building a lottery system. The lottery system will provide two primary ports: One for the users to submit lottery tickets and another for system administrators to perform the draw.
36+
37+
The secondary ports consist of lottery ticket database, banking for wire transfers and event log for handling and storing lottery events. The resulting hexagon of the system can be seen in the following diagram.
38+
39+
![Lottery system]({{ site.url }}{{ site.baseurl }}/assets/lottery.png)
40+
41+
## Start from the core concepts
42+
43+
We start the implementation from the system core. First we need to identify the core concepts of the lottery system. Probably the most important one is the lottery ticket. In lottery ticket you are supposed to pick the numbers and provide your contact details. This leads us to write the following classes.
44+
45+
<script src="http://gist-it.appspot.com/http://github.com/iluwatar/java-design-patterns/raw/master/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java?slice=24:44"></script>
46+
<script src="http://gist-it.appspot.com/http://github.com/iluwatar/java-design-patterns/raw/master/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java?slice=32:144"></script>
47+
48+
`LotteryTicket` contains `LotteryNumbers` and `PlayerDetails`. `LotteryNumbers` contains means to hold given numbers or generate random numbers and test the numbers for equality with another `LotteryNumbers` instance. [PlayerDetails](https://github.com/iluwatar/java-design-patterns/blob/master/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java) is a simple value object containing player's email address, bank account number and phone number.
49+
50+
## The core business logic
51+
52+
Now that we have the nouns presenting our core concepts we need to implement the core business logic that defines how the system works. In `LotteryAdministration` and `LotteryService` classes we write the methods that are needed by the lottery players and system administrators.
53+
54+
<script src="http://gist-it.appspot.com/http://github.com/iluwatar/java-design-patterns/raw/master/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java?slice=31:"></script>
55+
56+
For administrators `LotteryAdministration` has `resetLottery()` method for starting a new lottery round. At this stage the players submit their lottery tickets into the database and when the time is due the administration calls `performLottery()` to draw the winning numbers and check each of the tickets for winnings.
57+
58+
<script src="http://gist-it.appspot.com/http://github.com/iluwatar/java-design-patterns/raw/master/hexagonal/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java?slice=31:"></script>
59+
60+
The lottery players use `submitTicket()` to submit tickets for lottery round. After the draw has been performed `checkTicketForPrize()` tells the players whether they have won.
61+
62+
`LotteryAdministration` and `LotteryService` have dependencies to lottery ticket database, banking and event log ports. We use [Guice](https://github.com/google/guice) dependency injection framework to provide the correct implementation classes for each purpose. The core logic is tested in [LotteryTest](https://github.com/iluwatar/java-design-patterns/blob/master/hexagonal/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java).
63+
64+
## Primary port for the players
65+
66+
Now that the core implementation is ready we need to define the primary port for the players. We introduce `ConsoleLottery` class to provide command line interface that allows players to interact with the lottery system.
67+
68+
<script src="http://gist-it.appspot.com/http://github.com/iluwatar/java-design-patterns/raw/master/hexagonal/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java?slice=41:"></script>
69+
70+
It has commands to view and transfer bank account funds, submit and check lottery tickets.
71+
72+
## Primary port for the administrators
73+
74+
We also need to define the lottery administrator facing port. This is another command line interface named `ConsoleAdministration`.
75+
76+
<script src="http://gist-it.appspot.com/http://github.com/iluwatar/java-design-patterns/raw/master/hexagonal/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java?slice=35:"></script>
77+
78+
The interface's commands allow us to view submitted tickets, perform the lottery draw and reset the lottery ticket database.
79+
80+
## Secondary port for banking
81+
82+
Next we implement the secondary ports and adapters. The first one is the banking support that enables us to manipulate bank account funds. To explain the concept, the player can write his bank account number on the lottery ticket and in case the ticket wins the lottery system automatically wire transfers the funds.
83+
84+
<script src="http://gist-it.appspot.com/http://github.com/iluwatar/java-design-patterns/raw/master/hexagonal/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java?slice=24:"></script>
85+
86+
The banking port has two adapters for different purposes. The first one `InMemoryBank` is a simple `HashMap` based implementation for testing. The lottery service's bank account is statically initialized to contain enough funds to pay the prizes in case some of the lottery tickets win.
87+
88+
The other adapter `MongoBank` is based on Mongo and is intended for production use. Running either one of the command line interfaces use this adapter.
89+
90+
## Secondary port for event log
91+
92+
Another secondary port is the lottery event log. Events are sent as the players submit their lottery tickets and when the draw is performed.
93+
94+
<script src="http://gist-it.appspot.com/http://github.com/iluwatar/java-design-patterns/raw/master/hexagonal/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java?slice=26:"></script>
95+
96+
We have two adapters for this port: The first one `StdOutEventLog` is for testing and simply sends the events to standard output. The second `MongoEventLog` is more sophisticated, has persistent storage and is based on Mongo.
97+
98+
## Secondary port for database
99+
100+
The last secondary port is the database. It contains methods for storing and retrieving lottery tickets.
101+
102+
<script src="http://gist-it.appspot.com/http://github.com/iluwatar/java-design-patterns/raw/master/hexagonal/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java?slice=30:"></script>
103+
104+
The port has two adapters. The `LotteryTicketInMemoryRepository` is a mock database holding its contents in memory only and is meant for testing. The `MongoTicketRepository` is used for production runs and provides persistent storage over application restarts.
105+
106+
## Lottery application
107+
108+
With all the pieces in place we create a command line application to drive the lottery system. The test application begins the lottery round using the administration methods and starts collecting lottery tickets from the players. Once all the lottery tickets have been submitted the lottery number draw is performed and all the submitted tickets are checked for wins.
109+
110+
<script src="http://gist-it.appspot.com/http://github.com/iluwatar/java-design-patterns/raw/master/hexagonal/src/main/java/com/iluwatar/hexagonal/App.java?slice=62:"></script>
111+
112+
Running the test application produces the following output:
113+
114+
```
115+
Lottery ticket for [email protected] was submitted. Bank account 265-748 was charged for 3 credits.
116+
Lottery ticket for [email protected] was submitted. Bank account 024-653 was charged for 3 credits.
117+
Lottery ticket for [email protected] was submitted. Bank account 842-404 was charged for 3 credits.
118+
Lottery ticket for [email protected] was submitted. Bank account 663-765 was charged for 3 credits.
119+
Lottery ticket for [email protected] was submitted. Bank account 225-946 was charged for 3 credits.
120+
Lottery ticket for [email protected] was submitted. Bank account 023-638 was charged for 3 credits.
121+
Lottery ticket for [email protected] was submitted. Bank account 983-322 was charged for 3 credits.
122+
Lottery ticket for [email protected] was submitted. Bank account 934-734 was charged for 3 credits.
123+
Lottery ticket for [email protected] was submitted. Bank account 735-964 was charged for 3 credits.
124+
Lottery ticket for [email protected] was submitted. Bank account 425-907 was charged for 3 credits.
125+
Lottery ticket for [email protected] was submitted. Bank account 225-946 was charged for 3 credits.
126+
Lottery ticket for [email protected] was submitted. Bank account 265-748 was charged for 3 credits.
127+
Lottery ticket for [email protected] was submitted. Bank account 946-384 was charged for 3 credits.
128+
Lottery ticket for [email protected] was submitted. Bank account 425-907 was charged for 3 credits.
129+
Lottery ticket for [email protected] could not be submitted because the credit transfer of 3 credits failed.
130+
Lottery ticket for [email protected] was submitted. Bank account 833-836 was charged for 3 credits.
131+
Lottery ticket for [email protected] was submitted. Bank account 284-936 was charged for 3 credits.
132+
Lottery ticket for [email protected] was submitted. Bank account 335-886 was charged for 3 credits.
133+
Lottery ticket for [email protected] was submitted. Bank account 310-992 was charged for 3 credits.
134+
Lottery ticket for [email protected] was submitted. Bank account 234-987 was charged for 3 credits.
135+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
136+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
137+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
138+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
139+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
140+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
141+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
142+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
143+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
144+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
145+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
146+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
147+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
148+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
149+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
150+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
151+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
152+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
153+
Lottery ticket for [email protected] was checked and unfortunately did not win this time.
154+
```
155+
156+
## Final words
157+
158+
Applications implemented with Hexagonal Architecture are a joy to maintain and work with. Implementation details such as frameworks, user interfaces and databases are pushed out of the core and the application can work without them. We can clearly point in the center of the hexagon and say that this is our application and it uses these technologies to implement the submodule interfaces. Restricting communication to happen only through the ports forces the application to produce testable and maintainable code.
159+
160+
The full demo application of Hexagonal Architecture is available in [Java Design Patterns](https://github.com/iluwatar/java-design-patterns) Github repository.

assets/hexagon.png

44.2 KB
Loading

assets/hexagonal-architecture.png

242 KB
Loading

assets/layers.png

15.5 KB
Loading

assets/lottery.png

35.3 KB
Loading

0 commit comments

Comments
 (0)