A Quick and Practical Example of Hexagonal Architecture in Java

Backing up Your Wordpress Site

1. Overview

This is a quick introduction of hexagonal architecture with Spring boot.

2. Hexagonal Architecture

This is a design pattern that ensures decoupling between the core domain logic inside the hexagon and its external dependencies. Mainly, the intent is to isolate the core logic and make it accessible to adapters through specific contracts. Likewise, this is also the main intent of an n-tier architecture but in this case, the tiers tend to have many implementation details of the tier below.

The main concept is very similar to Dependency Injection (DI)  where high-level modules should be independent of low-level modules.  One way to implement this architecture is through the use of an Inversion of Control (IOC) container.

The benefits of this architecture are:

  • the core layer is independent and fully functional on its own
  • implementations of the interfaces are replaceable which makes it great to test.
  • development of the external layers will not disrupt the functionality of the inner core layer

RELATED POSTS


3. Core Domain

Let us introduce this architecture by creating a simple user service to store and fetch users.

First, let’s start by creating a simple User object.

<code lang="java" class="language-java">public class User {

  private long id;
  private String name;

  public User() {
  }

  public User(long id, String name) {
       super();
       this.id = id;
       this.name = name;
  }

  // getters and setters
}
</code>

Secondly, we create a UserService interface to add and retrieve users.

<code lang="java" class="language-java">public interface UserService {

    void addUser(User user);

    List&lt;User> getUsers();
}</code>

Next, let’s create a UserDTO object so that it is used outside the core domain.

<code lang="java" class="language-java">public class UserDTO {

    private long id;
    private String name;

    // getters and setters
}</code>

We also need to create an interface UserDataAdapter for user data related calls. This will act as a contract between the core domain and the data adapter layer. In this way, we abstract the data repository from our core domain. As a result, any changes to the data layer will not break our service class UserServiceImpl.

<code lang="java" class="language-java">public interface UserDataAdapter {

     void addUser(UserDTO user);
  
     List&lt;UserDTO> getUsers();
}
</code>
<code lang="java" class="language-java">@Service
public class UserServiceImpl implements UserService {

  @Autowired
  private UserDataAdapter userDataAdapter;

  @Autowired
  private CoreModelMapper coreModelMapper;

  public void addUser(User user) { 
     userDataAdapter.addUser(coreModelMapper.map(user, UserDTO.class));
  }

  public List&lt;User> getUsers() {
     return coreModelMapper.mapAsList(userDataAdapter.getUsers(), User.class);
  }
}</code>

The UserServiceImpl class used  Orika to map between the User object and UserDTO.

4. Primary Port and Adapters

A primary port is an external service that starts the interaction with our application. Usually, one or more primary adapters work on top of this port. The adapters use a specific contract to forward messages sent by the port to the core domain.

So, let’s create a REST Controller to be our primary adapter. The controller will forward any messages received to the core domain via the UserService bean.

<code lang="java" class="language-java">@RestController
public class UserController {

  @Autowired
  private UserService userService;

  @PostMapping("/users")
  @ResponseStatus(HttpStatus.CREATED)
  public void addUser(@RequestBody User user) {
       userService.addUser(user);
  }

  @GetMapping("/users")
  public List&lt;User> getUsers() {
       return userService.getUsers();
  }
}</code>

5. Secondary Port and Adapter

Sometimes our application needs to talk to other external services also called secondary ports. For example, our application will need to talk to a database (secondary port). In our case, an embedded H2 database will be used.

<code lang="java" class="language-java">@Configuration
public class PersistenceConfig {

  @Bean
  public DataSource dataSource() {
       EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
       EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.H2)
       .addScript("mySchema.sql")
       .addScript("myData.sql")
       .build();
       return db;
  }
}</code>

Let the UserDataAdapterImpl class be our secondary adapter. Briefly, it creates and retrieves users by calling the save() and findAll()  methods of the JPARepository interface.

<code lang="java" class="language-java">@Component
public class UserDataAdapterImpl implements UserDataAdapter {

  @Autowired
  private UserRepository userRepository;

  @Autowired
  private ModelMapper modelMapper;

  public void addUser(UserDTO user) {
       userRepository.save(modelMapper.map(user, UserEntity.class));
  }

  public List&lt;UserDTO> getUsers() {
       return modelMapper.mapAsList(userRepository.findAll(), UserDTO.class);
  }
}

</code>
<code lang="java" class="language-java">public interface UserRepository extends JpaRepository&lt;UserEntity, Long> {

}</code>

6. Conclusion

In conclusion, this pattern is ideal in project setups with many developers working simultaneously. However, the main advantage of using a Hexagonal Architecture is that the code in the core domain is kept protected from any changes to the ports or adapters.


POPULAR THIS WEEK


Share this:

Leave a Reply

Your email address will not be published. Required fields are marked *