Overview
Would we rather choose to work with the code which is properly segregated into different parts (Core logic and additional services) or the messed-up code? Well, a skilled programmer will always choose the former. The workaround for this is Hexagonal architecture.
What is the Hexagonal Architecture?
Hexagonal architecture is a design pattern in Java. It represents the application as a Hexagon. At the center of the hexagon, entire core business logic is present. The hexagonal edges act as input and output elements.
The edges could be a user interface, database connection, messaging services, web services, etc.
Therefore, the core business logic of the application is segregated from outside concerns. Here, ports and adapters act as a communication medium between core logic and external interfaces.
Ports
There are two types of ports – Inbound and Outbound.
Inbound port exposes the core logic to the external services. In other words, exposes the center of the hexagon to its edges.
However, the outbound port works as a path from external services to the core logic. The core of the application uses these ports to communicate to external services like Databases, UI, etc.
In short, the inbound port and outbound port allows the inbound and outbound flow of the application respectively.
Adapters
Adapters are the services that fit into the inbound and outbound ports.
In this code example, the primary adapter is the REST service which interacts with the inbound port i.e. the service interface.
On the other hand, the secondary adapter is the implementation of an outbound port i.e. connection to the database API with the data repository.
Implementation of Hexagonal Architecture
Let’s try to understand the Hexagonal architecture principles using an example.
Consider a Book Store application with the following features:
- Buying a book by ISBN
- Adding a book in the store
- Listing all the available books
Let’s see all the objects of this application categorized as Hexagonal principles:
The Core Logic
The core logic is present at the center of the hexagonal architecture. For example, we have a Book object as the core of the application:
public class Book {
private String name;
private String isbn;
private String author;
@Override
public String toString() {
return "Book [name=" + name + ", ISBN=" + isbn + ", author=" + author + "]";
}
}
Inbound Port
We will have a BookService to expose the core logic of the application:
public interface BookService {
public void addBook(Book book);
public Book buyBook(String isbn);
public List<Book> listBooks();
}
Outbound Port
Let’s assume, we have an external system for adding and accessing books. The core application will use an outbound port so that it can communicate to the external service:
public interface BookRepository {
public void add(Book book);
public Book buy(String isbn);
public List<Book> list();
}
Primary Adapter
We will now define the REST controller to allow the user to communicate with our application:
@RestController
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/book/add")
@PostMapping(produces = { MediaType.TEXT_PLAIN_VALUE })
public void addBook(@RequestBody Book book) {
bookService.addBook(book);
}
public Book buyBook(@PathVariable String isbn) {
return bookService.buyBook(isbn);
}
public List<Book> listBooks() {
return bookService.listBooks();
}
}
Secondary Adapter
Now, we will implement the outbound port. In this application, BookRepositoryImpl is our secondary adapter:
@Repository
public class BookRepositoryImpl implements BookRepository {
private Map<String, Book> bookMap = new HashMap<>();
@Override
public void add(Book book) {
bookMap.put(book.getIsbn(), book);
}
@Override
public Book buy(String isbn) {
return bookMap.get(isbn);
}
@Override
public List<Book> list() {
return bookMap.values()
.stream()
.collect(Collectors.toList());
}
}
Accessing Database API
Let’s provide an implementation for the core service BookService:
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookRepository bookRepository;
@Override
public void addBook(Book book) {
bookRepository.add(book);
}
@Override
public Book buyBook(String isbn) {
return bookRepository.buy(isbn);
}
@Override
public List<Book> listBooks() {
return bookRepository.list();
}
}
Advantages
- The core logic is isolated from the external services as a result code is less cohesive
- Replacement of one adapter with the other is easy without affecting the core logic
- Following the hexagonal architecture, the application becomes very flexible to connect with other channels
- The higher degree of maintainability is achieved as a result of segregation of code into the inside and outside parts
Conclusion
In this article, we have explored the principles of Java Hexagonal architecture with the help of an example.
To sum up, Hexagonal architecture divides the code into the inside and outside parts which result in a lot of advantages including better flexibility and maintainability.
The post Hexagonal Architecture in Java appeared first on TechBlogStation.