Hexagonal Architecture

Hexagonal Architecture

Now a days technology is evolving so fast that any life span of any stack in an application has reduced to 5-6 years, either we move to a new stack or we upgrade the stack to have the latest version of the existing stack. In a nutshell, our design will be decoupled. Let us discuss one such architecture design which gives us this flexibility.

In the Hexagonal Architecture, the core business layer interacts with other layers through ports and adapters. Since we have decoupled the core logic layer with the external layer, we can change the underlying technologies during the requirements. I'll try to demonstrate this through code.

No alt text provided for this image

We have to understand the core logic and then segregate our core layers with the external layers. External layers will connect to the code layer via the inbound layer, and we can connect external sources with an adapter in the outbound layer. I'll be demonstrating different layers of simple card application. Application Layer will contain (Domain layer and Inbound layer) the core business logic and the core business logic, in turn, will have a domain and business-related rules. The Adapter layer (web interface, persistence layer) will have the REST Controllers and repositories for database-related tasks, or any third-party tasks.

The Domain layer will have a domain model. This model verifies the business rules related to the domain. The domain model is not dependent on technology and is not directly linked to the persistence layer. This gives us the flexibility to have a decoupled persistence layer.

@AllArgsConstructor
public class Card {
    private Long id;
    private BigDecimal balance;
    private BigDecimal creditBalance;
    private Boolean isDebit;

    public boolean swipe(BigDecimal amount, boolean isCash) {
        if (isDebit) {
            return debitSwipe(amount);
        } else {
            return creditSwipe(amount, isCash);
        }
    }
 
    public void pay(BigDecimal amount) {
        balance = balance.add(amount);
    }

    public CardEntity convertModelToEntity(){
        return new CardEntity(id, balance, creditBalance,isDebit);
    }
}

The core business logic interacts with the outside world via the inbound layer, and to achieve this, we've introduced a couple of ports. Now, we'll define the incoming ports in the inbound layer, where one will have the write access, and the other will have the read access. These ports will be used by external components to call our application.

public interface PaymentCase {
    void pay(Long id, BigDecimal amount);
}

public interface SwipeCase {
    boolean swipe(Long id, BigDecimal amount, boolean isCash);
}

Similarly, we will also have two outgoing ports; one for reading and another for writing. These are for our application to interact with the database / third party service.

public interface ReadPort {
    Optional<Card> load(Long id);
}

public interface WritePort {
    void save(Card card);
}

Then we'll create a service that will add all the pieces together and drive the execution of the incoming requests.

@RequiredArgsConstructor
public class CardService implements PaymentCase, SwipeCase {

    final private ReadPort readPort;
    final private WritePort writePort;

    @Override
    public void pay(Long id, BigDecimal amount) {
        CardEntity cardEntity = readPort.load(id)
                .orElseThrow(NoSuchElementException::new);
        Card card = cardEntity.entityToModel();
        card.pay(amount);
        writePort.save(card.convertModelToEntity());
    }

    @Override
    public boolean swipe(Long id, BigDecimal amount, boolean isCash) {
        CardEntity cardEntity = readPort.load(id)
                .orElseThrow(NoSuchElementException::new);
        Card card = cardEntity.entityToModel();
        boolean hasSwiped = card.swipe(amount, isCash);
        if (hasSwiped) {
            writePort.save(card.convertModelToEntity());
        }
        return hasSwiped;
    }
}

The above code snippet shows how the service implements the inbound ports; it uses one port to read the details from the database, and other WritePort for writing the updates to the database. Also, it applies the necessary changes in our domain objects based on request.

Now we will have the REST Controller which acts as an outside gateway for the incoming requests. We will create adapters that will be used for incoming interactions for the REST controller. The controller uses the defined ports to interact with the core domain layer.

@RequiredArgsConstructor
@RestController
@RequestMapping("/card")
public class CardController {

    private final PaymentCase paymentCase;
    private final SwipeCase swipeCase;

    @PostMapping(value = "/{id}/pay/{amount}")
    void pay(@PathVariable final Long id, @PathVariable final BigDecimal amount) {
        paymentCase.pay(id, amount);
    }

    @PostMapping(value = "/{id}/swipe")
    void swipe(@PathVariable final Long id, @RequestBody SwipeRequest swipeRequest) {
        swipeCase.swipe(id, swipeRequest.getAmount(), swipeRequest.isCash());
    }
}

The persistence layer uses Spring Data and connects to MySQL, we will create a repo that connects the outgoing ports.

@Component
@RequiredArgsConstructor
public class CardImplRepo implements ReadPort, WritePort {

    final private CardRepo repository;

    @Override
    public Optional load(Long id) {
        return repository.findById(id);
    }

    @Override
    public void save(CardEntity card) {
        repository.save(card);
    }
}

I hope this article explains the decoupled layered architecture principle. Please let me know your views and do drop in your feedback.

To view or add a comment, sign in

More articles by Amitabh Tiwari

  • Are we biased towards microservices ???

    Today, when we talk about any product in development and each and every product, is based on microservice architecture,…

    3 Comments
  • Small changes make application more responsive !!!

    Have you ever thought that small changes can make your application more responsive? As incredible it is as it sounds!…

    5 Comments

Others also viewed

Explore content categories