Article image
João Rafael
João Rafael18/07/2024 16:47
Compartilhe

Arquitetura Hexagonal em Projetos Go (Golang)

    Arquitetura Hexagonal em Projetos Go (Golang)

    A arquitetura hexagonal, também conhecida como arquitetura de portas e adaptadores, é um padrão de design que promove a modularidade e a testabilidade em sistemas de software. Essa abordagem busca isolar o núcleo da lógica de negócios do restante do sistema, facilitando a manutenção, a evolução e a testagem automatizada. Ao aplicar a arquitetura hexagonal a projetos em Go (Golang), podemos aproveitar os benefícios dessa linguagem eficiente.

    Entendendo a Arquitetura Hexagonal

    A arquitetura hexagonal tem como princípio central a separação de preocupações (SoC - Separation of Concerns). Ela organiza o código em camadas, onde o núcleo da aplicação (lógica de negócios) fica isolado e independente de detalhes de implementação externos, como interfaces de usuário, bancos de dados e frameworks.

    Principais Componentes:

    1. Domínio (Core): Contém a lógica de negócios da aplicação. Essa camada é agnóstica quanto à implementação externa e não contém dependências de frameworks ou bibliotecas específicas.
    2. Portas (Ports): Definem interfaces para interações externas. Isso inclui interfaces para serviços, repositórios, notificações, etc. As implementações concretas dessas interfaces são fornecidas por adaptadores.
    3. Adaptadores (Adapters): Implementam as interfaces definidas nas portas. Eles traduzem as chamadas do núcleo da aplicação para o mundo externo, lidando com detalhes de infraestrutura, como acesso a bancos de dados e interações com APIs externas.

    Aplicando Hexagonal em Projetos Golang

    Adaptando alguns conceitos e para criar a aplicação é possível chegar em um resultado simples e flexível altamente escalável. Vamos usar como exemplo uma aplicação que tem dois usos fundamentais, uma api REST, usada pela aplicação Front-end para comunicação e tráfego dos dados, uma api gRPC para comunicação entre serviços que define modelos de comunicação de uma forma extremamente rápida, leve e independente de linguagem.

    REST vs gRPC

    REST

    • Utiliza Texto / JSON para se comunicar
    • A comunicação é Unidirecional
    • Alta latência
    • Sem contrato ( maior chance de erros )
    • Sem suporte a streaming
    • Design pré-definido
    • Bibliotecas de terceiros

    gRPC

    • Utiliza Protocol Buffers
    • Comunicação Bidirecional e Assíncrona
    • Baixa latência
    • Contrato de comunicação bem definido (.proto)
    • Suporte a Streaming

    - Design é livre

    - Geração de código

    Estrutura de Diretórios:

    Ao estruturar um projeto em Go com a arquitetura hexagonal, é comum organizar os diretórios da seguinte maneira:

    ├───application
    │   ├───dtos
    │   ├───grpc
    │   └───api
    │   
    ├───cmd
    |   ├─── rest
    |   ├─── grpc
    |   └─── main
    ├───domain
    |   |───usecase
    |   |───services
    │   └───model
    └───adapters
      ├───db
      └───repository
    
    1. cmd: Contém o ponto de entrada das aplicações.
    2. Application: Responsável por iniciar a aplicação e conectar os diferentes componentes, como os servidores ou Microsserviços.
    3. domain: Contém a lógica de negócios da aplicação.
    4. ports: Define as interfaces (portas) para interações externas.
    5. adaprters: Define os adaptadores, módulos de infra-estrutura como bancos de dados e repositórios.

    Exemplo de Implementação:

    Domínio (domain/service):

    package domain type ProductService interface {
      GetProductByID(id int) (*Product, error) 
    }
    
    type Product struct { 
      ID int Name string Price float64 
    }
    

    repository:

    package ports 
    import "your_project/internal/domain" 
    type ProductRepository interface { 
      GetByID(id int) (*domain.Product, error) 
    }
    

    Adaptador (Banco de dados):

    package database 
    
    import ( 
      "your_project/internal/domain" 
      "your_project/internal/ports" 
    ) 
    
    type ProductRepository struct { 
      // implementa a interface ProductRepository 
    } 
    func (r *ProductRepository) GetByID(id int) (*domain.Product, error) { 
      // lógica de acesso ao banco de dados para obter o produto pelo ID 
    }
    

    Porta (servidor gRPC):

    func StartGrpcServer(database *DB, port int) {
    
      grpcServer := grpc.NewServer()
      reflection.Register(grpcServer)
      pixRepository := repository.PixKeyRepositoryDB{Db: database}
      pixUsecase := usecase.PixUseCase{PixKeyRepository: pixRepository}
      pixGrpcService := NewgRPCService(pixUsecase)
    
      pb.RegisterPixServiceServer(grpcServer, pixGrpcService)
    
      addres := fmt.Sprintf("0.0.0.0:%d", port)
      listener, err := net.Listen("tcp", addres)
      if err != nil {
          log.Fatalf("Not start gRPC server:%v", err)
      }
    
      log.Printf("grpc server start on port:%d", port)
      errors := grpcServer.Serve(listener)
    
      if errors != nil {
          log.Fatalf("Not start gRPC server:%v", err)
      }
    
    }
    
    

    CMD ponto de entrada do servidor gRPC:

    func main() {
      database := db.ConectDB(os.Getenv("env"))
      grpc.StartGrpcServer(database, grpcPortNumber)
    }
    

    CMD ponto de entrada do servidor REST:

    func main() {
      database := db.ConectDB(os.Getenv("env"))
      rest.StartRESTServer(database, restPortNumber)
    }
    

    Benefícios de Usar Hexagonal em Projetos Go

    1. Testabilidade: A separação de preocupações facilita a criação de testes unitários e de integração, permitindo testar a lógica de negócios independentemente das implementações de infraestrutura.
    2. Flexibilidade: A troca de implementações de infraestrutura (banco de dados, API, etc.) é facilitada, pois as mudanças são isoladas nos adaptadores.
    3. Manutenibilidade: A clareza na estrutura do código facilita a manutenção e evolução do software ao longo do tempo.
    4. Desacoplamento: O núcleo da aplicação não depende de detalhes de implementação, tornando-o mais independente e fácil de entender.

    Ao aplicar a arquitetura hexagonal em projetos Go, os desenvolvedores podem criar sistemas mais modulares, testáveis e flexíveis, tirando proveito das características únicas da linguagem para construir software robusto e escalável.

    Um ótimo exemplo de escalabilidade seria utilizarmos a aplicação para integrar um microserviço utilizando serviços de fila de mensagens como RabbitMQ, Kafka entre outros. Para integrar o serviço não seria necessário modificar o núcleo da aplicação, e sim somente criar um novo ponto de entrada, assim como o gRPC e o REST, e um novo comando para a aplicação, e o contrário também é verdadeiro, é possível modificar toda a regra de negócios da aplicação sem modificar as APIs. Isso mostra como é simples criar aplicações altamente escaláveis e simples adaptando alguns conceitos e trazendo para o contexto da aplicação.

    Compartilhe
    Comentários (1)
    Lucas Martins
    Lucas Martins - 21/07/2024 21:19

    muito bom, continue assim!