Hexagonal Architecture in Java Simplified

1. Overview

In this tutorial, we will implement a simple Java CMS application with a CLI consumer by using the principles of Hexagonal Architecture. The main idea will be to keep business logic as separate as possible and use the Dependency Inversion principle which is “D” in SOLID principle to prevent coupling between layers.

2. What is Hexagonal Architecture?

It is a way of designing a software application architecture around business logic and decouple it from other layers. Decoupling is handled by using ports and adapters, that is why Hexagonal Architecture is also called Ports & Adapters.

You can see a couple of key characteristics of a typical application layered by Hexagonal Architecture;

  • You can defined ports to state what you can do with a specific domain object, and these ports should be interfaces
  • There are 2 types of ports; inbound(or driver) ports, and outbound (or driven) ports
  • Adapters are a different kinds of implementations of ports
  • There are 2 types of adapters; inbound(or driver) adapters, and outbound (or driven) adapters
  • Domain objects never depend on external layers.

3. Organizing Project Structure

The project contains 2 root packages:

  • domain for domain objects, ports, and usecases to state the contract and behaviour of domain object
  • infrastructure mostly contains adapter implementations for input and output ports

For each domain object, article in our case, we have a package to keep ports and usecases within domain package. In infrastructure package, we have adapter package for each domain object.

4. Domain Models

Our domain model, Article is for stating the article information and related implementation is as follows;

public record Article(
    Long id,
    Long accountId,
    String title,
    String body
) {}

This is a simple Java POJO class to state article information, and let’s see how we can write contracts within Port interface

5. Ports

If you want to define a port that contains create, retrieve, and query behavior, following interface would be a good candidate;

public interface ArticlePort {

    Article create(ArticleCreate articleCreate);

    Article retrieve(Long articleId);

    List<Article> query(ArticleQuery articleQuery);
}

When you look at the above interface, you can understand that return value is a domain model, but about function arguments?

6. Use Cases

Applications consist of one or more use cases, and you can use ports as a dependency within these use cases while you are defining steps to meet the requirements in terms of business logic. Here we say ports since consumers will not use them directly, implementation will be used instead.

public class ArticleRetrieveUseCase {

    final ArticlePort articlePort;

    public ArticleRetrieveUseCase(ArticlePort articlePort) {
        this.articlePort = articlePort;
    }

    public Article retrieve(ArticleRetrieve useCase){
        return this.articlePort.retrieve(useCase.id());
    }
}

Within ArticleRetrieveUseCase class, we have a retrieve use case function and it uses ArticlePort and never knows what is the actual implementation since does not depend on a concrete implementation.

Use case requests are also in usecase package and here is an example for Article Create operation

public record ArticleCreate(
    Long accountId, 
    String title, 
    String body
) {}

7. Adapters

We mostly saw interfaces until this part and within the infrastructure package, we will see the implementations for them. Think about that you depend on a database and if you want to persist data into the database, you need to use JPA-related technologies for MySQL, mongo, or Cassandra etc… These technology-specific implementations are all adapters and they need to satisfy the signatures we provided in port implementation.

public class ArticleImMemoryDataAdapter implements ArticlePort {

    private final ConcurrentHashMap<Long,Article> articles = new ConcurrentHashMap<>();

    @Override
    public Article create(ArticleCreate articleCreate) {
        long id = (articles.size() + 1);
        Article article = new Article(id, articleCreate.accountId(), articleCreate.title(),
            articleCreate.body());
        articles.put(id, article);
        return article;
    }

    @Override
    public Article retrieve(Long articleId) {
        return articles.get(articleId);
    }

    @Override
    public List<Article> query(ArticleQuery articleQuery) {
        return articles.values().stream()
            .filter(a-> a.accountId().equals(articleQuery.accountId()))
            .collect(Collectors.toList());
    }
}

In the above example, you can see the persistence layer is just an in-memory map to keep article data and fetch over this map.

8. Application Entry Point

In most of the applications, you see there are REST, gRPC, etc… interface to let consumers use it via that protocol. In this example, we will see a CLI interface that directly uses the exposed use case of the article domain model.

public class ArticleCli implements ArticlePort {
    private  final ArticleCreateUseCase articleCreateUseCase;
    private final ArticleRetrieveUseCase articleRetrieveUseCase;
    private final ArticleQueryUseCase articleQueryUseCase;

