Erstellen eines Microservice-Blogs - Teil 4 Post-Service

Dieser Artikel ist eine Fortsetzung der Geschichte des Schreibens eines Demo-Blogs über Microservices (die vorherigen Teile können hier gelesen werden: Teil 1 „Allgemeine Beschreibung der Architektur“ , Teil 2 „API-Gateway“ , Teil 3 „Servicebenutzer“ ). Dieser Artikel konzentriert sich auf die Implementierung des Post-Microservice (Artikel).

Das Hauptmerkmal des Mikrodienstes ist, dass er verschiedene Arten von Verbindungen mit anderen Diensten implementiert. Beispielsweise wird mit dem Kommentardienst (Kommentare) die Eins-zu-Viele-Art der Kommunikation implementiert (ein Artikel kann mehrere Kommentare enthalten), und die Benutzer- und Kategoriedienste haben viele-zu-Eins-Verbindungen (d. H. Ein Benutzer kann viele Artikel und einen haben) Kategorien können mehrere Artikel sein).

In Bezug auf die Funktionalität werden die folgenden Methoden im Post-Service implementiert:

  • Protokollierung von Serviceanfragen und Zwischenzuständen (der Mechanismus wird ausführlich in Teil 3 „User Service“ beschrieben) mit TraceId (wie von api-gw ausgegeben; siehe Teil 2 „API Gateway“ )
  • CRUD-Funktionen (Datensätze in der Datenbank erstellen, lesen, bearbeiten, löschen - MongoDB).
  • Suchfunktionen: Suche nach allen Artikeln, Suche nach Kategorie, Suche nach Autor

Traditionell erstellen wir einen Microservice, indem wir ihn in einem Protokoll beschreiben.

//post.proto yntax = "proto3"; package protobuf; import "google/api/annotations.proto"; //   Post service PostService { //  rpc Create (CreatePostRequest) returns (CreatePostResponse) { option (google.api.http) = { post: "/api/v1/post" }; } //  rpc Update (UpdatePostRequest) returns (UpdatePostResponse) { option (google.api.http) = { post: "/api/v1/post/{Slug}" }; } //  rpc Delete (DeletePostRequest) returns (DeletePostResponse) { option (google.api.http) = { delete: "/api/v1/post/{Slug}" }; } //      rpc GetPostCategory (GetPostCategoryRequest) returns (GetPostCategoryResponse) { //     option (google.api.http) = { get: "/api/v1/post/category/{Slug}" }; } //   rpc Find (FindPostRequest) returns (FindPostResponse) { option (google.api.http) = { get: "/api/v1/post" }; } //     rpc Get (GetPostRequest) returns (GetPostResponse) { option (google.api.http) = { get: "/api/v1/post/{Slug}" }; } //   rpc GetAuthor (GetAuthorRequest) returns (GetAuthorResponse) { //    SLUG option (google.api.http) = { get: "/api/v1/author/{Slug}" }; } //   rpc FindAuthors (FindAuthorRequest) returns (FindAuthorResponse) { //   option (google.api.http) = { get: "/api/v1/author" }; } } //--------------------------------------------------------------- // CREATE //--------------------------------------------------------------- message CreatePostRequest { string Title = 1; string SubTitle = 2; string Content = 3; string Categories = 4; } message CreatePostResponse { Post Post = 1; } //--------------------------------------------------------------- // UPDATE //--------------------------------------------------------------- message UpdatePostRequest { string Slug = 1; string Title = 2; string SubTitle = 3; string Content = 4; int32 Status = 5; string Categories = 6; } message UpdatePostResponse { int32 Status =1; } //--------------------------------------------------------------- // DELETE //--------------------------------------------------------------- message DeletePostRequest { string Slug = 1; } message DeletePostResponse { int32 Status =1; } //--------------------------------------------------------------- // GET //--------------------------------------------------------------- message GetPostRequest { string Slug = 1; } message GetPostResponse { Post Post = 1; } //--------------------------------------------------------------- // FIND POST //--------------------------------------------------------------- message FindPostRequest { string Slug = 1; } message FindPostResponse { repeated Post Posts = 1; } //--------------------------------------------------------------- // GET AUTHOR //--------------------------------------------------------------- message GetAuthorRequest { string Slug = 1; } message GetAuthorResponse { Author Author = 1; } //--------------------------------------------------------------- // FIND AUTHOR //--------------------------------------------------------------- message FindAuthorRequest { string Slug = 1; } message FindAuthorResponse { repeated Author Authors = 1; } //--------------------------------------------------------------- // GET CATEGORY //--------------------------------------------------------------- message GetPostCategoryRequest { string Slug = 1; } message GetPostCategoryResponse { PostCategory Category = 1; } //--------------------------------------------------------------- // POST //--------------------------------------------------------------- message Post { string Slug = 1; string Title = 2; string SubTitle = 3; string Content = 4; string UserId = 5; int32 Status = 6; string Src = 7; Author Author = 8; string Categories = 9; repeated PostCategory PostCategories = 10; string Comments = 11; repeated PostComment PostComments = 12; } //--------------------------------------------------------------- // Author //--------------------------------------------------------------- message Author { string Slug = 1; string FirstName = 2; string LastName = 3; string SrcAvatar = 4; string SrcCover = 5; repeated Post Posts = 6; } //--------------------------------------------------------------- // PostCategory //--------------------------------------------------------------- message PostCategory { string Slug = 1; string Name = 2; repeated Post Posts = 3; } //--------------------------------------------------------------- // PostComment //--------------------------------------------------------------- message PostComment { string Slug = 1; string Content = 2; Author Author = 3; } 

