Dando continuidade à série de posts sobre arquitetura hexagonal, nesse post implementaremos os ports do package category.
Se você está chegando agora ou se gostaria de relembrar, até o momento nós já:
Ports para atores Driven
Como é através de ports que o core da aplicação se comunicará com o mundo externo e, os atores do tipo driven são chamados a partir do core, a primeira coisa que vamos fazer é definir uma interface para esse tipo de ator.
type Repository interface { FindAll() ([]*Category, error) FindById(id string) (*Category, error) Save(category *Category) error Delete(id string) error }
Repare que, ao invés de ter um método Create
e um Update
, optamos por centralizar esse tipo de ação no método Save
.
Com essa abordagem conseguimos facilmente:
- Deixar explícito a intenção do core de salvar as mudanças no repository.
- Passar a responsabilidade de quando e como executar uma adição ou um update para a implementação do repository.
Service
Agora, para facilitar as interações vindas do mundo externo para o core, vamos criar uma struct de serviço.
Essa struct, além de ter todos os métodos de comunicação do mundo externo para o core, terá um único atributo, onde vamos armazenar a struct que implementar o Repository
definido acima.
type Service struct { repository Repository }
Pelo fato do atributo não ser exportado, ou seja, ser privado, precisamos implementar a função NewService
.
func NewService(repository Repository) *Service { return &Service{ repository: repository, } }
Ports para atores Driver
Com nossa struct definida, precisamos implementar os métodos para que os atores do tipo driver acessem as funcionalidades do Core.
// FindAll retorna todas as categorias no repositório func (s *Service) FindAll() ([]*Category, error) { categories, err := s.repository.FindAll() if err != nil { return nil, err } return categories, nil } // FindById retorna uma categoria do repositório pelo id func (s *Service) FindById(id string) (*Category, error) { c, err := s.repository.FindById(id) if err != nil { return nil, err } return c, nil } // Create cria uma nova categoria e salva no repositório func (s *Service) Create(name, description string) (*Category, error) { c, err := New(name, description) if err != nil { return nil, err } err = s.repository.Save(c) if err != nil { return nil, err } return c, nil } // Update atualiza uma categoria no repositório func (s *Service) Update(name, description, id string) (*Category, error) { c, err := s.repository.FindById(id) if err != nil { return nil, err } err = c.ChangeName(name) if err != nil { return nil, err } c.ChangeDescription(description) err = s.repository.Save(c) if err != nil { return nil, err } return c, nil } // Delete deleta uma categoria do repositório func (s *Service) Delete(id string) error { err := s.repository.Delete(id) if err != nil { return err } return nil }
Embora os métodos FindAll
, FindById
e Delete
não façam praticamente nada além de chamar um método de mesmo nome do repository, ter essa hierarquia de chamada garante que todas as ações realizadas no mundo externo, com chamadas advindas do core, sejam realizadas pelo mesmo repository.
Conclusão
Nesse post, concluímos a implementação do core da aplicação sem ter que adicionar nenhuma dependência externa.
Isso deixa claro como as regras de negócio do sistema estão totalmente separado das escolhas de banco de dados e framework web.
Eu sei, alguns podem dizer: “mas você adicionou uma dependência quando usou tag bson
na entity”.
Minha questão sobre esse ponto é que, no inicio do desenvolvimento definimos que o banco utilizado seria o Mongo. Caso no futuro haja alguma mudança nessa decisão, me parece muito mais barato mudar a tag que está atrelada ao atributo da entity, do que consumir 2 ou 3 vezes mais recursos para se ter entities exatamente iguais em camadas diferentes.
E por falar em Mongo, no próximo post dessa série, vamos implementar o repository. Então assina a nossa newsletter para não perder nada.