Rédaction d'un blog sur les microservices - Partie 3 «Utilisateur»

Dans la deuxième partie de notre série d'articles «Rédaction d'un blog sur les microservices», nous avons décrit la passerelle API .

Nous décrivons ici l'implémentation du microservice utilisateur.

Notre microservice devrait pouvoir:

  • Consigner les appels de service et les Ă©tats intermĂ©diaires indiquant TraceId (le mĂŞme Ă©mis par api-gw, voir la partie 2 «API Gateway» )
  • ImplĂ©menter les fonctions de connexion (SignIN) et d'inscription (SignUp)
  • ImplĂ©menter les fonctions CRUD (crĂ©er, lire, Ă©diter, supprimer des enregistrements dans la base de donnĂ©es). Utilisez MongoDB comme base de donnĂ©es.

Tout d'abord, nous décrivons notre service dans le fichier proto (./services/user/protobuf/user.proto).
Spécifiez la syntaxe utilisée - proto3. Nous indiquons le nom du package protobuf, dans ce package le code généré automatiquement des parties serveur et client sera implémenté.

Nous importons la bibliothèque d'annotations google / api / annotations.proto, elle sera nécessaire pour décrire les directives de génération de l'API REST.

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

Description du service utilisateur, directement les interfaces (méthodes) qu'il devrait avoir. Par exemple, l'interface SignUp (inscription): elle reçoit un message SignUpRequest qui contient les attributs Username, Password, FirstName et LastName et répond avec un message SignUpResponse qui contient les attributs Slug (UserID), Username, Role. Toujours dans la description de l'interface, dans la section des options, spécifiez la directive post: "/ api / v1 / user / signup. Sur cette base, le générateur de code créera une interface REST qui recevra les requêtes POST sur http: {{api_gw_host}} / api / v1 / user / inscription.

 //-------------------------------------------------- //   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; } 

Et il attendra la structure suivante dans le corps de la demande:

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

Et en conséquence, en cas de succès, il donnera la structure:

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

Ou une erreur. Nous vous en dirons plus sur les erreurs un peu plus loin dans la section décrivant les fonctions qui implémentent les interfaces décrites dans le profil.

Les autres interfaces (SignIn, Create, Update, Delete, Get, Find) sont déclarées de la même manière.

Maintenant que nous avons un profil prédéfini. Nous allons dans le répertoire racine du projet et exécutons la commande sh ./bin/protogen.sh. Ce script générera le code principal.
Ensuite, allez dans le répertoire ./services/user et dans le fichier functions.go écrivez l'implémentation des interfaces déclarées.

Tout d'abord, nous implémentons un middleware. À chaque demande au service, nous extrayons les paramètres TraceId, UserId, UserRole du contexte de la demande et les écrivons dans le fichier journal. Ici, vous pouvez implémenter l'autorisation de demande.

 //-------------------------------------------------- // 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 } 

Dans la méthode SignUp, nous déterminons la structure de réponse.

 //,   STATUS_FAIL out:=&SignUpResponse{} 

Ensuite, vérifiez les paramètres de la demande.

 //     // 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 } 

Et si tout va bien, remplissez la structure Utilisateur, écrivez dans la base de données et renvoyez la réponse.

 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 

Séparément, nous prêtons attention au retour d'erreur, par exemple:

 err:=app.ErrInsert 

Puisque finalement cette erreur reviendra à notre api-wg (dans sa partie REST) ​​et il serait cool de la convertir en un code de réponse HTTP standard. Afin de ne pas écrire un tas de code supplémentaire, vous ne devez pas utiliser l'erreur go standard, mais status.error du package google.golang.org/grpc/status.

Toutes les erreurs typiques du microservice Utilisateur et la façon dont elles sont converties en codes de réponse HTTP sont décrites dans le fichier. / 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 

Et la dernière chose que je voudrais dire sur le microservice utilisateur est la façon dont il démarre et se connecte à la base de données. Ces opérations sont effectuées dans le fichier ./services/user/main.go.

Lancement du service:

 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) 

Connexion à la base de données (main.go):

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

Implémentation de la fonction 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/fr482002/


All Articles