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.