A Practical Example of Hexagonal Architecture in Java

Hexagonal architecture is an application design pattern. It solves some problems of the layered architecture by introducing ports-and-adapter for the dependencies between our components of the application toward our domain objects. The domain objects are the core part of the application and it is the part of inside a hexagon. And other parts such as web interface, DB, messaging systems, etc are outside of a hexagon.

Hexagonal Architecture Diagram

Hexagonal Architecture in Java

Let’s discuss in details each of the stereotypes in this architecture style.

Core application part

Domain Objects are the core parts of an application. These have business rules and validations and also have state and behaviour. This core application part doesn’t have any outward dependency. These are pure core business logic services. Domain objects will be changed when the business requirement will be changed otherwise they never affect the changes in other layers.

Let’s see the following domain class Account of the core application, it has account-related information and business validations.

public class Account{
	
	private String accountHolderName;
	private Long accountNumber;
	private String bankName;
	
	// Constructors , Getters/Setters etc.
}

Inbound and outbound ports

In the Hexagonal architecture pattern, the ports provide the flow to the application from outside and inside.

Inbound ports

An inbound port provides the flow and the application functionality to the outside. An inbound port is a service interface that exposes the core logic and can be called by outside components. You can see the following example of an inbound port:

public interface AccountService { 
	void createAccount(Account account); 
	Account getAccount(Long accountNumber); 
	List<Account> allAccounts(); 
}

Outbound ports

An outbound port provides the outside functionality or interface. The core application calls this output port as per requirement such as external database call etc. For example, a simple repository interface AccountRepository that provides a port to enable communication from the core application to a database. This simple repository interface is an outbound port. Let’s see the following example of an outbound port:

public interface AccountRepository { 
     void createAccount(Account account); 
     Account getAccount(Long accountNumber); 
     List allAccounts();
 }

Adapters

Adapters are nothing but these are the implementation of inbound and outbound ports. The adapters from the outside of the hexagonal architecture and they are not part of the core application. They only interact with the core application from outside by using inbound and outbound ports.

Input adapters

The input adapters are also known as primary or driving adapters. These drive the application by invoking actions on the application using the inbound ports of application.

For example, the AccountController provides REST APIs or web interfaces as the input adapters. The REST controllers use the service interfaces (inbound ports) to interact with the core part of the business logic of the application.

@RestController 
@RequestMapping("/account") 
public class AccountController{ 
	@Autowired private AccountService accountService; 
	
	@PostMapping 
	public void createAccount(@RequestBody Account account) { 
		accountService.createAccount(account); 
	} 
	
	@GetMapping("/{accountNumber}") 
	public Account getAccount(@PathVariable Long accountNumber) { 
		return accountService.getAccount(accountNumber);		
	} 
	
	@GetMapping 
	public List<Account> allAccounts() { 
		return accountService.allAccounts(); 
	} 
}

Output adapters

The output adapters are also known as secondary or driven adapters. These are the implementation of the outbound ports. These are driven by the core application using the outbound ports to find the connections to the database and external APIs.

For example, the AccountRepositoryImpl provides an interface to the core application to communicate to external dependency such as the database.

@Repository 
public class AccountRepositoryImpl implements AccountRepository { 
	
	private Map<Long, Account> accountDB = new HashMap<Long, Account>(); 
	
	@Override 
	public void createAccount(Account account) { 
		accountDB.put(account.getAccountNumber(), account); 
	} 
	
	@Override 
	public Account getAccount(Long accountNumber) { 
		return accountDB.get(accountNumber); 
	} 
	
	@Override 
	public List<Account> allAccounts() { 
		return accountDB.values().stream().collect(Collectors.toList()); 
	} 
}

Use cases of a core application

In the Hexagonal architecture, the use cases and business domain objects are inside of the hexagonal. The use cases are nothing but it is specific use case implementation of the inbound port to communication from the core to the downstream system. Let’s see the following use case implementation AccountServiceImpl provides a use case for a specific requirement:

@Service 
public class AccountServiceImpl implements AccountService { 

	@Autowired 
	private AccountRepository accountRepository; 

	@Override 
	public void createAccount(Account account) { 
		accountRepository.createAccount(account); 
	} 
	
	@Override 
	public Account getAccount(Long accountNumber) { 
		return accountRepository.getAccount(accountNumber); 
	} 
	
	@Override 
	public List<Account> allAccounts() { 
		return accountRepository.allAccounts(); 
	} 
}

Conclusion

In the article, we have discussed the Hexagonal application architecture with a quick example in Java. This architecture focuses to simplify application design with external and internal dependencies.

Previous