Hexagonal Architecture

Hexagonal Architecture

Unraveling the Ports and Adapters Architecture: Flexibility and Adaptability in Software Development

Fundamentals of the Ports and Adapters Architecture

  • Definition and origin of the architecture.

  • Main objectives: separation of concerns, flexibility, and testability.

Key Components of the Architecture

  • Application Core: Description of the application domain and its central business logic.

  • Ports: Explanation of how ports act as points of interaction for external communication.

  • Adapters: The function of adapters in connecting the application core to different technologies or external systems.

Benefits of Implementation

  • Flexibility: How the architecture allows for easy replacement and integration of new components.

  • Testability: Ease of unit and integration testing, given the clear separation between business logic and infrastructure.

  • Maintainability: Improvements in code maintenance thanks to modularity and low coupling.

  • Implementation

    Here I will demonstrate an implementation of the architecture with just one Use Case: Posting an article.

  • It's important to note that the package names and their organization are not rules, but this is a good way to organize and structure the application to follow the hexagonal architecture.

    Tools used:

    • spring-boot

    • spring-web

    • spring-data

    • gradle

    • lombok

    • model-mapper

    • H2 database

Representation of the hexagonal architecture

  • Here is an explanation of the different packages and their responsibilities:

    • adapters: This package contain all the adapters of the application. Adapters are implementations that adapt the communication from the core application to the external world or vice versa. It is worth noting that adapters can be both incoming and outgoing.

    • in.rest.v1: - It is important to always maintain versioning for organization

    • dto: Data Transfer Objects used to exchange information between the core system and the users via REST endpoints.

      The mapper is responsible for carrying out the DTO - Domain mapping and vice versa to keep the business layer independent and focused only on the domain.

    @Getter
    @Setter
    public class ArticleDTO {
        private UUID id;
        private String title;
        private String content;
    }
    @Component
    public class ArticleMapper {

        private final ModelMapper modelMapper;

        @Autowired
        public ArticleMapper(ModelMapper modelMapper) {
            this.modelMapper = modelMapper;
        }

        public ArticleDTO toDTO(Article article) {
            return modelMapper.map(article, ArticleDTO.class);
        }

        public Article toDomain(ArticleDTO dto) {
            return modelMapper.map(dto, Article.class);
        }
    }

ArticleResource: A REST controller in Spring that handles HTTP requests for operations related to articles (for example, creating, retrieving, updating, deleting articles).

@RestController
@RequestMapping("/articles")
public class ArticleResource {


    private final PostArticleInputPort postArticleInputPort;
    private final ArticleMapper articleMapper;

    public ArticleResource(
                           PostArticleInputPort postArticleInputPort,
                           ArticleMapper articleMapper) {

        this.postArticleInputPort = postArticleInputPort;
        this.articleMapper = articleMapper;
    }


    @PostMapping
    public ArticleDTO createArticle(@RequestBody ArticleDTO articleDTO) {
        Article article = articleMapper.toDomain(articleDTO);
        Article createdArticle = postArticleInputPort.createArticle(article);
        return articleMapper.toDTO(createdArticle);
    }
}

out.repository.database.jpa:

  • dto: DTOs that might be used specifically for JPA-related operations.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "articles")
public class ArticleEntity {

    @Id
    private UUID id;
    private String title;
    private String content;
    private String author;
}

The mapper appears here again, but this time it's to isolate the domain entity.

@Component
public class ArticleEntityMapper {

    private final ModelMapper modelMapper;

    @Autowired
    public ArticleEntityMapper(ModelMapper modelMapper) {
        this.modelMapper = modelMapper;
    }

    public ArticleEntity toEntity(Article article) {
        return modelMapper.map(article, ArticleEntity.class);
    }

    public Article toDomain(ArticleEntity entity) {
        return modelMapper.map(entity, Article.class);
    }
}

ArticleH2Adapter: An adapter for the H2 database, providing the implementation for the persistence operations defined by the ArticleRepository interface.

@Component
public class ArticleH2Adapter implements PostArticleOutputPort {
    private final ArticleRepository articleRepository;
    private final ArticleEntityMapper articleEntityMapper;

    public ArticleH2Adapter(ArticleRepository articleRepository, ArticleEntityMapper articleEntityMapper) {
        this.articleRepository = articleRepository;
        this.articleEntityMapper = articleEntityMapper;
    }

    @Override
    public Article createArticle(Article article) {
        ArticleEntity entity = articleEntityMapper.toEntity(article);
        ArticleEntity savedEntity = articleRepository.save(entity);
        return articleEntityMapper.toDomain(savedEntity);
    }

ArticleRepository: A Spring Data interface that extends JpaRepository, used for creating data access operations in a standard way without the need for boilerplate code. It provides CRUD operations and allows the definition of other query methods.

@Repository
public interface ArticleRepository extends JpaRepository<ArticleEntity, UUID> {
}

application: This package contains the application-specific logic that encompasses the business rules and use cases.

core:

  • domain: Domain models (business objects) and domain services, representing the core business logic and rules of the application.

      @Getter
      @Setter
      @NoArgsConstructor
      @AllArgsConstructor
      public class Article {
    
          private UUID id;
          private String title;
          private String content;
          private String author;
      }
    

    usecase: Application-specific business rules (use cases).

    ports:

    • in: Input ports, which define the interfaces that the core logic provides for adapters to implement. Examples include REST, GraphQL, gRPC, File, PubSub, etc.
    public interface PostArticleInputPort {

        Article createArticle(Article article);
    }
  • out: Output ports, which define the interfaces the core logic requires from the adapters. Examples include REST clients, gRPC, File, PubSub, Database, etc.
    public interface PostArticleInputPort {

        Article createArticle(Article article);
    }
  • config: This would contain configuration classes for the Spring application. Remember that the domain layer must be pure, not dependent on technologies or frameworks, hence there is a BeanConfig class that performs the injection of these necessary beans into the Use Case. Here we might also have other configurations like Open API, SSL Certificates, etc.

      @Configuration
      public class BeanConfig {
    
          @Bean
          public ModelMapper modelMapper() {
              return new ModelMapper();
          }
    
          @Bean
          public CreateArticleUseCase createArticleUseCase(PostArticleOutputPort postArticleOutputPort) {
              return new CreateArticleUseCase(postArticleOutputPort);
          }
    
    • shared-kernel: This package could include common functionalities and shared domain logic that can be used across different parts of the application or even shared packages.

    • HexaArchSpringApplication: This is the main application class with the main method that starts the Spring application.

This structure is consistent with the principles of Hexagonal Architecture, which focuses on the separation of concerns by isolating the application's core logic from external influences and side effects. The use of interfaces (ports) allows the application core to remain agnostic of the specifics of database access, external service communication, and user interface details.

In the context of a blog, the application core would handle use cases such as creating a new post, editing a post, deleting a post, commenting, etc. The adapters would then provide the necessary implementations to interact with a database (like H2), serve RESTful endpoints, and potentially interact with other systems or APIs.

Challenges and Considerations

Analysis of the potential challenges in adopting the architecture, such as the learning curve and initial complexity.

Considerations about when and for which projects the architecture is most suitable.

Complexity in implementation due to code verbosity and separation of responsibilities.

Complete code using hexagonal architecture for a CRUD of articles.

Fontes:
https://helpdev.com.br/
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html