    public ArticleCli(ArticleCreateUseCase articleCreateUseCase,
        ArticleRetrieveUseCase articleRetrieveUseCase,
        ArticleQueryUseCase articleQueryUseCase) {
        this.articleCreateUseCase = articleCreateUseCase;
        this.articleRetrieveUseCase = articleRetrieveUseCase;
        this.articleQueryUseCase = articleQueryUseCase;
    }

    @Override
    public Article create(ArticleCreate articleCreate) {
        ArticleCreate article = new ArticleCreate(articleCreate.accountId(),articleCreate.title(),articleCreate.body());
        return this.articleCreateUseCase.create(article);
    }

    @Override
    public Article retrieve(Long articleId){
        return this.articleRetrieveUseCase.retrieve(ArticleRetrieve.from(articleId));
    }

    @Override
    public List<Article> query(ArticleQuery articleQuery) {
        return this.articleQueryUseCase.query(ArticleQuery.from(articleQuery.accountId()));
    }
}

ArticleCli depends on ArticlePort and lets see how we construct and use ArticleCli within a sample Java application.

public class Application {
    static Logger log = Logger.getLogger(Application.class.getName());

    public static void main(String[] args) {

        ArticleImMemoryDataAdapter articleImMemoryDataAdapter = new ArticleImMemoryDataAdapter();

        ArticleCli articleCli = new ArticleCli(
            new ArticleCreateUseCase(articleImMemoryDataAdapter),
            new ArticleRetrieveUseCase(articleImMemoryDataAdapter),
            new ArticleQueryUseCase(articleImMemoryDataAdapter));

        Article article = articleCli.create(new ArticleCreate(5L, "Hexagonal in 5 Minutes",
            "Hexagonal architecture is initially suggested..."));
        log.info("Article is created " + article);

        Article articleDetails = articleCli.retrieve(1L);
        log.info("Article details "+articleDetails);

        List<Article> result = articleCli.query(new ArticleQuery(5L));
        log.info("Found articles " + result);
    }

}

ArticleCli accepts any implementation of ArticlePort which is ArticleImMemoryDataAdapter in our case. By using this notation, you can easily construct fake implementations within your test instead of trying to mock third-party libraries in the persistence layer. Once you execute this main application, you will see the following

Oct 23, 2021 12:26:49 AM com.huseyin.hexagonal4j.Application main
INFO: Article is created Article[id=1, accountId=5, title=Hexagonal in 5 Minutes, body=Hexagonal architecture is initially suggested...]
Oct 23, 2021 12:26:49 AM com.huseyin.hexagonal4j.Application main
INFO: Article details Article[id=1, accountId=5, title=Hexagonal in 5 Minutes, body=Hexagonal architecture is initially suggested...]
Oct 23, 2021 12:26:49 AM com.huseyin.hexagonal4j.Application main
INFO: Found articles [Article[id=1, accountId=5, title=Hexagonal in 5 Minutes, body=Hexagonal architecture is initially suggested...]]

9. Conclusion

Hexagonal Architecture helps us on organizing the layers to decouple domain logic to have more maintainable software applications. This decouples our business logic from external layers, and external layers access domain layer by using ports by implementing them within adapters. This will let you freely add/update/remove features to your business logic without worrying about problems on other layers.

You can access source code of example here

6 thoughts on “Hexagonal Architecture in Java Simplified

  1. This confuses me: ArticleCLI is supposed to be an adapter, means an implementation of a Port, as you explained in the beginning. But there is no interface that is implemented by ArticleCLI.

    Then you define use cases like ArticleRetrieve. But these are never used? Instead ArticleCLI depends directly on ArticlePort. I think I miss the point of the use cases.

    One more comment: You are using Lombok annotations, but that’s not explained. While I understand the value for the brevity of the examples, I fear that too many people will read this and think “I need Lombok for hexagonal architecture”. I think, for this example, Java records would have been as good.

    Liked by 1 person

    1. Hi Christian,
      I really appreciated your valuable comment, I really missed some part of article, I just updated it. I have used records instead of Lombok. I have added use cases in adapters, actually my intention was to use them since I created them, but somehow I just forgot 🙂

      Like

  2. Hi Huseyin! Nice post! However, I just wonder – shouldn’t you use in ArticleCli implementations of use cases like ArticleRetrieveUseCase? You are using directly a repository there.

    Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s