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.
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.
I could not refrain from commenting. Perfectly written!| а
Thank you so much 🙂
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 .
Well I sincerely enjoyed studying it. This post offered by you is very effective for accurate planning. Ellynn Muffin Godart