Este artigo é uma continuação do histórico de criação de um blog de demonstração em microsserviços (as partes anteriores podem ser lidas aqui:
Parte 1 "Descrição geral da arquitetura" ,
Parte 2 "API Gateway" ,
Parte 3 "Usuário de serviço" ). Este artigo se concentrará na implementação do Post microsserviço (artigos).
A principal característica do microsserviço é que ele implementa vários tipos de conexões com outros serviços. Por exemplo, com o serviço Comentários (comentários), o tipo de comunicação um para muitos é implementado (um artigo pode ter vários comentários) e os serviços Usuário e Categoria têm conexões muitos para um (ou seja, um usuário pode ter muitos artigos e um categorias podem ser vários artigos).
Em termos de funcionalidade, os seguintes métodos serão implementados no serviço de postagem:
- Registro de solicitações de serviço e estados intermediários (o mecanismo é descrito em detalhes na Parte 3 “Serviço do Usuário”) com o TraceId (o mesmo emitido pelo api-gw, consulte a Parte 2 “Gateway da API” )
- Funções CRUD (criar, ler, editar, excluir registros no banco de dados - MongoDB).
- Funções de pesquisa: pesquise todos os artigos, pesquise por categoria, pesquise por autor
Tradicionalmente, a criação de um microsserviço começará com sua descrição no protofile
Em seguida, geramos a estrutura de microsserviço. Para fazer isso, vá para o diretório raiz do projeto e execute o comando sh ./bin/protogen.sh.
Ótimo! A maior parte do trabalho para nós foi feita pelo gerador de código, apenas tivemos que escrever uma implementação das funções do aplicativo. Abra o arquivo ./services/post/functions.go e escreva a implementação.
Considere os principais fragmentos da função Criar.
1. Analise o contexto da chamada e obtenha informações sobre o usuário.
... md,_:=metadata.FromIncomingContext(ctx) var userId string if len(md["user-id"])>0{ userId=md["user-id"][0] } ...
2. Verifique os parâmetros da consulta e, se eles contiverem valores inválidos, retorne o erro correspondente.
... if in.Title==""{ return nil,app.ErrTitleIsEmpty } ...
3. Salve Post no banco de dados (mongoDB).
... collection := o.DbClient.Database("blog").Collection("posts") post:=&Post{ Title:in.Title, SubTitle:in.SubTitle, Content:in.Content, Status:app.STATUS_NEW, UserId:userId, Categories:in.Categories, } insertResult, err := collection.InsertOne(context.TODO(), post) if err != nil { return nil,err } ...
4. Recebemos o ID do registro criado, adicionamos à resposta e retornamos a resposta.
... if oid, ok := insertResult.InsertedID.(primitive.ObjectID); ok { post.Slug=fmt.Sprintf("%s",oid.Hex()) }else { err:=app.ErrInsert return out,err } out.Post=post return out,nil ...
Mencionei anteriormente que o serviço Post é interessante por suas conexões com outros serviços. Isso é claramente demonstrado pelo método Get (get Post pelo ID fornecido).
Primeiro, leia a partir do mongoDB Post:
... collection := o.DbClient.Database("blog").Collection("posts") post:=&Post{} id, err := primitive.ObjectIDFromHex(in.Slug) if err != nil { return nil,err } filter:= bson.M{"_id": id} err= collection.FindOne(context.TODO(), filter).Decode(post) if err != nil { return nil,err } ...
Tudo é mais ou menos simples aqui. Primeiro, converta a seqüência de caracteres em ObjectID e use-a no filtro para procurar o registro.
Agora precisamos enriquecer o registro de postagem resultante com dados sobre o autor. Para fazer isso, acesse o serviço Usuário e obtenha um registro para o UserId especificado. Você pode fazer isso da seguinte maneira:
...
Quero observar que deliberadamente uso dois termos diferentes Usuário e Autor, porque Eu acredito que eles se encontram em diferentes contextos. O usuário trata da autenticação de nomes de usuário / senhas e outros atributos e funções, de uma maneira ou de outra, relacionados à segurança e acesso. Autor é uma entidade sobre postagens publicadas, comentários e muito mais. A entidade Autor nasce no contexto Post usando dados do Usuário como base. (Espero ter conseguido explicar a diferença;)
A próxima etapa é ler os dados para categorias relacionadas no serviço Categoria. Não tenho certeza se estou propondo a melhor opção (espero que a comunidade a corrija). A essência da abordagem é a seguinte: fazemos uma solicitação para o serviço Categoria e subtraímos TODAS as categorias existentes; em seguida, no serviço Post, selecionamos apenas as categorias relacionadas ao Post. A desvantagem dessa abordagem é a sobrecarga para os dados transmitidos, além disso - fazemos apenas uma solicitação. Porque o número de categorias definitivamente não é uma quantidade impressionante, acho que as despesas gerais podem ser negligenciadas.
...
A próxima coisa que devemos fazer é obter todos os comentários relacionados. Aqui, a tarefa é semelhante à tarefa com categorias, exceto que no caso das categorias ID, as categorias relacionadas foram armazenadas no Post, no caso dos comentários, por outro lado, o ID do Post pai é armazenado diretamente nos comentários filhos. De fato, isso simplifica bastante a tarefa, porque tudo o que precisamos fazer é fazer uma solicitação para o serviço Comments, indicando o Post pai e processar o resultado - em um loop, adicionar ao PostComment Post relacionado
...
E devolver o Post coletado
... out.Post=post return out,nil ...
Na interface da web, implementamos a navegação por categoria e por autor. I.e. Quando um usuário clica em uma categoria, é exibida uma lista de todos os artigos vinculados à categoria selecionada. E quando ele clica no autor, uma lista de artigos é exibida de acordo, onde o usuário selecionado é indicado pelo autor.
Existem dois métodos para implementar essa funcionalidade no serviço de postagem:
GetPostCategory - Retorna uma estrutura PostCategory que contém o ID, o nome da categoria e a coleção de artigos relacionados
GetAuthor - Retorna uma estrutura de Autor que contém atributos de usuário (Nome, Sobrenome, etc.) e uma coleção de Post relacionado.
Não descreverei em detalhes a implementação desses métodos para não ser repetida. Eles são baseados nos mesmos fragmentos de código que foram descritos acima.