1. Introduction
Hexagonal Architecture is a model or pattern for application design. It is also known as Ports & Adapter Architecture. The Core logic is embedded inside a Hexagon. Edges of Hexagon are considered as input and output.
It divides the application into inside and outside parts. Inside parts is the core logic of the application.Outside part could be UI, Database, Messaging Connectors, etc. Thus, application business logic is isolated from outside concerns. Communication between two happen using so called ports & adapters.
1.1 Port
Port is nothing but a gateway to your application. It allows inbound & outbound flow. A inbound port exposes application functionality to the outside world . For example, a service Interface exposing the core logic is an inbound port. A outbound port define core’s view of outside world. A Repository interface enabling communication from core application to a data source is an outbound port.
1.2 Adapters
Adapters are implementation of ports.
1.2.1 Primary Adapters
They are also called as Driving Adapters. They actually drive the application or invoke actions on application. They use the inbound ports of application.
Rest Controllers or Web Views are example of Primary adapters .They use service interfaces( inbound ports of application) to communicate to the core logic.
1.2.2 Secondary Adapters
They are also called as Driven Adapters. They are implementations of outbound port. They are invoked /driven by core application. However, since they are invoked using outbound ports , the application is not tightly coupled to the Secondary Adapters.
Connections to Database, external API calls are examples of Secondary Adapters.
2. Putting it together
Application user wants to connect to the System or core application. Application has exposed the inbound ports however application users are presented with Primary adapters which in turn uses the inbound port to connect to the System. System in turn uses outbound port to connect to some external system for its data needs etc. Outbound port is implemented by Secondary adapters to expose a way to connect to the external system.
Let’s try to understand all this using a simple example.
3. Example
We will use Pizza Service application as an example to understand this architecture. Pizza Service has 3 features:
- It allows to create Pizza
- List a particular Pizza ( by name)
- List all Pizzas available in Store.
4. Implementation
4.1 The Value Object
Let’ start with Core of the Application. At the centre of application, you will have a Pizza Object.
1
2
3
4
5
6
7
8
9
| public class Pizza implements Serializable{ private static final long serialVersionUID = 1L; private String name; private String[] toppings; //Constructors and Setters/Getters |
4.2 Inbound port
Applying Hexagonal Architecture, core application will expose its functionality using an inbound port. Hence, let’s define the inbound port, PizzaService.
1
2
3
4
5
6
7
8
9
| public interface PizzaService { public void createPizza(Pizza Pizza); public Pizza getPizza(String name); public List<Pizza> listPizza(); } |
This inbound port exposes the application to outside world.
4.3 Outbound Port
We have an external system for creating Pizzas. The core Application will use a outbound port to access this external system to create or access Pizzas. Let’s define outbound port , PizzaRepo.
1
2
3
4
5
6
7
8
9
| public interface PizzaRepo { void createPizza(Pizza pizza); Pizza getPizza(String name); List<Pizza> getAllPizza(); } |
4.4 Primary Adapters
Applying Hexagonal Architecture principles again, you would have a primary adapters using inbound ports. Let’s define a RestController as our primary adapters providing endpoints for creating & accessing Pizzas . This RestController Secondary adapter will use PizzaService to connect to the core application.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| @RestController @RequestMapping ( "/pizza" ) public class PizzaRestController implements PizzaRestUI { @Autowired private PizzaService pizzaService; @Override public void createPizza( @RequestBody Pizza pizza) { pizzaService.createPizza(pizza); } @Override public Pizza getPizza( @PathVariable String name) { return pizzaService.getPizza(name); } @Override public List<Pizza> listPizza() { return pizzaService.listPizza(); } } |
4.5 Secondary Adapters
Going by definition above, they are implementation of outbound port . PizzaRepo is our outbound port , let’s implement this.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| @Repository public class PizzaRepoImpl implements PizzaRepo { private Map<String, Pizza> pizzaStore = new HashMap<String, Pizza>(); @Override public void createPizza(Pizza pizza) { pizzaStore.put(pizza.getName(), pizza); } @Override public Pizza getPizza(String name) { return pizzaStore.get(name); } @Override public List<Pizza> getAllPizza() { return pizzaStore.values().stream().collect(Collectors.toList()); } } |
4.6 Communication from Core to DownStream System
Core application uses outbound port to communicate to downstream system. So let’s implement the implementation for core service.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| @Service public class PizzaServiceImpl implements PizzaService { @Autowired private PizzaRepo pizzaRepo; @Override public void createPizza(Pizza pizza) { pizzaRepo.createPizza(pizza); } @Override public Pizza getPizza(String name) { return pizzaRepo.getPizza(name); } @Override public List<Pizza> listPizza() { return pizzaRepo.getAllPizza(); } } |
5. Running the PizzaService
The code is available here in github. You can download and run the application and hit following endpoints :
- POST http://localhost:8080/pizza
Input :
1
2
3
4
| { "name" : "Margherita", "toppings" : ["tomato","onion","cucumber"] } |
1
|
- POST http://localhost:8080/pizza
Input :
1
2
3
4
| { "name" : "Veg_Exotica", "toppings" : ["tomato","cucumber"] } |
- GET http://localhost:8080/pizza/Margherita
- GET http://localhost:8080/pizza/Veg_Exotica
- GET http://localhost:8080/pizza
6. Conclusion
To conclude, Hexagonal Architecture offers following advantages :
- It layers your classes/objects in such a way that Core logic is isolated from external elements. In our application above, we see that external user will see Rest Endpoints which internally uses inbound port to communicate to core application. Core application uses outbound port to communicate to downstream system, you achieve a higher degree of decoupling with this.
- It enables to replace one adapter with another without any impact to core logic or application. For example, if you want to the way PizzaRepoImpl is implemented, you just need to define new implementation of PizzaRepo and register it using @Repository annotation while remove current implementation. This will lead to no change to PizzaServiceImpl (Core logic).
- Testing is eased out as you can easily mock the ports using Mock Adapters.
- The Ports based Architecture makes your application very flexible to adapt or connect to new channels or use new communication protocols.
No comments:
Post a Comment