Als nächstes generieren wir das Microservice Framework. Wechseln Sie dazu in das Stammverzeichnis des Projekts und führen Sie den Befehl sh ./bin/protogen.sh aus.

Großartig! Der größte Teil der Arbeit wurde für uns vom Codegenerator erledigt, wir mussten nur eine Implementierung der Anwendungsfunktionen schreiben. Öffnen Sie die Datei ./services/post/functions.go und schreiben Sie die Implementierung.

Betrachten Sie die Hauptfragmente der Create-Funktion.

1. Analysieren Sie den Aufrufkontext und rufen Sie Informationen über den Benutzer ab.

 ... md,_:=metadata.FromIncomingContext(ctx) var userId string if len(md["user-id"])>0{ userId=md["user-id"][0] } ... 

2. Überprüfen Sie die Abfrageparameter und geben Sie den entsprechenden Fehler zurück, wenn sie ungültige Werte enthalten.

 ... if in.Title==""{ return nil,app.ErrTitleIsEmpty } ... 

3. Speichern Sie den Beitrag in der Datenbank (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. Wir erhalten die ID des erstellten Datensatzes, fügen sie der Antwort hinzu und geben die Antwort zurück.

 ... 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 ... 

Ich habe bereits erwähnt, dass der Postdienst für seine Verbindungen zu anderen Diensten interessant ist. Dies wird durch die Get-Methode deutlich demonstriert (Post anhand der angegebenen ID abrufen).

Lesen Sie zuerst aus 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 } ... 

Hier ist alles mehr oder weniger einfach. Konvertieren Sie zuerst die Zeichenfolge in ObjectID und verwenden Sie sie dann im Filter, um nach dem Datensatz zu suchen.

Jetzt müssen wir den resultierenden Post-Datensatz mit Daten über den Autor anreichern. Rufen Sie dazu den Benutzerdienst auf und rufen Sie einen Datensatz für die angegebene Benutzer-ID ab. Sie können dies wie folgt tun:

 ... //   User var header, trailer metadata.MD resp, err := o.UserService.Get( getCallContext(ctx), &userService.GetUserRequest{Slug:post.UserId}, grpc.Header(&header), //       grpc.Trailer(&trailer), //       ) if err != nil { return nil,err } author:=&Author{ Slug:resp.User.Slug, FirstName:resp.User.FirstName, LastName:resp.User.LastName, SrcAvatar:SRC_AVATAR, //TODO -  SrcCover:SRC_COVER, //TODO -  } post.Author=author ... 

