Neste artigo, mostrarei como implementar um aplicativo Spring Boot usando a Arquitetura Hexagonal.
Vamos construir uma simulação de conta bancária com operações de depósito e saque expostas por meio de endpoints REST.
Arquitetura Hexagonal
A arquitetura hexagonal é um estilo arquitetônico que se concentra em manter a lógica de negócios separada das preocupações externas .
O núcleo de negócios interage com outros componentes por meio de portas e adaptadores. Dessa forma, podemos mudar as tecnologias subjacentes sem ter que modificar o núcleo do aplicativo.

Application Core
Modelo de Domínio
Vamos começar com o modelo de domínio. Sua principal responsabilidade é modelar as regras de negócios. Ele também verifica se os objetos estão sempre em um estado válido:
public class BankAccount {
private Long id;
private BigDecimal balance;
// Constructor
public boolean withdraw(BigDecimal amount) {
if(balance.compareTo(amount) < 0) {
return false;
}
balance = balance.subtract(amount);
return true;
}
public void deposit(BigDecimal amount) {
balance = balance.add(amount);
}
}O modelo de domínio não deve depender de nenhuma tecnologia específica. Essa é a razão pela qual você não encontrará anotações do Spring aqui.
Portos
Agora é a hora de fazer com que nossa lógica de negócios interaja com o mundo externo. Para conseguir isso, vamos apresentar algumas portas.
Primeiro, vamos definir 2 portas de entrada. Eles são usados por componentes externos para chamar nosso aplicativo . Nesse caso, teremos um por caso de uso. Um para depósito :
public interface DepositUseCase {
void deposit(Long id, BigDecimal amount);
}E um para retirada :
public interface WithdrawUseCase {
boolean withdraw(Long id, BigDecimal amount);
}Serviço
A seguir, criaremos um serviço para unir todas as peças e conduzir a execução:
public class BankAccountService implements DepositUseCase, WithdrawUseCase {
private LoadAccountPort loadAccountPort;
private SaveAccountPort saveAccountPort;
// Constructor
@Override
public void deposit(Long id, BigDecimal amount) {
BankAccount account = loadAccountPort.load(id)
.orElseThrow(NoSuchElementException::new);
account.deposit(amount);
saveAccountPort.save(account);
}
@Override
public boolean withdraw(Long id, BigDecimal amount) {
BankAccount account = loadAccountPort.load(id)
.orElseThrow(NoSuchElementException::new);
boolean hasWithdrawn = account.withdraw(amount);
if(hasWithdrawn) {
saveAccountPort.save(account);
}
return hasWithdrawn;
}
}Observe como o serviço implementa as portas de entrada. Em cada método, ele usa a porta de carregamento para buscar a conta no banco de dados. Em seguida, realiza as alterações no modelo de domínio. E, finalmente, ele salva essas alterações por meio da porta Salvar .
Adaptadores
Rede
Para completar nosso aplicativo, precisamos fornecer implementações para as portas definidas. Chamamos esses adaptadores.
Para as interações de entrada, criaremos um controlador REST:
@RestController
@RequestMapping("/account")
public class BankAccountController {
private final DepositUseCase depositUseCase;
private final WithdrawUseCase withdrawUseCase;
// Constructor
@PostMapping(value = "/{id}/deposit/{amount}")
void deposit(@PathVariable final Long id, @PathVariable final BigDecimal amount) {
depositUseCase.deposit(id, amount);
}
@PostMapping(value = "/{id}/withdraw/{amount}")
void withdraw(@PathVariable final Long id, @PathVariable final BigDecimal amount) {
withdrawUseCase.withdraw(id, amount);
}
}O controlador usa as portas definidas para fazer chamadas ao núcleo do aplicativo.
Persistência
Para a camada de persistência, usaremos Mongo DB por meio do Spring Data:
public interface SpringDataBankAccountRepository extends MongoRepository<BankAccount, Long> {
}Além disso, criaremos uma classe BankAccountRepository que conecta as portas de saída com SpringDataBankAccountRepository :
@Component
public class BankAccountRepository implements LoadAccountPort, SaveAccountPort {
private SpringDataBankAccountRepository repository;
// Constructor
@Override
public Optional<BankAccount> load(Long id) {
return repository.findById(id);
}
@Override
public void save(BankAccount bankAccount) {
repository.save(bankAccount);
}
}A infraestrutura
Finalmente, precisamos dizer ao Spring para expor o BankAccountService como um bean, para que ele possa ser injetado no controlador:
@Configuration
@ComponentScan(basePackageClasses = HexagonalApplication.class)
public class BeanConfiguration {
@Bean
BankAccountService bankAccountService(BankAccountRepository repository) {
return new BankAccountService(repository, repository);
}
}Definir os beans na camada de Adaptadores nos ajuda a manter o código de infraestrutura desacoplado da lógica de negócios.
Conclusão
Neste artigo, vimos como implementar um aplicativo usando Arquitetura Hexagonal e Spring Boot. É assim que o sistema acaba ficando:

O exemplo desse artigo esta no meu GitHub.
