كتابة مدونة Microservice - الجزء 4 بعد الخدمة

هذه المقالة هي امتداد لتاريخ كتابة مدونة تجريبية على الخدمات المصغرة (يمكن قراءة الأجزاء السابقة هنا: الجزء 1 "الوصف العام للعمارة" ، الجزء 2 "بوابة API" ، الجزء 3 "مستخدم الخدمة" ). سوف تركز هذه المقالة على تنفيذ Post post microservice (المقالات).

الميزة الرئيسية للجهاز microservice هي أنه ينفذ أنواعًا مختلفة من الاتصالات مع خدمات أخرى. على سبيل المثال ، من خلال خدمة التعليقات (التعليقات) ، يتم تنفيذ نوع الاتصال من شخص إلى عدة أشخاص (قد تحتوي إحدى المقالات على عدة تعليقات) ، ولخدمات المستخدم والفئة العديد من الاتصالات (على سبيل المثال ، يمكن أن يكون لدى مستخدم واحد العديد من المقالات وواحد فئات يمكن أن يكون عدة مقالات).

من حيث الوظيفة ، سيتم تطبيق الطرق التالية في خدمة النشر:

  • تسجيل طلبات الخدمة والحالات الوسيطة (يتم وصف الآلية بالتفصيل في الجزء 3 "خدمة المستخدم") مع TraceId (نفس إصدار api-gw ، انظر الجزء 2 "بوابة API" )
  • وظائف CRUD (إنشاء ، قراءة ، تحرير ، حذف السجلات في قاعدة البيانات - MongoDB).
  • وظائف البحث: البحث عن جميع المقالات ، البحث حسب الفئة ، البحث عن طريق المؤلف

تقليديًا ، سنبدأ في إنشاء خدمة microservice من خلال وصفها في ملف تعريف.

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

بعد ذلك ، نقوم بإنشاء إطار عمل microservice. للقيام بذلك ، انتقل إلى الدليل الجذر للمشروع وتنفيذ الأمر sh ./bin/protogen.sh.

سوبر! تم إنجاز معظم العمل بالنسبة لنا بواسطة مُنشئ الشفرة ، وكان علينا فقط كتابة تنفيذ لوظائف التطبيق. افتح الملف ./services/post/functions.go واكتب التنفيذ.

النظر في الأجزاء الرئيسية من وظيفة إنشاء.

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. حفظ المشاركة في قاعدة البيانات (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. نتلقى معرف السجل الذي تم إنشاؤه ، ونضيفه إلى الإجابة ونعيد الإجابة.

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

ذكرت في وقت سابق أن خدمة البريد مثيرة للاهتمام لصلاتها مع الخدمات الأخرى. يتم توضيح ذلك بوضوح من خلال طريقة Get (الحصول على المشاركة بمعرف معين).

أولاً ، اقرأ من 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 ثم استخدمها في التصفية للبحث عن السجل.

نحتاج الآن إلى إثراء سجل النشر الناتج ببيانات حول المؤلف. للقيام بذلك ، انتقل إلى خدمة المستخدم واحصل على سجل لـ 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 ... 

أريد أن أشير إلى أنني أستخدم عن قصد مصطلحين مختلفين المستخدم والمؤلف ، لأن أعتقد أنهم يكمنون في سياقات مختلفة. المستخدم هو حول مصادقة أسماء المستخدمين / كلمات المرور والسمات والوظائف الأخرى بطريقة أو بأخرى متعلقة بالأمان والوصول. المؤلف هو كيان عن المنشورات والتعليقات المنشورة وغير ذلك الكثير. يتم إنشاء الكيان المؤلف في سياق النشر ، باستخدام البيانات من المستخدم كأساس. (آمل أن أتمكن من شرح الفرق ؛)

الخطوة التالية هي قراءة بيانات الفئات ذات الصلة من خدمة الفئة. لست متأكدًا من أنني أقترح الخيار الأفضل (آمل أن يقوم المجتمع بتصحيحه). جوهر النهج كما يلي: نحن نطلب طلب واحد لخدمة الفئة ونطرح جميع الفئات الحالية ، ثم في خدمة النشر نختار فقط تلك الفئات المرتبطة بالنشر. عيب هذا النهج هو الحمل العام للبيانات المرسلة ، بالإضافة إلى - نحن نطلب طلب واحد فقط. لأن عدد الفئات هو بالتأكيد ليست كمية هائلة ، وأعتقد أنه يمكن إهمال النفقات العامة.

 ... //   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 ، في حالة التعليقات ، من ناحية أخرى ، يتم تخزين Id of the parent Post مباشرة في التعليقات الفرعية. في الواقع ، هذا يبسط المهمة إلى حد كبير ، لأن كل ما نحتاج إلى القيام به هو تقديم طلب إلى خدمة التعليقات يشير إلى نشر الوالد ومعالجة النتيجة - في حلقة إضافة إلى نشر جميع الرسائل ذات الصلة

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

في واجهة الويب ، قمنا بتطبيق التنقل حسب الفئة والمؤلف. أي عندما ينقر المستخدم على فئة ، يتم عرض قائمة بجميع المقالات التي تصل إلى الفئة المحددة. وعندما ينقر على المؤلف ، يتم عرض قائمة بالمقالات وفقًا لذلك ، حيث يشير المستخدم إلى المستخدم المحدد.

هناك طريقتان لتطبيق هذه الوظيفة في خدمة النشر:

إرجاع GetPostCategory - بنية PostCategory التي تحتوي على المعرف واسم الفئة ومجموعة المقالات ذات الصلة
GetAuthor - إرجاع بنية المؤلف التي تحتوي على سمات المستخدم (الاسم الأول واسم العائلة ، وما إلى ذلك) ومجموعة من النشرات ذات الصلة.

لن أصف بالتفصيل تنفيذ هذه الأساليب حتى لا تتكرر. تستند إلى نفس أجزاء التعليمات البرمجية الموضحة أعلاه.

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


All Articles