Dando continuidade à nossa série de posts sobre arquitetura hexagonal, nesse post implementaremos a entity do package category, que faz parte do core da aplicação.
Se você ainda não leu, convido a ler o post onde definimos a organização das pastas e arquivos desse projeto utilizando arquitetura hexagonal.
Definindo Category
Sem mais delongas, a primeira coisa que vou fazer no arquivo entity.go, é definir a struct Category.
type Category struct {
ID primitive.ObjectID `bson:"_id" json:"id"`
Name string `bson:"name" json:"name"`
Description string `bson:"description" json:"description"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
ModifiedAt time.Time `bson:"modified_at" json:"modified_at"`
Active bool `bson:"active" json:"active"`
}
Repare que, além de definir os campos da struct, também adicionei as tags bson e json. Essas tags serão utilizadas para serializar e desserializar essa struct nos formatos bson (mongo) e json.
Sei que alguns podem criticar minha abordagem e dizer que “o certo seria ter uma struct para cada camada”. No entanto, sendo arquitetura hexagonal, domain-driven design, solid, clean architecture e etc apenas teorias, cabe a nós entender seus fundamentos antes de aplicá-los.
Entendo que algumas linguagens não tem essa feature de tag, e por isso, faz todo sentido ter várias classes “iguais” em camadas diferentes, mas esse não é o caso do Go.
Em Go, ter várias structs para representar o mesmo tipo de dado, a meu ver, não faz muito sentido. Isso por que, tirando o fato de você aumentar o consumo de memória e processamento (afinal você precisa ficar “copiando” os dados de uma struct para a outra), você acaba dificultando futuras manutenções, já que você tem 3 structs para olhar ao invés de uma.
Lembre-se, menos código, menos chance de bug.
Ok, agora que expliquei um pouco da minha motivação para utilizar essa abordagem, vamos para o segundo passo, definir as variáveis de erro.
Variáveis de erro
Como o campo Name é o único obrigatório para a struct Category, precisamos criar somente uma variável de erro.
var (
ErrNameRequired = errors.New("name is required")
)
Essa abordagem de criar as variáveis de erro como variável global do package, facilita na hora de escrever testes unitários, pois conseguimos testar exatamente o erro retornado e não só a mensagem do erro.
Métodos
Agora, implementaremos os cinco métodos da struct Category. Esses métodos devem fornecer meios para mudar o valor dos campos Name, Description e Active, assim como validar se a struct tem todos os campos obrigatórios preenchidos.
unc (c *Category) ChangeName(name string) error {
if name == "" {
return ErrNameRequired
}
c.Name = name
c.ModifiedAt = time.Now()
return nil
}
func (c *Category) ChangeDescription(description string) {
c.Description = description
c.ModifiedAt = time.Now()
}
func (c *Category) Enable() {
c.Active = true
c.ModifiedAt = time.Now()
}
func (c *Category) Disable() {
c.Active = false
c.ModifiedAt = time.Now()
}
func (c *Category) Validate() error {
if c.Name == "" {
return ErrNameRequired
}
return nil
}
Repare que, exceto no método de validação, sempre que mudamos um valor da struct, também atualizamos o campo ModifiedAt.
Funções
Por fim, mas não menos importante, proveremos duas funções para criar a struct Category.
A primeira será uma função mais declarativa, onde precisamos passar os valores name e description para que ela nos retorne um ponteiro de Category.
func New(name, description string) (*Category, error) {
now := time.Now()
c := Category{
Name: name,
Description: description,
CreatedAt: now,
ModifiedAt: now,
Active: true,
}
err := c.Validate()
if err != nil {
return nil, err
}
return &c, nil
}
Já a segunda será uma função “facilitadora”, onde só precisamos passar um parâmetro do tipo io.ReadCloser– que é o tipo do atributo Body da struct Request do package net/http – que a função nos retornará um ponteiro de Category.
func Bind(body io.ReadCloser) (*Category, error) {
var c Category
err := json.NewDecoder(body).Decode(&c)
if err != nil {
return nil, err
}
now := time.Now()
c.CreatedAt = now
c.ModifiedAt = now
err = c.Validate()
if err != nil {
return nil, err
}
return &c, nil
}
Repare que em ambas as funções, afim de validar se todos os campos obrigatórios foram preenchidos corretamente, após o preenchimento dos valores da struct Category, o método Validate() é executado.
Conclusão
Nesse post definimos a struct Category, implementamos quatro métodos para manipular os valores da struct, um método de validação e duas funções para inicialização da struct.
Com isso, fechamos a implementação básica da entity Category.
No próximo post, vamos começar a implementação dos ports.
Para não perder nada, assine agora a nossa newsletter. É 100% gratuita e sempre será!
Até a próxima!
Faça parte da comunidade!
Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.
Livros Recomendados
Abaixo listei alguns dos melhores livros que já li sobre arquitetura de software e desenvolvimento.





[…] Implementamos a entity do package category. […]