{{announcement.body}}
{{announcement.title}}

Introduction to the Hexagonal Architecture in Java

DZone 's Guide to

Introduction to the Hexagonal Architecture in Java

Learn how to implement the hexagonal architecture in your code.

· Java Zone ·
Free Resource

To start with, the hexagonal architecture is nothing but a design pattern. Every design pattern solves some problem, right? That is why it originally came into existence. So, what more does hexagonal architecture bring to the table?

Well, a very common problem is encountered whenever an application tries to interact due to huge dependencies over factors such as UI, the testing environment, DBs, external APIs, and so on.

What hexagonal architecture does is it segregates our application's core logic from the unwanted dependencies.

What Is the Hexagonal Architecture?

Dr. Alistair Cockburn coined the term hexagonal architecture. According to his idea, the application was assumed to be a hexagon (a closed body). Now, whatever is relevant to the core logic of our application gets to reside inside the hexagon and the rest can lie peacefully outside. This way, the core logic can be easily tested without worrying too much about external factors.

How Is this Achieved?

Well, the outside can interact with the inside world, and vice-versa, via the concept of what is known as 'ports and adapters.'

In our application, first, we have interfaces that become ports, and then, the implementation of those ports (interfaces) is called the adapter.

In a nutshell, ports are the inside points that need a way to integrate with the outside world. For this, ports are declared in the form of interfaces and we provide the implementation of those ports in the form of adapters to establish communication between them.

Example

Let's take an example of a grocery store service. The core business logic resides in the GroceryStoreService class. There is a user using our grocery store service; so we need the following basic functionality:

  •  Add an item to their shopping cart
  •  Delete a particular item
  •  View the list of items they have already added to the cart

The controller layer acts as an adapter, which will communicate with the inside of application via ports (or interfaces) adapter.

/**
 * Controller layer acts as the adapter to communicate with application's port(s)
 */
@RestController
public class GroceryStoreControllerAdapter {
    //Spring resolved service bean. The service bean caters to the application's core business logic
    @Autowired
    GroceryStoreServicePort groceryStoreService;

    @PostMapping(value = "/add/{itemId}")
    public void addItem(@RequestParam Long itemId){
        groceryStoreService.addItem(itemId);
    }

    @DeleteMapping("/delete/{itemId}")
    public void deleteItem(@RequestParam Long itemId){
        groceryStoreService.deleteItem(itemId);
    }

    @GetMapping("/fetch/all/items")
    public void fetchAllItems(){
        groceryStoreService.fetchAllItems();
    }
}


Next, we define the port of the application as GroceryStoreServicePort under:

/**
 * This interface acts as the port of our application
 */
public interface GroceryStoreServicePort {

    //Port that will add an item  with specified id
    void addItem(Long itemId);

    //Port that will delete an item  with specified id
    void deleteItem(Long itemId);

    //Port that will fetch all items added so far
    void fetchAllItems();
}


Since we can infer from the above code that the adapter is communicating with the applications inside, we know it is utilizing the power of interfaces presented in the form of the port. Further, we can provide an implementation layer for the port's methods, which, again, would act as an adapter to provide the business logic of our application.

This is demonstrated in the class GroceryStoreServicePortImpl via the below code snippet.

/**
 * The implementation layer for core business logic
 */
public class GroceryStoreServicePortAdapter implements GroceryStoreServicePort {
    /**
     * Spring resolved bean for repo. * It acts as port for communication with database
     */
    @Autowired
    GroceryStoreRepositoryPort groceryStoreRepositoryPort;

    @Override
    public void addItem(Long itemId) {
        GroceryCart groceryCart = new GroceryCart();
        //code to set values using standard protocol (e.g. constructors, setters,etc.)

        // using repo port to persist this instance into db
        groceryStoreRepositoryPort.save(groceryCart);
    }


    @Override
    public void deleteItem(Long itemId)
    {
        Optional<GroceryCart> groceryCartInstance = groceryStoreRepositoryPort.findById(itemId);
        //helper code... 
        groceryStoreRepositoryPort.delete(cart);
    }

    @Override public void fetchAllItems()
    {
        //using repo port to fetch all items from db
        Iterable<GroceryCart> groceryList = groceryStoreRepositoryPort.findAll();
        //code to Iterate and fetch all items {...}
    }
}


As the last step, we provide the repository layer in the application so that we are able to communicate with the database. Another port, GroceryStoreRepositoryPort, will help us achieve this:

import entities.GroceryCart;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface GroceryStoreRepositoryPort extends CrudRepository<GroceryCart, Integer> {}


Conclusion

To conclude, the hexagonal architecture in Java provides us with a way to divide the core business logic of our application from other elements. It procures an efficient methodology in the form of ports (interfaces) and adapters, which easily and effectively keep the core logic separated from the outside of the application.

Topics:
java ,hexagonal architecture ,ports ,interfaces ,design patterns ,example ,database

Opinions expressed by DZone contributors are their own.

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

{{ parent.tldr }}

{{ parent.urlSource.name }}