Ich möchte darauf hinweisen, dass ich absichtlich zwei verschiedene Begriffe User und Author verwende, weil Ich glaube, dass sie in verschiedenen Zusammenhängen liegen. Beim Benutzer geht es um die Authentifizierung von Benutzernamen / Passwörtern und andere Attribute und Funktionen in Bezug auf Sicherheit und Zugriff. Der Autor ist eine Entität über veröffentlichte Beiträge, Kommentare und mehr. Die Author-Entität wird im Post-Kontext geboren, wobei Daten vom Benutzer als Grundlage verwendet werden. (Ich hoffe, ich konnte den Unterschied erklären;)

Der nächste Schritt ist das Lesen der Daten für verwandte Kategorien aus dem Kategoriedienst. Ich bin nicht sicher, ob ich die beste Option vorschlage (ich hoffe, die Community korrigiert sie). Der Ansatz ist im Wesentlichen wie folgt: Wir stellen EINE Anforderung an den Kategoriedienst und subtrahieren ALLE vorhandenen Kategorien. Im Postdienst wählen wir dann nur die Kategorien aus, die sich auf den Postdienst beziehen. Der Nachteil dieses Ansatzes ist der Overhead für die übertragenen Daten, plus - wir stellen nur eine Anfrage. Weil Die Anzahl der Kategorien ist definitiv nicht überwältigend. Ich denke, dass der Overhead vernachlässigt werden kann.

 ... //   Category, JOIN category respCategory,err:=o.CategoryService.Find( getCallContext(ctx), &categoryService.FindCategoryRequest{}, ) if err != nil { return out,err } for _, category:= range respCategory.Categories { for _, category_slug:= range strings.Split(post.Categories,",") { if category.Slug==category_slug{ postCategor:=&PostCategory{ Slug:category.Slug, Name:category.Name, } post.PostCategories=append(post.PostCategories,postCategor) } } } ... 

Als nächstes sollten wir alle dazugehörigen Kommentare einholen. Hier ähnelt die Aufgabe der Aufgabe mit Kategorien, mit der Ausnahme, dass im Fall von Kategorien-ID verwandte Kategorien in Post gespeichert wurden, im Fall von Kommentaren hingegen die ID des übergeordneten Posts direkt in untergeordneten Kommentaren gespeichert wird. Tatsächlich vereinfacht dies die Aufgabe erheblich, weil Alles, was wir tun müssen, ist eine Anfrage an den Kommentarservice zu richten, die den übergeordneten Post angibt, und das Ergebnis zu verarbeiten - fügen Sie in einer Schleife alle zugehörigen PostComment zum Post hinzu

 ... //   Comments, JOIN comments respComment,err:=o.CommentService.Find( getCallContext(ctx), &commentService.FindCommentRequest{PostId:in.Slug}, ) if err != nil { return out,err } for _, comment:= range respComment.Comments { postComment:=&PostComment{ Slug:comment.Slug, Content:comment.Content, } post.PostComments=append(post.PostComments,postComment) } ... 

Und geben Sie die gesammelte Post zurück

 ... out.Post=post return out,nil ... 

In der Weboberfläche haben wir die Navigation nach Kategorie und Autor implementiert. Das heißt Wenn ein Benutzer auf eine Kategorie klickt, wird eine Liste aller Artikel angezeigt, die mit der ausgewählten Kategorie verknüpft sind. Und wenn er auf den Autor klickt, wird eine Liste von Artikeln angezeigt, in der der ausgewählte Benutzer vom Autor angegeben wird.

Es gibt zwei Methoden, um diese Funktionalität im Post-Service zu implementieren:

GetPostCategory - Gibt eine PostCategory-Struktur zurück, die die ID, den Kategorienamen und die Sammlung verwandter Artikel enthält
GetAuthor - Gibt eine Author-Struktur zurück, die Benutzerattribute (Vorname, Nachname usw.) und eine Sammlung verwandter Posts enthält.

Ich werde die Implementierung dieser Methoden nicht im Detail beschreiben, um sie nicht zu wiederholen. Sie basieren auf denselben Codefragmenten, die oben beschrieben wurden.

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


All Articles