こんにちは。KOUKIです。
今回は、BlogAPIをgRPCで実装します。
<目次>
前回
Go言語記事まとめ
プロジェクトSet UP!
次のディレクトリとファイルを作成してください。
1 2 3 4 5 6 |
mkdir -p blog/blogpb mkdir blog/blog_server mkdir blog/blog_client touch blog/blogpb/blog.proto touch blog/blog_server/server.go touch blog/blog_client/client.go |
BlogAPI – protoファイルの作成 –
最初にprotoファイルを作成しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
syntax = "proto3"; package blog; option go_package = "blogpg"; message Blog { string id = 1; string author_id = 2; string title = 3; string content = 4; } message CreateBlogRequest { Blog blog = 1; } message CreateBlogResponse { Blog blog = 1; // will have a blog id } service BlogService { rpc CreateBlog (CreateBlogRequest) returns (CreateBlogResponse); } |
次にgenerate.shに以下のコマンドを追加してください。
1 2 3 4 5 6 7 8 9 |
#!/bin/bash export PATH=$PATH:/Users/<user>/go/bin protoc greet/greetpb/greet.proto --go_out=plugins=grpc:. protoc calculator/calculatorpb/calculator.proto --go_out=plugins=grpc:. # new protoc blog/blogpb/blog.proto --go_out=plugins=grpc:. |
コンパイルします。
1 |
./generate.sh |
BlogAPI – Serverコードの作成 –
Serverコードを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
// blog/blog_server/server.go package main import ( "fmt" "log" "net" "os" "os/signal" blogpb "github.com/selfnote/golang-grpc/blog/blogpb" "google.golang.org/grpc" ) type server struct{} func main() { // if we crash the go code, we get the file name and line number log.SetFlags(log.LstdFlags | log.Lshortfile) fmt.Println("Blog Service Started") lis, err := net.Listen("tcp", "0.0.0.0:50051") if err != nil { log.Fatalf("Failed to listen: %v", err) } opts := []grpc.ServerOption{} s := grpc.NewServer(opts...) blogpb.RegisterBlogServiceServer(s, &server{}) go func() { fmt.Println("Starting Server....") if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }() // Wait for control C to exit ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt) // Block until a signal is received <-ch fmt.Println("Stopping the server") s.Stop() fmt.Println("Closing the listener") lis.Close() fmt.Println("End of Program") } |
BlogAPI – Makefile修正 –
Makefileに下記のコマンドを追加してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
.PHONY: server client c_srv c_cnt b_srv b_cnt server: go run greet/greet_server/server.go client: go run greet/greet_client/client.go b_srv: go run blog/blog_server/server.go b_cnt: go run blog/blog_client/client.go c_srv: go run calculator/calculator_server/server.go c_cnt: go run calculator/calculator_client/client.go |
BlogAPI – MongoDBのインストール –
DBにMongoDBを使用するので、インストールしてください。
以下は、Linux Centos7のインストール例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# リポジトリ作成 sudo vi /etc/yum.repos.d/mongodb-org-4.0.repo # リポジトリの中身 [mongodb-org-4.0] name=MongoDB Repository baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/4.0/x86_64/ gpgcheck=1 enabled=1 gpgkey=https://www.mongodb.org/static/pgp/server-4.0.asc # インストール sudo yum install -y mongodb-org # バージョン確認 mongod -version db version v4.0.13 # 起動 sudo systemctl start mongod # 確認コマンド sudo systemctl status mongod ● mongod.service - MongoDB Database Server Loaded: loaded (/usr/lib/systemd/system/mongod.service; enabled; vendor preset: disabled) Active: active (running) since 月 2019-10-28 11:44:39 JST; 15s ago |
MongoDBを導入できたらmonogo-go-driverをインストールします。
1 2 |
go get go.mongodb.org/mongo-driver/mongo go get github.com/globalsign/mgo |
BlogAPI – Serverコードの作成2 –
MongoDBへのアクセス準備は整ったので、Serverコードを書き換えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
// blog/blog_server/server.go package main import ( "context" "fmt" "log" "net" "os" "os/signal" "github.com/globalsign/mgo/bson" blogpb "github.com/selfnote/golang-grpc/blog/blogpb" "google.golang.org/grpc" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/bson/primitive" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) var collection *mongo.Collection type server struct{} type blogItem struct { ID bson.ObjectId `bson:"_id.omitempty"` AuthorID string `bson:"author_id"` Content string `bson:"content"` Title string `bson:"title"` } func (*server) CreateBlog(ctx context.Context, req *blogpb.CreateBlogRequest)(*blogpb.CreateBlogResponse, error) { fmt.Println("Create blog request") blog := req.GetBlog() data := blogItem{ AuthorID: blog.GetAuthorId(), Title: blog.GetTitle(), Content: blog.GetContent(), } res, err := collection.InsertOne(context.Background(), data) if err != nil { return nil, status.Errorf( codes.Internal, fmt.Sprintf("Internal Error: %v", err), ) } oid, ok := res.InsertedID.(primitive.ObjectID) if !ok { return nil, status.Errorf( codes.Internal, fmt.Sprintf("Cannot convert to OID"), ) } return &blogpb.CreateBlogResponse{ Blog: &blogpb.Blog{ Id: oid.Hex(), AuthorId: blog.GetAuthorId(), Title: blog.GetTitle(), Content: blog.GetContent(), }, }, nil } func main() { // if we crash the go code, we get the file name and line number log.SetFlags(log.LstdFlags | log.Lshortfile) fmt.Println("Connecting to MongoDB") // connect to MongoDB client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017")) if err != nil { log.Fatal(err) } err = client.Connect(context.TODO()) if err != nil { log.Fatal(err) } fmt.Println("Blog Service Started") collection = client.Database("mydb").Collection("blog") lis, err := net.Listen("tcp", "0.0.0.0:50051") if err != nil { log.Fatalf("Failed to listen: %v", err) } collection = client.Database("mydb").Collection("blog") opts := []grpc.ServerOption{} s := grpc.NewServer(opts...) blogpb.RegisterBlogServiceServer(s, &server{}) go func() { fmt.Println("Starting Server....") if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }() // Wait for control C to exit ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt) // Block until a signal is received <-ch fmt.Println("Stopping the server") s.Stop() fmt.Println("Closing the listener") lis.Close() fmt.Println("Closing MongoDB Connection") client.Disconnect(context.TODO()) fmt.Println("End of Program") } |
BlogAPI – Client コードの作成 –
最後にClientコードを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
// blog/blog_client/client.go package main import ( "context" "fmt" "log" blogpb "github.com/selfnote/golang-grpc/blog/blogpb" "google.golang.org/grpc" ) func main() { fmt.Println("Blog Client") opts := grpc.WithInsecure() cc, err := grpc.Dial("localhost:50051", opts) if err != nil { log.Fatalf("could not connect: %v", err) } defer cc.Close() c := blogpb.NewBlogServiceClient(cc) // create Blog fmt.Println("Create a blog") blog := &blogpb.Blog{ AuthorId: "j.k rowling", Title: "Harry Potter", Content: "Magical Stuend Story", } createBlogRes, err := c.CreateBlog(context.Background(), &blogpb.CreateBlogRequest{Blog: blog}) if err != nil { log.Fatal("Unexpected error: %v", err) } fmt.Println("Blog has been created: %v", createBlogRes) } |
BlogAPI – 動作確認 –
動作確認をしてみましょう。
Server -> Clientの順にプログラムを実行してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
make b_srv go run blog/blog_server/server.go Connecting to MongoDB Blog Service Started Starting Server.... Create blog request make b_cnt go run blog/blog_client/client.go Blog Client Create a blog Blog has been created: %v blog:<id:"5db672ddd3b1266bd860da8b" author_id:"j.k rowling" title:"Harry Potter" content:"Magical Stuend Story" > |
上手くいきましたね。この調子で、データ一覧の取得やデータ削除などにもTryしてみてください。
MongoDB関連記事
おまけ: DBデータ全件取得
MongoDBのデータを全件取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
syntax = "proto3"; package blog; option go_package = "blogpg"; message Blog { string id = 1; string author_id = 2; string title = 3; string content = 4; } message CreateBlogRequest { Blog blog = 1; } message CreateBlogResponse { Blog blog = 1; } message FindBlogRequest { } message FindBlogResponse { Blog blog = 1; } service BlogService { rpc CreateBlog (CreateBlogRequest) returns (CreateBlogResponse); // new rpc FindBlog (FindBlogRequest) returns (stream FindBlogResponse); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
// blog/blog_server/server.go package main import ( "context" "fmt" "log" "net" "os" "os/signal" "github.com/globalsign/mgo/bson" blogpb "github.com/selfnote/golang-grpc/blog/blogpb" "google.golang.org/grpc" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/bson/primitive" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) var collection *mongo.Collection type server struct{} type blogItem struct { ID bson.ObjectId `bson:"_id.omitempty"` AuthorID string `bson:"author_id"` Content string `bson:"content"` Title string `bson:"title"` } func (*server) CreateBlog(ctx context.Context, req *blogpb.CreateBlogRequest)(*blogpb.CreateBlogResponse, error) { fmt.Println("Create blog request") blog := req.GetBlog() data := blogItem{ AuthorID: blog.GetAuthorId(), Title: blog.GetTitle(), Content: blog.GetContent(), } res, err := collection.InsertOne(context.Background(), data) if err != nil { return nil, status.Errorf( codes.Internal, fmt.Sprintf("Internal Error: %v", err), ) } oid, ok := res.InsertedID.(primitive.ObjectID) if !ok { return nil, status.Errorf( codes.Internal, fmt.Sprintf("Cannot convert to OID"), ) } return &blogpb.CreateBlogResponse{ Blog: &blogpb.Blog{ Id: oid.Hex(), AuthorId: blog.GetAuthorId(), Title: blog.GetTitle(), Content: blog.GetContent(), }, }, nil } // new func (*server) FindBlog(req *blogpb.FindBlogRequest, stream blogpb.BlogService_FindBlogServer) error { fmt.Println("Find blog data") data := &blogItem{} cursor, err := collection.Find(context.Background(), bson.M{}) if err != nil { return status.Errorf(codes.Internal, fmt.Sprintf("Unknown internal error: %v", err)) } defer cursor.Close(context.Background()) for cursor.Next(context.Background()) { err := cursor.Decode(data) if err != nil { return status.Errorf(codes.Unavailable, fmt.Sprintf("Could not decode data: %v", err)) } stream.Send(&blogpb.FindBlogResponse{ Blog: &blogpb.Blog { Id: data.ID.Hex(), AuthorId: data.AuthorID, Content: data.Content, Title: data.Title, }, }) } if err := cursor.Err(); err != nil { return status.Errorf(codes.Internal, fmt.Sprintf("Unknown cursor error: %v", err)) } return nil } func main() { // if we crash the go code, we get the file name and line number log.SetFlags(log.LstdFlags | log.Lshortfile) fmt.Println("Connecting to MongoDB") // connect to MongoDB client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017")) if err != nil { log.Fatal(err) } err = client.Connect(context.TODO()) if err != nil { log.Fatal(err) } fmt.Println("Blog Service Started") collection = client.Database("mydb").Collection("blog") lis, err := net.Listen("tcp", "0.0.0.0:50051") if err != nil { log.Fatalf("Failed to listen: %v", err) } collection = client.Database("mydb").Collection("blog") opts := []grpc.ServerOption{} s := grpc.NewServer(opts...) blogpb.RegisterBlogServiceServer(s, &server{}) go func() { fmt.Println("Starting Server....") if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }() // Wait for control C to exit ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt) // Block until a signal is received <-ch fmt.Println("Stopping the server") s.Stop() fmt.Println("Closing the listener") lis.Close() fmt.Println("Closing MongoDB Connection") client.Disconnect(context.TODO()) fmt.Println("End of Program") } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package main import ( "context" "fmt" "io" "log" blogpb "github.com/selfnote/golang-grpc/blog/blogpb" "google.golang.org/grpc" ) func main() { fmt.Println("Blog Client") opts := grpc.WithInsecure() cc, err := grpc.Dial("localhost:50051", opts) if err != nil { log.Fatalf("could not connect: %v", err) } defer cc.Close() c := blogpb.NewBlogServiceClient(cc) // create Blog fmt.Println("Create a blog") //blog := &blogpb.Blog{ //AuthorId: "j.k rowling", //Title: "Harry Potter", //Content: "Magical Stuend Story", //} // createBlogRes, err := c.CreateBlog(context.Background(), &blogpb.CreateBlogRequest{Blog: blog}) resStream, err := c.FindBlog(context.Background(), &blogpb.FindBlogRequest{}) if err != nil { log.Fatal("Unexpected error: %v", err) } for { msg, err := resStream.Recv() if err == io.EOF { fmt.Println(io.EOF) // EOF break } if err != nil { log.Fatalf("error while reading stream: %v", err) } log.Printf("Response from GreetManyTimes: %v", msg.GetBlog()) } // fmt.Println("Blog has been created: %v", createBlogRes) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
make b_srv go run blog/blog_server/server.go Connecting to MongoDB Blog Service Started Starting Server.... Find blog data make b_cnt go run blog/blog_client/client.go Blog Client Create a blog 2019/10/28 14:55:01 Response from GreetManyTimes: author_id:"j.k rowling" title:"Harry Potter" content:"Magical Stuend Story" 2019/10/28 14:55:01 Response from GreetManyTimes: author_id:"j.k rowling" title:"Harry Potter" content:"Magical Stuend Story" 2019/10/28 14:55:01 Response from GreetManyTimes: author_id:"j.k rowling" title:"Harry Potter" content:"Magical Stuend Story" 2019/10/28 14:55:01 Response from GreetManyTimes: author_id:"j.k rowling" title:"Harry Potter" content:"Magical Stuend Story" 2019/10/28 14:55:01 Response from GreetManyTimes: author_id:"j.k rowling" title:"Harry Potter" content:"Magical Stuend Story" 2019/10/28 14:55:01 Response from GreetManyTimes: author_id:"j.k rowling" title:"Harry Potter" content:"Magical Stuend Story" 2019/10/28 14:55:01 Response from GreetManyTimes: author_id:"j.k rowling" title:"Harry Potter" content:"Magical Stuend Story" 2019/10/28 14:55:01 Response from GreetManyTimes: author_id:"j.k rowling" title:"Harry Potter" content:"Magical Stuend Story" 2019/10/28 14:55:01 Response from GreetManyTimes: author_id:"j.k rowling" title:"Harry Potter" content:"Magical Stuend Story" 2019/10/28 14:55:01 Response from GreetManyTimes: author_id:"j.k rowling" title:"Harry Potter" content:"Magical Stuend Story" EOF |
おわりに
Go言語でのgRPCの学習は、今回で最後です。
色々なAPIの作成に挑戦してみてください。
コメントを残す
コメントを投稿するにはログインしてください。