Clean Architecture, 2 anos depois

Em Fevereiro de 2018 escrevi aquele que viria a ser o mais relevante texto que já publiquei: Clean Architecture using Golang. Com mais de 105 mil views o assunto gerou apresentações em alguns eventos de Go e PHP, além de me proporcionar a oportunidade de conversar sobre o assunto com várias pessoas.

Conforme fomos usando esta arquitetura para o desenvolvimento dos produtos da Codenation fomos ganhando experiência, resolvendo problemas e também gerando novos posts:

Depois dessa experiência toda posso afirmar com certeza:

Escolher a Clean Architecture foi a melhor decisão técnica que tomamos!

A segunda melhor foi a escolha da linguagem Go. Fiz uma palestra sobre essa escolha. Os slides e o video estão disponíveis caso queira ver mais detalhes.

Além de ressaltar o sucesso que tivemos com a Clean Architecture, este post serve para divulgar um repositório que criei com uma nova versão do exemplo de implementação em Go. Ele é uma atualização com melhorias na organização dos códigos e diretórios, bem como é um exemplo mais completo para quem está querendo implementar esta arquitetura.

Abaixo faço uma explicação do que significa cada diretório do projeto.

Camada Entity

Vamos começar pela camada mais interna da arquitetura.

De acordo com o post do Uncle Bob:

Entities encapsulate Enterprise wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions. It doesn’t matter so long as the entities could be used by many different applications in the enterprise.

A estrutura ficou desta forma:

entity

Neste pacote temos a definição da entidade (entity.go) e da interface do Repository e Manager (em interface.go). O Manager vai usar um Repository para realizar as operações básicas na entidade, como o famoso CRUD . Também temos as implementações do repositório em MySQL (repository_mysql.go) e em memória (repository_inmem.go), assim como a implementação da interface Manager (em manager.go).Também encontramos os mocks gerados pelo Gomock, conforme explicado neste post. Estes mocks são usados pelas demais camadas da arquitetura durante os testes.

Camada UseCase

De acordo com o Uncle Bob:

The software in this layer contains application specific business rules. It encapsulates and implements all of the use cases of the system

A estrutura ficou desta forma:

domain

Nos pacotes dentro de domain/usecase implementamos as regras de negócio do nosso produto. De acordo com a definição da arquitetura, estes UseCases fazem uso das entidades e dos managers que fazem o tratamento das mesmas (o CRUD). Também é possível ver a existência de mocks, que são usados pelas outras camadas da arquitetura.

Camada Controller

Neste aplicação de exemplo existem duas formas de acesso aos UseCases. A primeira é através de uma API e a segunda é usando um aplicativo de linha de comando (CLI).

A estrutura do CLI é bem simples:

cli

Ele faz uso dos pacotes de domínio para realizar uma busca de livros:

dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?parseTime=true", config.DB_USER, config.DB_PASSWORD, config.DB_HOST, config.DB_DATABASE)
   db, err := sql.Open("mysql", dataSourceName)
   if err != nil {
      log.Fatal(err.Error())
   }
   defer db.Close()
   repo := book.NewMySQLRepository(db)
   manager := book.NewManager(repo)
   all, err := manager.Search(query)
   if err != nil {
      log.Fatal(err)
   }
   for _, j := range all {
      fmt.Printf("%s %s \n", j.Title, j.Author)
   }

No exemplo acima é possível ver o uso do pacote config. Sua estrutura pode ser vista abaixo e mais detalhes encontrados neste post.

config

A estrutura da API é um pouco mais complexa e composta por três pacotes: handler, presenter e middleware.

O pacote handler é responsável pelo tratamento das requests e responses HTTP, bem como usar as regras de negócio existentes no domain.

handler

Os presenters são responsáveis pela representação dos dados que serão gerados como response pelos handlers.

presenter

Desta forma, a entidade User:

type User struct {
	ID        entity.ID
	Email     string
	Password  string
	FirstName string
	LastName  string
	CreatedAt time.Time
	UpdatedAt time.Time
	Books     []entity.ID
}

Pode ser transformada em:

type User struct {
	ID        entity.ID `json:"id"`
	Email     string    `json:"email"`
	FirstName string    `json:"first_name"`
	LastName  string    `json:"last_name"`
}

Com isso ganhamos maior controle em relação a como uma entidade será entregue pela API.

No último pacote da API encontramos os middlewares, que são usados por vários endpoints:

middlware

Pacotes auxiliares

Além dos pacotes comentados acima, podemos incluir na nossa aplicação outros trechos de código que podem ser utilizados por várias camadas. São pacotes que fornecem funcionalidades comuns como criptografia, log, tratamento de arquivos, etc. Estas funcionalidades não fazem parte do domínio da nossa aplicação, e podem ser inclusive reutilizados por outros projetos:

pkg

No README.md do repositório constam mais detalhes, como instruções para compilação e exemplos de uso.

Espero com este post fortalecer minha recomendação quanto a esta arquitetura e também receber feedbacks quanto aos códigos. Se você quer aprender a usar esta arquitetura em sua linguagem favorita, fica a sugestão para usar este repositório como exemplo para este aprendizado. Assim podemos ter diferentes implementações, em diferentes linguagens, para facilitar a comparação.

Agradecimentos especiais ao amigo Gustavo Schirmer que deu ótimos feedbacks sobre o texto e os códigos.