Escrevendo um blog sobre microsserviços - Parte 3 “Usuário”

Na segunda parte de nossa série de artigos "Escrevendo um blog sobre microsserviços", descrevemos o API Gateway .

Aqui, descrevemos a implementação do microsserviço de usuário.

Nosso microsserviço deve ser capaz de:

  • Chamadas de serviço de log e estados intermediários indicando TraceId (o mesmo emitido pelo api-gw, consulte a Parte 2 “API Gateway” )
  • Implementar as funções Login (SignIN) e Registration (SignUp)
  • Implementar funções CRUD (criar, ler, editar, excluir registros no banco de dados). Use o MongoDB como o banco de dados.

Primeiro, descrevemos nosso serviço no arquivo proto (./services/user/protobuf/user.proto).
Especifique a sintaxe usada - proto3. Indicamos o nome do pacote protobuf, neste pacote o código gerado automaticamente das partes do servidor e do cliente será implementado.

Importamos a biblioteca de anotações google / api / annotations.proto, será necessário descrever as diretrizes para gerar a API REST.

syntax = "proto3"; package protobuf; import "google/api/annotations.proto"; 

Descrição do serviço do usuário, diretamente as interfaces (métodos) que ele deveria ter. Por exemplo, a interface SignUp (registro): recebe uma mensagem SignUpRequest que contém os atributos Nome de usuário, Senha, Nome e Sobrenome e responde com uma mensagem SignUpResponse que contém os atributos Slug (UserID), Nome de usuário, Função. Também na descrição da interface, na seção options, especifique a diretiva pós: "/ api / v1 / user / signup. Com base nisso, o gerador de código criará uma interface REST que receberá solicitações POST em http: {{api_gw_host}} / api / v1 / user / inscrição.

 //-------------------------------------------------- //   User //-------------------------------------------------- service UserService { //  rpc SignUp (SignUpRequest) returns (SignUpResponse) { option (google.api.http) = { post: "/api/v1/user/signup" }; } … } //-------------------------------------------------- // SignUp //-------------------------------------------------- message SignUpRequest { string Username = 1; string Password = 2; string FirstName = 3; string LastName = 4; } message SignUpResponse { string Slug = 1; string Username = 2; string Role = 3; } 

E esperará a seguinte estrutura no corpo da solicitação:

 { Username: 'username_value', Password: 'password_value', FirstName: 'firstname_value', LastName: 'lastname_value', } 

E, portanto, se for bem-sucedido, fornecerá a estrutura:

 { Slug: 'user_id_value', Username: 'username_value', Role: 'role_value', } 

Ou um erro. Falaremos mais sobre erros um pouco mais adiante na seção que descreve as funções que implementam as interfaces descritas no protofile.

Outras interfaces (entrada, criação, atualização, exclusão, obtenção, localização) são declaradas da mesma maneira.

Agora que temos um protofile pronto. Vamos para o diretório raiz do projeto e executamos o comando sh ./bin/protogen.sh. Este script irá gerar o código principal.
Em seguida, acesse o diretório ./services/user e, no arquivo functions.go, escreva a implementação das interfaces declaradas.

Primeiro, implementamos o middleware. Em cada solicitação para o serviço, extraímos os parâmetros TraceId, UserId, UserRole do contexto da solicitação e os gravamos no arquivo de log. Aqui você pode implementar a solicitação de autorização.

 //-------------------------------------------------- // Midelware //-------------------------------------------------- func AccessLogInterceptor(ctx context.Context,req interface{},info *grpc.UnaryServerInfo,handler grpc.UnaryHandler,) (interface{}, error) { start:=time.Now() md,_:=metadata.FromIncomingContext(ctx) // Calls the handler reply, err := handler(ctx, req) var traceId,userId,userRole string if len(md["trace-id"])>0{ traceId=md["trace-id"][0] } if len(md["user-id"])>0{ userId=md["user-id"][0] } if len(md["user-role"])>0{ userRole=md["user-role"][0] } msg:=fmt.Sprintf("Call:%v, traceId: %v, userId: %v, userRole: %v, time: %v", info.FullMethod,traceId,userId,userRole,time.Since(start)) app.AccesLog(msg) return reply, err } 

No método SignUp, determinamos a estrutura de resposta.

 //,   STATUS_FAIL out:=&SignUpResponse{} 

