Hexagonal Architecture in Java

1. Introduction

We all have experience in building layered architecture, but the main drawback of this approach is that the core business logic is tightly coupled with the presentation and data layer. Additionally, changing any underlying technology is next to impossible.

Certainly, Hexagonal Architecture is the solution to this problem; we will learn “how” in this article.

2. Hexagonal Architecture

The main idea behind Hexagonal Architecture is to keep the core logic independent of the input and output interfaces. To achieve this, we need to bundle the main domain and business logic into one functional entity called core.

All input and output interfaces that allow users to interact with the application are called ports. There are two types of Ports:

  • Primary Ports – They trigger an event in the application through User Interface, Testing Interface
  • Secondary Ports – They provide the necessary information required by the application to execute the business logic. So, secondary ports are interfaces for utilities like repository, in-memory database, events, and notifications

The module which implements the primary port is termed as Primary Adapter and likewise, the module which is build to implement secondary port is termed as a Secondary Adapter.

hexagonal-architecture-in-java
Hexagonal Architecture

2.1. Hexagonal Architecture Example

To illustrate this architecture, let’s create a product library (a module of an E-commerce site) using spring boot and maven.

The primary goal of this library is, to create and retrieve the products. Our main domain object is Product entity:

@Entity
public class Product implements Serializable {

    private int productId;

    private String productName;

    private String productDesc;

    private String productCategory;

    private int productPrice;
}

Further, the core business logic goes in the service interface implementation of ProductService:

public interface ProductService {

    Product createProduct(Product product);

    Product getProduct(int productId);

    List<Product> getAllProducts();
}

As in this architecture, we use ports to communicate with core business logic, let’s create two of them one for API and another for data access- ProductAPIport for external system and ProductRepositoryPort for data access.

public interface ProductAPIPort {

    @PostMapping
    ResponseEntity<Product> createProduct(@RequestBody Product product);

    @GetMapping("/{productId}")
    Product getProduct(@PathVariable int productId);

    @GetMapping
    List<Product> getAllProducts();

}

Here, the ProductAPIPort is the primary port, the presentation layer connects with this port with the help of adapter ProductController:

@RestController
@RequestMapping("/products/")
public class ProductController implements ProductAPIPort {

    @Autowired
    ProductService productService;

    @Override
    public ResponseEntity<Product> createProduct(@RequestBody Product product) {

        product = productService.createProduct(product);
        return ResponseEntity.status(HttpStatus.CREATED).body(product);
    }

    @Override
    public Product getProduct(@PathVariable int productId) {
        return productService.getProduct(productId);
    }

    @Override
    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }
}

Moreover, ProductRepositoryPort is the secondary port:

public interface ProductRepositoryPort {

    Product createProduct(Product product);

    Product getProduct(int productId);

    List<Product> getAllProducts();
}

So, the Core communicates with this port using the adapter ProductRepositoryAdapter to access the database.

@Service
public class ProductRepositoryAdapter implements ProductRepositoryPort {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    @Transactional
    public Product createProduct(Product product) {
        product.setProductId(0);
        entityManager.persist(product);
        return product;
    }

    @Override
    public Product getProduct(int productId) {
        return entityManager.find(Product.class, productId);
    }

    @Override
    public List<Product> getAllProducts() {
        List<Product> retList = new ArrayList<>();

        Query qry = entityManager.createQuery("select prod from Product prod");
        retList = qry.getResultList();

        return retList;
    }
}

The code snippet is available in the Github.

2.2 Pros

  • We can take advantage of the TDD style of development as Ports are simple interfaces that expose individual behavior.
  • We can use the in-memory database for demo if our production database isn’t ready.
  • Switching between various technologies becomes easier.

3. Conclusion

To summarize, Hexagonal Architecture lets us reuse and maintain the code; Since the core logic is independent of the ports, hence we can change the underlying implementation of the port as per our requirements without touching the functionality.

4 thoughts on “Hexagonal Architecture in Java”

  1. I could not refrain from commenting. Perfectly written!| а

  2. Hi my friend! I want to say that this article is amazing, nice written and come with almost all vital infos.
    I’d like to see extra posts like this .

  3. Well I sincerely enjoyed studying it. This post offered by you is very effective for accurate planning. Ellynn Muffin Godart

Comments are closed.