Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Hexagonal Architecture in Java

DZone 's Guide to

Hexagonal Architecture in Java

Want to learn more about the hexagonal architecture?

· Java Zone ·
Free Resource

Hexagonal architecture is a model or pattern for application design. It is also known as a ports-and-adapter architecture. The core logic is embedded inside a hexagon, and the edges of the hexagon are considered the input and output.

It divides the application into the inside and outside parts. Inside parts are the core logic of the application. The outside part could be the UI, database, messaging connectors, etc. Thus, the application's business logic is isolated from outside concerns. Communication between the two happens using so-called ports and adapters.

Port

The port is simply a gateway to your application, allowing inbound and outbound flow. An inbound port exposes application functionality to the outside world. For example, a service interface exposing the core logic is an inbound port. An outbound port defines the core’s view of the outside world. A repository interface that enables communication from the core application to a data source is an outbound port.

Adapters

Adapters are an implementation of ports.

Primary Adapters

Also called driving adapters, primary adapters actually drive the application and/or invoke actions on the application using the inbound ports of application.

REST controllers or WebViews are examples of primary adapters. They use service interfaces (inbound ports of the application) to communicate with the core logic.

Secondary Adapters

Also called driven adapters, secondary adapters are implementations of the outbound port that are invoked/driven by the core application. However, since they are invoked using outbound ports, the application is not tightly coupled to the secondary adapters.

Connections to the database and external API calls are both examples of secondary adapters.

Putting it Together

Application users want to connect to the system or core application. The application has exposed the inbound ports; however, application users are presented with primary adapters, which use the inbound port to connect to the system. The system, in turn, uses the outbound port to connect to some external system for its data needs, etc. An outbound port is implemented by secondary adapters to expose a way to connect to the external system.

Let’s try to understand all of this using a simple example.

Example

We will use a Pizza Service application as an example to understand this architecture. Pizza Service has three features:

  • It allows us to create Pizza
  • List a particular Pizza (by name)
  • List all Pizzas available in Store

Implementation

The Value Object

Let’s start with the core of the application. At the center of application, you will have a Pizza object.

public class Pizza implements Serializable{

private static final long serialVersionUID = 1L;

private String name;

private String[] toppings;

  // Constructors , Getters/Setters etc.

}


Inbound port

Applying the hexagonal architecture, our core application will expose its functionality using an inbound port. Hence, we need to define the inbound port, PizzaService.

public interface PizzaService {

public void createPizza(Pizza pizza);

public Pizza getPizza(String name);

public List<Pizza> listPizza();

}


This inbound port exposes the application to the outside world.

Outbound Port

We have an external system for creating  Pizzas. The core application will use an outbound port to access this external system to create or access  Pizzas. Let’s define an outbound port, PizzaRepo.

public interface PizzaRepo {

void createPizza(Pizza pizza);

Pizza getPizza(String name);

List<Pizza> getAllPizza();

}


Primary Adapters

Applying the hexagonal architecture principles, you would have primary adapters using inbound ports. Let’s define a REST controller as our primary adapter, providing endpoints for creating and accessing Pizzas . This REST controller secondary adapter will use PizzaService to connect to the core application. The code for PizzaRestUI interface is available here.

@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();
}
}


Secondary Adapters

According to the definition above, we need an implementation of the outbound port. PizzaRepo is our outbound port; let’s implement this.

@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());
}

}


Communication From Core to DownStream System

The core application uses the outbound port to communicate to the downstream system. So, let’s implement the implementation for core service.

@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();
}

}


Running the PizzaService

The code is available on GitHub. You can download and run the application and hit the following endpoints:

POST http://localhost:8080/pizza

Input:

{
    "name" : "Margherita",
    "toppings" : ["tomato","onion","cucumber"]
}


POST http://localhost:8080/pizza

Input:

{
    "name" : "Margherita",
    "toppings" : ["tomato","onion","cucumber"]
}


  • GET http://localhost:8080/pizza/Margherita
  • GET http://localhost:8080/pizza/Veg_Exotica
  • GET http://localhost:8080/pizza

Conclusion

To conclude, the hexagonal architecture offers the following advantages:

  • It layers your classes/objects in such a way that the core logic is isolated from external elements. In our application above, we see that the external user will see REST endpoints, which internally use inbound ports to communicate with the core application. The core application uses an outbound port to communicate with the downstream system, and you achieve a higher degree of decoupling with this.
  • It enables you to replace one adapter with another without any impact on the core logic or application. For example, if you want to implement it the way PizzaRepoImpl is implemented, you just need to define a new implementation of PizzaRepo and register it using the @Repository annotation while removing the current implementation. This will lead cause no change to the 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.
Topics:
java ,hexagonal architecture ,spring ,spring boot ,rest api ,design pattern

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}