Em seguida, verifique os parâmetros de solicitação.

 //     // Username err:=checkUserName(in.Username) if err!=nil{ log.Printf("[ERR] %s.SignUp, %v", app.SERVICE_NAME,err) return out,err } // Username   err=o.checkUserNameExist(in.Username) if err!=nil{ log.Printf("[ERR] %s.SignUp, %v", app.SERVICE_NAME,err) return out,err } // Password err=checkPassword(in.Password) if err!=nil{ log.Printf("[ERR] %s.SignUp, %v", app.SERVICE_NAME,err) return out,err } 

E se estiver tudo bem, preencha a estrutura do usuário, escreva no banco de dados e retorne a resposta.

 user:=&User{ Username:in.Username, FirstName:in.FirstName, LastName:in.LastName, Password:getMD5(in.Password), } var slug string collection:= o.DbClient.Database("blog").Collection("users") insertResult, err := collection.InsertOne(context.TODO(), user) if err != nil { return out,err } if oid, ok := insertResult.InsertedID.(primitive.ObjectID); ok { slug=fmt.Sprintf("%s",oid.Hex()) }else { err:=app.ErrInsert return out,err } out.Slug=slug out.Username=in.Username out.Role=app.ROLE_USER return out,nil 

Separadamente, prestamos atenção ao retorno de erro, por exemplo:

 err:=app.ErrInsert 

Como, em última análise, esse erro retornará ao nosso api-wg (na parte REST) ​​e seria legal convertê-lo em um código de resposta HTTP padrão. Para não escrever um monte de código adicional, você não deve usar o erro padrão, mas status.error do pacote google.golang.org/grpc/status.

Todos os erros típicos do microsserviço de usuário e como eles são convertidos em códigos de resposta HTTP estão descritos no arquivo / Services / user / app / errors.go.

 package app import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) //    var ( //  ErrEmailIncorrect = status.Error(codes.InvalidArgument, " E-mail") ErrPasswordIsEmpty = status.Error(codes.InvalidArgument, "Password  ") ErrUserNameIsEmpty = status.Error(codes.InvalidArgument, "E-mail  ") ErrUserNameIsExist = status.Error(codes.AlreadyExists, "  ") ErrNotFound = status.Error(codes.NotFound, "  ") ErrIncorrectLoginOrPassword = status.Error(codes.Unauthenticated,"   ") // CRUD ErrInsert = status.Error(codes.Internal, "  ") ErrUpdate = status.Error(codes.Internal, "  ") ) //================================================== // All gRPC err codes //================================================== // codes.OK - http.StatusOK // codes.Canceled - http.StatusRequestTimeout // codes.Unknown - http.StatusInternalServerError // codes.InvalidArgument - http.StatusBadRequest // codes.DeadlineExceeded - http.StatusGatewayTimeout // codes.NotFound - http.StatusNotFound // codes.AlreadyExists - http.StatusConflict // codes.PermissionDenied - http.StatusForbidden // codes.Unauthenticated - http.StatusUnauthorized // codes.ResourceExhausted - http.StatusTooManyRequests // codes.FailedPrecondition - http.StatusBadRequest // codes.Aborted - http.StatusConflict // codes.OutOfRange - http.StatusBadRequest // codes.Unimplemented - http.StatusNotImplemented // codes.Internal - http.StatusInternalServerError // codes.Unavailable - http.StatusServiceUnavailable // codes.DataLoss - http.StatusInternalServerError 

E a última coisa que gostaria de dizer sobre o microsserviço de usuário é como ele inicia e se conecta ao banco de dados. Essas operações são executadas no arquivo ./services/user/main.go.

Lançamento do serviço:

 lis,err:= net.Listen("tcp", fmt.Sprintf(":%s", Port)) if err != nil { log.Fatalf("failed to listen: %v", err) } grpcServer:= grpc.NewServer( grpc.UnaryInterceptor(protobuf.AccessLogInterceptor), ) s:=&protobuf.Server{} … // attach the user service to the server protobuf.RegisterUserServiceServer(grpcServer, s) 

Conexão com o banco de dados (main.go):

 //   s.DbConnect() defer s.DbDisconnect() 

Implementação da função DbConnect (./services/user/functions.go):

 //-------------------------------------------------- // /   //-------------------------------------------------- func (o *Server) DbConnect() error { var client *mongo.Client // Create client strURI:=fmt.Sprintf("mongodb://%s:%s@%s:%s",os.Getenv("MONGO_USER"),os.Getenv("MONGO_PASS"),os.Getenv("MONGO_HOST"),os.Getenv("MONGO_PORT")) client, err:= mongo.NewClient(options.Client().ApplyURI(strURI)) if err != nil { return err } // Create connect err = client.Connect(context.TODO()) if err != nil { return err } o.DbClient=client return nil } 

Source: https://habr.com/ru/post/pt482002/


All Articles