Implementando Clean Architecture com Golang
Tags: go, cleanarchitecture, cleancode, webdev
Published: 2022-03-23 16:50:51

Clean architecture é um assunto muito abordado nos últimos tempos. Mas... Como podemos estruturar uma arquitetura limpa com golang?

Primeiramente precisamos entender que clean architecture é uma especificação e não uma implementação. As implementações da arquitetura mais famosas são:

- **Hexagonal**
- DCI
- Screaming
- Onion

Nosso exemplo vai usar a arquitetura hexagonal, ou também chamada Ports and Adapters. Com a arquitetura em mãos precisamos agora definir o cenário da nossa aplicação e os requisitos que precisam existir para contemplar todas as funcionalidades. Vamos deixar pré fixado que a solução a ser criada será consumida pelo protocolo http com REST.
Os requisitos são:

- Criação de produtos (id, nome, preço e descrição)
- Listagem de produtos (com paginação no servidor)

Com os requisitos definidos, bora codar isso ai!
Calma, ainda não! Vamos definir quais tecnologias vamos usar, banco de dados, drivers de conexão e mais algumas bibliotecas que vão nos ajudar a criar a aplicação.

Usaremos então:

- Banco de dados:
    - [PostgresSQL](https://www.postgresql.org/)
- Libs no go
    - [Pgx:](https://github.com/jackc/pgx) Conexão com o banco de dados
    - [Mux:](https://github.com/gorilla/mux) Roteador de solicitação e um dispatcher para combinar as solicitações recebidas com seus respectivos manipuladores.
    - [Go-paginate:](https://github.com/booscaaa/go-paginate) Criação de queries para o postgres
    - [Viper:](http://github.com/spf13/viper) Configurações para o ambiente de dev/prod
    - [Testify:](https://github.com/stretchr/testify) Teste
    - [Pgx Mock:](http://github.com/pashagolub/pgxmock) Mock para o pgx connection pool
    - [Migrate:](https://github.com/golang-migrate/migrate) Rodar as atualizações do nosso banco de dados

Crie uma pasta no local desejado com o nome `clean-go/`
Na pasta, no seu editor preferido, estruture o projeto:

- adapter/
    - http/
        - main.go
    - postgres/
        - connector.go
- core/
    - domain/
        - product.go
    - dto/
        - product.go
- database
    - migrations

## Database
Instale a CLI [migrate](https://github.com/golang-migrate/migrate/tree/master/cmd/migrate) para gerar os arquivos de migrations necessários para o projeto.

```sh
migrate create -ext sql -dir database/migrations -seq create_product_table
```

Edite o arquivo gerado em `database/migrations/000001.create_product_table.up.sql` com o SQL da criação da tabela product.

```sql
CREATE TABLE product (
  id SERIAL PRIMARY KEY NOT NULL,
  name VARCHAR(50) NOT NULL,
  price FLOAT NOT NULL,
  description VARCHAR(500) NOT NULL
);
```

E também altere o arquivo `database/migrations/000001.create_product_table.down.sql`.
```sql
DROP TABLE IF EXISTS product;
```

## Go modules
Vamos inicializar os módulos do go com o comando:

```sh
# go mod init github.com/<seu usuario>/<nome do repo>
# no meu caso:
go mod init github.com/booscaaa/clean-go
```



## DTO (Data Transfer Object)

Vamos começar editando o arquivo `core/dto/product.go` e definindo o modelo de dados para a request de criação de um novo produto no servidor.

```go
package dto

import (
	"encoding/json"
	"io"
)

// CreateProductRequest is an representation request body to create a new Product
type CreateProductRequest struct {
	Name        string  `json:"name"`
	Price       float32 `json:"price"`
	Description string  `json:"description"`
}

// FromJSONCreateProductRequest converts json body request to a CreateProductRequest struct
func FromJSONCreateProductRequest(body io.Reader) (*CreateProductRequest, error) {
	createProductRequest := CreateProductRequest{}
	if err := json.NewDecoder(body).Decode(&createProductRequest); err != nil {
		return nil, err
	}

	return &createProductRequest, nil
}
```

Em seguida definimos o DTO para nossas requests de paginação no arquivo `core/dto/pagination.go`.

```go
package dto

import (
	"net/http"
	"strconv"
	"strings"
)

// PaginationRequestParms is an representation query string params to filter and paginate products
type PaginationRequestParms struct {
	Search       string   `json:"search"`
	Descending   []string `json:"descending"`
	Page         int      `json:"page"`
	ItemsPerPage int      `json:"itemsPerPage"`
	Sort         []string `json:"sort"`
}

// FromValuePaginationRequestParams converts query string params to a PaginationRequestParms struct
func FromValuePaginationRequestParams(request *http.Request) (*PaginationRequestParms, error) {
	page, _ := strconv.Atoi(request.FormValue("page"))
	itemsPerPage, _ := strconv.Atoi(request.FormValue("itemsPerPage"))

	paginationRequestParms := PaginationRequestParms{
		Search:       request.FormValue("search"),
		Descending:   strings.Split(request.FormValue("descending"), ","),
		Sort:         strings.Split(request.FormValue("sort"), ","),
		Page:         page,
		ItemsPerPage: itemsPerPage,
	}

	return &paginationRequestParms, nil
}
```

## Domain

Com nosso DTO configurado podemos criar o core da nossa aplicação. Criaremos um aquivo chamado `core/domain/pagination.go`.

```go
package domain

// Pagination is representation of Fetch methods returns
type Pagination[T any] struct {
	Items T     `json:"items"`
	Total int32 `json:"total"`
}
```

No arquivo `core/domain/product.go` vamos definir o modelo de dados referente a tabela product do banco e também as interfaces para implementação dos métodos, precisamos definir basicamente 3 interfaces: `service, usecase` e o nosso `repository`.
O **service** irá atender as requisições externas que batem na nossa api, o **usecase** é a nossa regra de negócio e o **repository** é nosso adapter do banco de dados.

```go
package domain

import (
	"net/http"

	"github.com/boooscaaa/clean-go/core/dto"
)

// Product is entity of table product database column
type Product struct {
	ID          int32   `json:"id"`
	Name        string  `json:"name"`
	Price       float32 `json:"price"`
	Description string  `json:"description"`
}

// ProductService is a contract of http adapter layer
type ProductService interface {
	Create(response http.ResponseWriter, request *http.Request)
	Fetch(response http.ResponseWriter, request *http.Request)
}

// ProductUseCase is a contract of business rule layer
type ProductUseCase interface {
	Create(productRequest *dto.CreateProductRequest) (*Product, error)
	Fetch(paginationRequest *dto.PaginationRequestParms) (*Pagination[[]Product], error)
}

// ProductRepository is a contract of database connection adapter layer
type ProductRepository interface {
	Create(productRequest *dto.CreateProductRequest) (*Product, error)
	Fetch(paginationRequest *dto.PaginationRequestParms) (*Pagination[[]Product], error)
}
```

## Repository

Com nosso domínio bem definido vamos começar definitivamente a implementação da nossa api. No arquivo `adapter/postgres/connector.go` vamos configurar a conexão com o banco de dados.

```go
package postgres

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/golang-migrate/migrate/v4"
	"github.com/jackc/pgconn"
	"github.com/jackc/pgx/v4"
	"github.com/jackc/pgx/v4/pgxpool"
	"github.com/spf13/viper"

	_ "github.com/golang-migrate/migrate/v4/database/pgx" //driver pgx used to run migrations
	_ "github.com/golang-migrate/migrate/v4/source/file"
)

// PoolInterface is an wraping to PgxPool to create test mocks
type PoolInterface interface {
	Close()
	Exec(ctx context.Context, sql string, arguments ...interface{}) (pgconn.CommandTag, error)
	Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error)
	QueryRow(ctx context.Context, sql string, args ...interface{}) pgx.Row
	QueryFunc(
		ctx context.Context,
		sql string,
		args []interface{},
		scans []interface{},
		f func(pgx.QueryFuncRow) error,
	) (pgconn.CommandTag, error)
	SendBatch(ctx context.Context, b *pgx.Batch) pgx.BatchResults
	Begin(ctx context.Context) (pgx.Tx, error)
	BeginFunc(ctx context.Context, f func(pgx.Tx) error) error
	BeginTxFunc(ctx context.Context, txOptions pgx.TxOptions, f func(pgx.Tx) error) error
}

// GetConnection return connection pool from postgres drive PGX
func GetConnection(context context.Context) *pgxpool.Pool {
	databaseURL := viper.GetString("database.url")

	conn, err := pgxpool.Connect(context, "postgres"+databaseURL)

	if err != nil {
		fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
		os.Exit(1)
	}

	return conn
}

// RunMigrations run scripts on path database/migrations
func RunMigrations() {
	databaseURL := viper.GetString("database.url")
	m, err := migrate.New("file://database/migrations", "pgx"+databaseURL)
	if err != nil {
		log.Println(err)
	}

	if err := m.Up(); err != nil {
		log.Println(err)
	}
}
```
Com nosso connector pronto, vamos implementar a interface ProductRepository lá do nosso domain, lembra? Criaremos a estrutura da implementação assim:

- adapter
    - postgres
        - productrepository
            - new.go
            - create.go
            - fetch.go

No arquivo `adapter/postgres/productrepository/new.go` criaremos nossa vinculação com o "contrato" da interface ProductRepository.

```go
package productrepository

import (
	"github.com/boooscaaa/clean-go/adapter/postgres"
	"github.com/boooscaaa/clean-go/core/domain"
)

type repository struct {
	db postgres.PoolInterface
}

// New returns contract implementation of ProductRepository
func New(db postgres.PoolInterface) domain.ProductRepository {
	return &repository{
		db: db,
	}
}
```
No arquivo `adapter/postgres/productrepository/create.go` criaremos a lógica que contempla o metodo Create do nosso contrato.

```go
package productrepository

import (
	"context"

	"github.com/boooscaaa/clean-go/core/domain"
	"github.com/boooscaaa/clean-go/core/dto"
)

func (repository repository) Create(productRequest *dto.CreateProductRequest) (*domain.Product, error) {
	ctx := context.Background()
	product := domain.Product{}

	err := repository.db.QueryRow(
		ctx,
		"INSERT INTO product (name, price, description) VALUES ($1, $2, $3) returning *",
		productRequest.Name,
		productRequest.Price,
		productRequest.Description,
	).Scan(
		&product.ID,
		&product.Name,
		&product.Price,
		&product.Description,
	)

	if err != nil {
		return nil, err
	}

	return &product, nil
}
```


No arquivo `adapter/postgres/productrepository/fetch.go` criaremos a lógica que contempla o método Fetch do nosso contrato.

```go
package productrepository

import (
	"context"

	"github.com/boooscaaa/clean-go/core/domain"
	"github.com/boooscaaa/clean-go/core/dto"
	"github.com/booscaaa/go-paginate/paginate"
)

func (repository repository) Fetch(pagination *dto.PaginationRequestParms) (*domain.Pagination[[]domain.Product], error) {
	ctx := context.Background()
	products := []domain.Product{}
	total := int32(0)

	query, queryCount, err := paginate.Paginate("SELECT * FROM product").
		Page(pagination.Page).
		Desc(pagination.Descending).
		Sort(pagination.Sort).
		RowsPerPage(pagination.ItemsPerPage).
		SearchBy(pagination.Search, "name", "description").
		Query()

	if err != nil {
		return nil, err
	}

	{
		rows, err := repository.db.Query(
			ctx,
			*query,
		)

		if err != nil {
			return nil, err
		}

		for rows.Next() {
			product := domain.Product{}

			rows.Scan(
				&product.ID,
				&product.Name,
				&product.Price,
				&product.Description,
			)

			products = append(products, product)
		}
	}

	{
		err := repository.db.QueryRow(ctx, *queryCount).Scan(&total)

		if err != nil {
			return nil, err
		}
	}

	return &domain.Pagination[[]domain.Product]{
		Items: products,
		Total: total,
	}, nil
}
```
Repository pronto! :D

## UseCase

Com nosso repository finalizado vamos implementar a regra de negócios da nossa aplicação. Criaremos a estrutura da implementação assim:

- core
    - domain
        - usecase
            - productusecase
                - new.go
                - create.go
                - fetch.go

No arquivo `core/domain/usecase/productusecase/new.go` criaremos nossa vinculação com o "contrato" da interface ProductUseCase.

```go
package productusecase

import "github.com/boooscaaa/clean-go/core/domain"

type usecase struct {
	repository domain.ProductRepository
}

// New returns contract implementation of ProductUseCase
func New(repository domain.ProductRepository) domain.ProductUseCase {
	return &usecase{
		repository: repository,
	}
}
```
No arquivo `core/domain/usecase/productusecase/create.go` criaremos a lógica que contempla o método Create do nosso contrato.

```go
package productusecase

import (
	"github.com/boooscaaa/clean-go/core/domain"
	"github.com/boooscaaa/clean-go/core/dto"
)

func (usecase usecase) Create(productRequest *dto.CreateProductRequest) (*domain.Product, error) {
	product, err := usecase.repository.Create(productRequest)

	if err != nil {
		return nil, err
	}

	return product, nil
}
```
No arquivo `core/domain/usecase/productusecase/fetch.go` criaremos a lógica que contempla o método Fetch do nosso contrato.

```go
package productusecase

import (
	"github.com/boooscaaa/clean-go/core/domain"
	"github.com/boooscaaa/clean-go/core/dto"
)

func (usecase usecase) Fetch(paginationRequest *dto.PaginationRequestParms) (*domain.Pagination[[]domain.Product], error) {
	products, err := usecase.repository.Fetch(paginationRequest)

	if err != nil {
		return nil, err
	}

	return products, nil
}
```

## Service
Com nosso usecase finalizado vamos implementar o adapter do http para receber as requisições da aplicação. Criaremos a estrutura da implementação assim:

- adapter
    - http
        - productservice
            - new.go
            - create.go
            - fetch.go

No arquivo `adapter/http/productservice/new.go` criaremos nossa vinculação com o "contrato" da interface ProductService.

```go
package productservice

import "github.com/boooscaaa/clean-go/core/domain"

type service struct {
	usecase domain.ProductUseCase
}

// New returns contract implementation of ProductService
func New(usecase domain.ProductUseCase) domain.ProductService {
	return &service{
		usecase: usecase,
	}
}
```
No arquivo `adapter/http/productservice/create.go` criaremos a lógica que contempla o método Create do nosso contrato.

```go
package productservice

import (
	"encoding/json"
	"net/http"

	"github.com/boooscaaa/clean-go/core/dto"
)

func (service service) Create(response http.ResponseWriter, request *http.Request) {
	productRequest, err := dto.FromJSONCreateProductRequest(request.Body)

	if err != nil {
		response.WriteHeader(500)
		response.Write([]byte(err.Error()))
		return
	}

	product, err := service.usecase.Create(productRequest)

	if err != nil {
		response.WriteHeader(500)
		response.Write([]byte(err.Error()))
		return
	}

	json.NewEncoder(response).Encode(product)
}
```
No arquivo `adapter/http/productservice/fetch.go` criaremos a lógica que contempla o método Fetch do nosso contrato.

```go
package productservice

import (
	"encoding/json"
	"net/http"

	"github.com/boooscaaa/clean-go/core/dto"
)

func (service service) Fetch(response http.ResponseWriter, request *http.Request) {
	paginationRequest, err := dto.FromValuePaginationRequestParams(request)

	if err != nil {
		response.WriteHeader(500)
		response.Write([]byte(err.Error()))
		return
	}

	products, err := service.usecase.Fetch(paginationRequest)

	if err != nil {
		response.WriteHeader(500)
		response.Write([]byte(err.Error()))
		return
	}

	json.NewEncoder(response).Encode(products)
}
```
Tudo pronto! Brincadeira... Estamos quase lá, vamos configurar nossa injeção de dependências, nosso arquivo `adapter/http/main.go` para rodar a aplicação e o arquivo json de configurações de conexão do banco de dados.
Para configurar a injeção de dependência do nosso product vamos criar um arquivo em `di/product.go`.

```go
package di

import (
	"github.com/boooscaaa/clean-go/adapter/http/productservice"
	"github.com/boooscaaa/clean-go/adapter/postgres"
	"github.com/boooscaaa/clean-go/adapter/postgres/productrepository"
	"github.com/boooscaaa/clean-go/core/domain"
	"github.com/boooscaaa/clean-go/core/usecase/productusecase"
)

// ConfigProductDI return a ProductService abstraction with dependency injection configuration
func ConfigProductDI(conn postgres.PoolInterface) domain.ProductService {
	productRepository := productrepository.New(conn)
	productUseCase := productusecase.New(productRepository)
	productService := productservice.New(productUseCase)

	return productService
}
```

E por fim configurar nosso arquivo `adapter/http/main.go`

```go
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"

	"github.com/boooscaaa/clean-go/adapter/postgres"
	"github.com/boooscaaa/clean-go/di"
	"github.com/gorilla/mux"
	"github.com/spf13/viper"
)

func init() {
	viper.SetConfigFile(`config.json`)
	err := viper.ReadInConfig()
	if err != nil {
		panic(err)
	}
}

func main() {
	ctx := context.Background()
	conn := postgres.GetConnection(ctx)
	defer conn.Close()

	postgres.RunMigrations()
	productService := di.ConfigProductDI(conn)

	router := mux.NewRouter()
	router.Handle("/product", http.HandlerFunc(productService.Create)).Methods("POST")
	router.Handle("/product", http.HandlerFunc(productService.Fetch)).Queries(
		"page", "{page}",
		"itemsPerPage", "{itemsPerPage}",
		"descending", "{descending}",
		"sort", "{sort}",
		"search", "{search}",
	).Methods("GET")

	port := viper.GetString("server.port")
	log.Printf("LISTEN ON PORT: %v", port)
	http.ListenAndServe(fmt.Sprintf(":%v", port), router)
}
```
Agora só a configuração de conexão com o banco de dados e a porta que a api vai rodar no aquivo `config.json` na raiz do projeto:

```js
{
  "database": {
    "url": "://postgres:postgres@localhost:5432/devtodb"
  },
  "server": {
    "port": "3000"
  }
}
```

E a estrutura final ficou:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zpauyu6h5humsvlu2c5d.png)

## Hora da verdade!
Será mesmo que o projeto vai rodar lisinho? É o que veremos.
Para executar a api basta se posicionar na raiz do projeto e rodar:

```sh
go run adapter/http/main.go
```
Com isso vai aparecer algo assim no terminal:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hn5iqykatxhlx88zu1ft.png)

## Testando, 1..2..3.. Teste som!

Para criar um produto basta mandar um JSON em uma request POST na URL: localhost:_port_/product 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z70kntjwvcv7vblbi23s.png)
Para listar os produtos com paginação é so mandar um GET maroto na URL localhost:_port_/product 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yibelf9a5cyue21zwquc.png)

## Sua vez
Vai na fé! Acredito totalmente em você, independente do seu nível de conhecimento técnico, você vai criar a melhor api em GO.
Se você se deparar com problemas que não consegue resolver, sinta-se à vontade para entrar em contato. Vamos resolver isso juntos.

## Onde tá os testes unitários?
Bora lá, próximo post vamos abordar isso e também mexer bastante com o coverage do Go. Vai ser muito legal! Até logo

[Repositório](https://github.com/booscaaa/clean-go)