编写微服务博客-第4部分后期服务

本文是关于编写微服务演示博客的历史的延续(可以在此处阅读前面的部分: 第1部分“体系结构的一般说明”第2部分“ API网关”第3部分“服务用户” )。 本文将重点介绍Post微服务的实现(文章)。

微服务的主要特征是它实现了与其他服务的各种类型的连接。 例如,使用“评论”服务(评论),实现了一对多的通信类型(一篇文章可能有多个评论),而“用户”和“类别”服务则具有多对一的连接(即,一个用户可以有多篇文章,一篇类别可以是几篇文章)。

在功能方面,将在邮政服务中实现以下方法:

  • 使用TraceId(与发出的api-gw相同;请参阅第2部分“ API网关” )记录服务请求和中间状态(该机制在第3部分“用户服务”中详细描述)。
  • CRUD功能(在数据库中创建,读取,编辑,删除记录-MongoDB)。
  • 搜索功能:搜索所有文章,按类别搜索,按作者搜索

传统上,我们将通过在原型文件中描述微服务来开始创建微服务。

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

接下来,我们生成微服务框架。 为此,请转到项目的根目录并执行sh ./bin/protogen.sh命令。

太好了! 我们的大部分工作是由代码生成器完成的,我们只需要编写应用程序功能的实现即可。 打开文件./services/post/functions.go并编写实现。

考虑一下Create函数的主要片段。

1.解析调用上下文并从中获取有关用户的信息。

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

2.检查查询参数,如果它们包含无效值,则返回相应的错误。

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

3.将Post保存在数据库(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.我们收到创建的记录的ID,将其添加到答案中,然后返回答案。

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

前面我提到过,Post服务与其他服务的连接很有趣。 Get方法(通过给定ID获取Post)清楚地证明了这一点。

首先,请阅读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 } ... 

这里的一切或多或少都很简单。 首先,将字符串转换为ObjectID,然后在过滤器中使用它来搜索记录。

现在,我们需要使用有关作者的数据来丰富生成的Post记录。 为此,请转到用户服务并获取指定UserId的记录。 您可以按照以下步骤进行操作:

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

我想指出的是,我故意使用两个不同的术语“用户”和“作者”,因为 我相信它们存在于不同的环境中。 用户是关于用户名/密码身份验证以及其他与安全性和访问方式相关的属性和功能。 作者是有关已发布帖子,评论等的实体。 Author实体在Post上下文中生成,使用User的数据作为基础。 (我希望我能解释其中的区别;)

下一步是从类别服务中读取相关类别的数据。 我不确定我是否提出了最佳选择(我希望社区予以纠正)。 该方法的本质如下:我们向Category服务发出一个请求,并减去所有现有的类别,然后在Post服务中,我们仅选择与Post相关的那些类别。 这种方法的缺点是传输数据的开销,加上-我们仅发出一个请求。 因为 类别的数量绝对不是压倒性的数目,我认为可以忽略开销。

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

我们下一步要做的就是获取所有相关注释。 在此,该任务与具有类别的任务相似,除了在类别ID的情况下,相关类别存储在Post中,在注释的情况下,父Post的ID直接存储在子注释中。 实际上,这大大简化了任务,因为 我们需要做的就是向评论服务发出请求,指示父帖子并处理结果-在循环中将所有相关的帖子添加到帖子中

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

并返回收集的帖子

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

在Web界面中,我们已按类别和作者实现了导航。 即 当用户单击类别时,将显示所有链接到所选类别的文章的列表。 当他单击作者时,将相应地显示文章列表,作者在此处显示所选用户。

有两种方法可以在Post服务中实现此功能:

GetPostCategory-返回一个PostCategory结构,其中包含ID,类别名称和相关文章的集合
GetAuthor-返回一个Author结构,该结构包含用户属性(FirstName,LastName等)和相关Post的集合。

为了避免重复,我将不详细描述这些方法的实现。 它们基于与上述相同的代码片段。

Source: https://habr.com/ru/post/zh-CN481316/


All Articles