Search Tutorials


Hexagonal Architecture using Spring Boot | JavaInUse

Hexagonal Architecture using Spring Boot

What is DDD

Domain Driven Design is a software architecture to solve complex business problems. In DDD we identify the core domain and the domain logic. This approach needs continuous collaboration between developers and business experts.

What is Hexagonal Architecture

Hexagonal Architecture is the design pattern for implementing Domain Driven Design. The main advantage of this approach is that we decouple the business logic from external factors.
Domain Driven Design Spring Boot
We will be taking example of banking application using which the user can perform deposit and withdrawal functions.

Implementation

The maven project we will be creating is as follows -
Hexagonal Architecture Spring Boot

Application Core

We will first be defining the domain class. It is this class that contains all the businesss logic. Also it should be noted that this class should not have any framework related boilerplate code.
package com.javainuse.domain.model;

import java.math.BigDecimal;

public class Account {

    public long getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(long accountNo) {
        this.accountNo = accountNo;
    }

    public BigDecimal getAccountBalance() {
        return accountBalance;
    }

    public void setAccountBalance(BigDecimal accountBalance) {
        this.accountBalance = accountBalance;
    }

    private long accountNo;
    private BigDecimal accountBalance;

    public boolean withdrawAmount(BigDecimal withdrawalAmount) {
        if (accountBalance.compareTo(withdrawalAmount) < 0) {
            return false;
        }
        accountBalance = accountBalance.subtract(withdrawalAmount);
        return true;
    }

    public void depositAmount(BigDecimal depositAmount) {
        accountBalance = accountBalance.add(depositAmount);
    }
}
Next we will be defining the input and output ports. Create an interface named Deposit using which the customer can perform deposit operation.
package com.javainuse.domain.port.incoming;

import java.math.BigDecimal;

public interface Deposit {

    void deposit(Long accountNo, BigDecimal depositAmount);
}
Create an interface named Withdraw using which the customer can perform withdraw operation.
package com.javainuse.domain.port.incoming;

import java.math.BigDecimal;

public interface Withdraw {

    boolean withdraw(Long accountNo, BigDecimal withdrawalAmount);
}

Create an interface named PersistAccount using which we save the account information.
package com.javainuse.domain.port.outgoing;

import com.javainuse.domain.model.Account;

public interface PersistAccount {

    void save(Account account);
}
Create an interface named RetrieveAccount using which we can retrieve account information.
package com.javainuse.domain.port.outgoing;

import com.javainuse.domain.model.Account;

public interface RetrieveAccount {

    Account load(Long accountNo);
}
Finally we will be creating the Service class which implements the incoming ports and have instances of outgoing ports. This is the class which holds everything together.
package com.javainuse.domain.service;

import java.math.BigDecimal;

import com.javainuse.domain.model.Account;
import com.javainuse.domain.port.incoming.Deposit;
import com.javainuse.domain.port.incoming.Withdraw;
import com.javainuse.domain.port.outgoing.PersistAccount;
import com.javainuse.domain.port.outgoing.RetrieveAccount;

public class AccountService implements Deposit, Withdraw {

    private RetrieveAccount retrieveAccount;
    private PersistAccount persistAccount;

    public AccountService(RetrieveAccount retrieveAccount, PersistAccount persistAccount) {
        this.retrieveAccount = retrieveAccount;
        this.persistAccount = persistAccount;
    }

    @Override
    public void deposit(Long accountNo, BigDecimal depositAmount) {
        Account account = retrieveAccount.load(accountNo);
        account.depositAmount(depositAmount);
        persistAccount.save(account);
    }

    @Override
    public boolean withdraw(Long accountNo, BigDecimal withdrawalAmount) {
        Account account = retrieveAccount.load(accountNo);
        boolean withdrawSucess = account.withdrawAmount(withdrawalAmount);

        if (withdrawSucess) {
            persistAccount.save(account);
        }
        return withdrawSucess;
    }
}

Adapters

Next we define the controller class to expose the API for the customer-
package com.javainuse.application.controller;

import java.math.BigDecimal;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.javainuse.domain.port.incoming.Deposit;
import com.javainuse.domain.port.incoming.Withdraw;

@RestController
@RequestMapping("/account")
public class AccountController {

    private final Deposit depositUseCase;
    private final Withdraw withdrawUseCase;

    public AccountController(Deposit depositUseCase, Withdraw withdrawUseCase) {
        this.depositUseCase = depositUseCase;
        this.withdrawUseCase = withdrawUseCase;
    }

    @PostMapping(value = "/{accountNo}/deposit/{depositAmount}")
    void deposit(@PathVariable final Long accountNo, @PathVariable final BigDecimal depositAmount) {
        depositUseCase.deposit(accountNo, depositAmount);
    }

    @PostMapping(value = "/{accountNo}/withdraw/{withdrawalAmount}")
    void withdraw(@PathVariable final Long accountNo, @PathVariable final BigDecimal withdrawalAmount) {
        withdrawUseCase.withdraw(accountNo, withdrawalAmount);
    }
}

Persistence

For persistence we extend the Spring JdbcDaoSupport class.
package com.javainuse.infrastructure.adapter;

import java.sql.ResultSet;
import java.sql.SQLException;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;

import com.javainuse.domain.model.Account;

@Repository
public class SpringDataAccountRepository extends JdbcDaoSupport {

    @Autowired
    DataSource dataSource;

    @PostConstruct
    private void initialize() {
        setDataSource(dataSource);
    }

    public Account findByAccountNo(Long accountNo) {
        String sql = "SELECT * FROM account WHERE accountNo = ?";
        return (Account) getJdbcTemplate().queryForObject(sql, new Object[] { accountNo }, new RowMapper<Account>() {
            @Override
            public Account mapRow(ResultSet rs, int rwNumber) throws SQLException {
                Account account = new Account();
                account.setAccountNo(rs.getLong("accountNo"));
                account.setAccountBalance(rs.getBigDecimal("balance"));
                return account;
            }
        });
    }

    public void save(Account bankAccount) {
        String sql = "UPDATE account SET " + "balance= ? WHERE accountNo = ?";
        getJdbcTemplate().update(sql, new Object[] { bankAccount.getAccountBalance(), bankAccount.getAccountNo() });
    }
}

Download Source Code

Download it - Spring Boot + Hexagonal Architecture Example