एक माइक्रोसॉर्फ़ ब्लॉग लिखना - भाग 4 पोस्ट सेवा

यह लेख माइक्रोसॉर्फ़ पर डेमो ब्लॉग लिखने के इतिहास की एक निरंतरता है (पिछले भागों को यहां पढ़ा जा सकता है: भाग 1 "वास्तुकला का सामान्य विवरण" , भाग 2 "एपीआई गेटवे" , भाग 3 "सेवा उपयोगकर्ता" )। यह लेख पोस्ट माइक्रोसर्विस (लेख) के कार्यान्वयन पर केंद्रित होगा।

माइक्रोसर्विस की मुख्य विशेषता यह है कि यह अन्य सेवाओं के साथ विभिन्न प्रकार के कनेक्शनों को लागू करता है। उदाहरण के लिए, टिप्पणियाँ सेवा (टिप्पणियां) के साथ, एक-से-कई प्रकार के संचार लागू होते हैं (एक लेख में कई टिप्पणियाँ हो सकती हैं), और उपयोगकर्ता और श्रेणी सेवाओं में कई-से-एक कनेक्शन होते हैं (यानी, एक उपयोगकर्ता के कई लेख और एक हो सकते हैं) श्रेणियां कई लेख हो सकती हैं)।

कार्यक्षमता के संदर्भ में, पोस्ट सेवा में निम्नलिखित विधियों को लागू किया जाएगा:

  • सेवा अनुरोधों और मध्यवर्ती राज्यों के लॉगिंग (तंत्र भाग 3 "उपयोगकर्ता सेवा" में विस्तार से वर्णित है) ट्रेसआईडी (एपीआई-जीडब्ल्यू के रूप में ही जारी किया गया है; भाग 2 "एपीआई गेटवे" देखें;
  • 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 और कार्यान्वयन लिखें।

फ़ंक्शन बनाने के मुख्य अंशों पर विचार करें।

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

डेटाबेस (mongoDB) में 3. पोस्ट सहेजें।

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

मैंने पहले उल्लेख किया है कि पोस्ट सेवा अन्य सेवाओं के साथ अपने कनेक्शन के लिए दिलचस्प है। यह स्पष्ट रूप से गेट विधि (दी गई आईडी द्वारा पोस्ट प्राप्त करें) द्वारा प्रदर्शित किया जाता है।

सबसे पहले, mongoDB पोस्ट से पढ़ें:

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

यहाँ सब कुछ कमोबेश सरल है। सबसे पहले, स्ट्रिंग को ऑब्जेक्ट में कनवर्ट करें और फिर रिकॉर्ड की खोज के लिए फ़िल्टर में इसका उपयोग करें।

अब हमें लेखक के बारे में डेटा के साथ परिणामी पोस्ट रिकॉर्ड को समृद्ध करने की आवश्यकता है। ऐसा करने के लिए, उपयोगकर्ता सेवा पर जाएँ और निर्दिष्ट 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) } } } ... 

अगली बात जो हमें करनी चाहिए वह है सभी संबंधित टिप्पणियां। यहां, कार्य श्रेणियों के साथ कार्य के समान है, सिवाय इसके कि श्रेणियों आईडी के मामले में, संबंधित श्रेणियां पोस्ट में संग्रहीत की गईं, टिप्पणियों के मामले में, दूसरी ओर, मूल पोस्ट की आईडी सीधे बाल टिप्पणियों में संग्रहीत की जाती है। वास्तव में, यह कार्य को सरल करता है, क्योंकि हम सभी को करने की आवश्यकता है एक टिप्पणी करने के लिए अनुरोध करते हैं सेवा माता-पिता को इंगित करते हुए पोस्ट करते हैं और परिणाम की प्रक्रिया करते हैं - पोस्ट से संबंधित सभी पोस्टकॉम में एक लूप में जोड़ें

 ... //   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 संरचना लौटाता है जिसमें ID, श्रेणी का नाम और संबंधित लेखों का संग्रह होता है
GetAuthor - एक लेखक संरचना लौटाता है जिसमें उपयोगकर्ता विशेषताएँ (FirstName, LastName, आदि) और संबंधित पोस्ट का संग्रह होता है।

मैं इन विधियों के कार्यान्वयन के बारे में विस्तार से नहीं बताऊंगा ताकि दोहराया न जाए। वे समान कोड अंशों पर आधारित हैं जो ऊपर वर्णित थे।

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


All Articles