本文是关于编写微服务演示博客的历史的延续(可以在此处阅读前面的部分:
第1部分“体系结构的一般说明” ,
第2部分“ API网关” ,
第3部分“服务用户” )。 本文将重点介绍Post微服务的实现(文章)。
微服务的主要特征是它实现了与其他服务的各种类型的连接。 例如,使用“评论”服务(评论),实现了一对多的通信类型(一篇文章可能有多个评论),而“用户”和“类别”服务则具有多对一的连接(即,一个用户可以有多篇文章,一篇类别可以是几篇文章)。
在功能方面,将在邮政服务中实现以下方法:
- 使用TraceId(与发出的api-gw相同;请参阅第2部分“ API网关” )记录服务请求和中间状态(该机制在第3部分“用户服务”中详细描述)。
- CRUD功能(在数据库中创建,读取,编辑,删除记录-MongoDB)。
- 搜索功能:搜索所有文章,按类别搜索,按作者搜索
传统上,我们将通过在原型文件中描述微服务来开始创建微服务。
接下来,我们生成微服务框架。 为此,请转到项目的根目录并执行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的记录。 您可以按照以下步骤进行操作:
...
我想指出的是,我故意使用两个不同的术语“用户”和“作者”,因为 我相信它们存在于不同的环境中。 用户是关于用户名/密码身份验证以及其他与安全性和访问方式相关的属性和功能。 作者是有关已发布帖子,评论等的实体。 Author实体在Post上下文中生成,使用User的数据作为基础。 (我希望我能解释其中的区别;)
下一步是从类别服务中读取相关类别的数据。 我不确定我是否提出了最佳选择(我希望社区予以纠正)。 该方法的本质如下:我们向Category服务发出一个请求,并减去所有现有的类别,然后在Post服务中,我们仅选择与Post相关的那些类别。 这种方法的缺点是传输数据的开销,加上-我们仅发出一个请求。 因为 类别的数量绝对不是压倒性的数目,我认为可以忽略开销。
...
我们下一步要做的就是获取所有相关注释。 在此,该任务与具有类别的任务相似,除了在类别ID的情况下,相关类别存储在Post中,在注释的情况下,父Post的ID直接存储在子注释中。 实际上,这大大简化了任务,因为 我们需要做的就是向评论服务发出请求,指示父帖子并处理结果-在循环中将所有相关的帖子添加到帖子中
...
并返回收集的帖子
... out.Post=post return out,nil ...
在Web界面中,我们已按类别和作者实现了导航。 即 当用户单击类别时,将显示所有链接到所选类别的文章的列表。 当他单击作者时,将相应地显示文章列表,作者在此处显示所选用户。
有两种方法可以在Post服务中实现此功能:
GetPostCategory-返回一个PostCategory结构,其中包含ID,类别名称和相关文章的集合
GetAuthor-返回一个Author结构,该结构包含用户属性(FirstName,LastName等)和相关Post的集合。
为了避免重复,我将不详细描述这些方法的实现。 它们基于与上述相同的代码片段。