Go言語で簡単なWebApiの実装をしてみます。
勉強の為に下記技術を用いてコーディングをしていきます。
※実装についてはgRPC通信を無理やり使用している為非効率な
実装になっています。
※protoコマンドを使用する為の準備等は省略しています。
【使用技術】
言語:Go
フレームワーク:Gin
ORM:sqlboiler
通信:gRPC通信
設計モデル:MVCモデルを意識しています
【フォルダ構成】
grpc-test
│
├─ go.mod
├─ go.sum
│
├─architect
│ 設計.xlsx
│
├─client
│ main.go
│
├─config
│ │ sqlboiler.toml
│ │
│ └─models
│ author.go
│ author_test.go
│ boil_main_test.go
│ boil_queries.go
│ boil_queries_test.go
│ boil_suites_test.go
│ boil_table_names.go
│ boil_types.go
│ boil_view_names.go
│ book.go
│ book_test.go
│ mysql_main_test.go
│ mysql_suites_test.go
│ mysql_upsert.go
│
├─controller
│ controller.go
│
├─pb
│ model.pb.go
│ model_grpc.pb.go
│
├─proto
│ model.proto
│
├─repository
│ repository.go
│
└─server
main.go
【実装物】
本のデータ(ID,Title)をCRUDするAPIを作成します。
http通信でrequestを送信するとresponseを返してくれるシステム実装します。
データ形式:json
使用ツール:postman
【Create】
「ID」と「Title」をrequestとして送信します。
createが成功したらその情報を表示させます。
【Read】
データの取得(全件/ID指定)を実装します。
【Update】
「ID」と「Title」をrequestとして送信します。
指定されたIDに対してTitleを変更します。
updateが成功したらその情報を表示させます。
【Delete】
「ID」をrequestとして送信します。
指定されたIDの情報を削除します。
削除が成功したら全件データを表示させます。
【protoファイルの作成】
■model.proto
syntax="proto3";
package proto;
option go_package="./pb";
message Book{
string ID =1;
string Title =2;
}
message Books{
repeated Book BookList=3;
}
//サーバー側のメソッドを定義
service BookService{
rpc getBooks(Book) returns (Books);
rpc getBook(Book) returns (Book);
rpc createBook(Book) returns (Book);
rpc updateBook(Book) returns (Book);
rpc deleteBook(Book) returns (Books);
}
gRPC通信を行う為にprotoファイルを作成します。
作成後下記コマンドを実行。
protoc --go_out=. --go-grpc_out=./ ./proto/*.proto
実行後、model.pb.goとmodel_grpc.pb.go作成される。
■model.pb.go
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v4.22.0
// source: proto/model.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Book struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"`
Title string `protobuf:"bytes,2,opt,name=Title,proto3" json:"Title,omitempty"`
}
func (x *Book) Reset() {
*x = Book{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_model_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Book) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Book) ProtoMessage() {}
func (x *Book) ProtoReflect() protoreflect.Message {
mi := &file_proto_model_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Book.ProtoReflect.Descriptor instead.
func (*Book) Descriptor() ([]byte, []int) {
return file_proto_model_proto_rawDescGZIP(), []int{0}
}
func (x *Book) GetID() string {
if x != nil {
return x.ID
}
return ""
}
func (x *Book) GetTitle() string {
if x != nil {
return x.Title
}
return ""
}
type Books struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
BookList []*Book `protobuf:"bytes,3,rep,name=BookList,proto3" json:"BookList,omitempty"`
}
func (x *Books) Reset() {
*x = Books{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_model_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Books) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Books) ProtoMessage() {}
func (x *Books) ProtoReflect() protoreflect.Message {
mi := &file_proto_model_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Books.ProtoReflect.Descriptor instead.
func (*Books) Descriptor() ([]byte, []int) {
return file_proto_model_proto_rawDescGZIP(), []int{1}
}
func (x *Books) GetBookList() []*Book {
if x != nil {
return x.BookList
}
return nil
}
var File_proto_model_proto protoreflect.FileDescriptor
var file_proto_model_proto_rawDesc = []byte{
0x0a, 0x11, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2c, 0x0a, 0x04, 0x42, 0x6f,
0x6f, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x09, 0x52, 0x05, 0x54, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x30, 0x0a, 0x05, 0x42, 0x6f, 0x6f, 0x6b,
0x73, 0x12, 0x27, 0x0a, 0x08, 0x42, 0x6f, 0x6f, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x03, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42, 0x6f, 0x6f, 0x6b,
0x52, 0x08, 0x42, 0x6f, 0x6f, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x32, 0xd2, 0x01, 0x0a, 0x0b, 0x42,
0x6f, 0x6f, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x08, 0x67, 0x65,
0x74, 0x42, 0x6f, 0x6f, 0x6b, 0x73, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42,
0x6f, 0x6f, 0x6b, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42, 0x6f, 0x6f, 0x6b,
0x73, 0x12, 0x23, 0x0a, 0x07, 0x67, 0x65, 0x74, 0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x0b, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42, 0x6f, 0x6f, 0x6b, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x26, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42, 0x6f, 0x6f,
0x6b, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x26,
0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x0b, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42, 0x6f, 0x6f, 0x6b, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x27, 0x0a, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x42, 0x6f, 0x6f, 0x6b, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42, 0x6f, 0x6f,
0x6b, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x42, 0x6f, 0x6f, 0x6b, 0x73, 0x42,
0x06, 0x5a, 0x04, 0x2e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proto_model_proto_rawDescOnce sync.Once
file_proto_model_proto_rawDescData = file_proto_model_proto_rawDesc
)
func file_proto_model_proto_rawDescGZIP() []byte {
file_proto_model_proto_rawDescOnce.Do(func() {
file_proto_model_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_model_proto_rawDescData)
})
return file_proto_model_proto_rawDescData
}
var file_proto_model_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proto_model_proto_goTypes = []interface{}{
(*Book)(nil), // 0: proto.Book
(*Books)(nil), // 1: proto.Books
}
var file_proto_model_proto_depIdxs = []int32{
0, // 0: proto.Books.BookList:type_name -> proto.Book
0, // 1: proto.BookService.getBooks:input_type -> proto.Book
0, // 2: proto.BookService.getBook:input_type -> proto.Book
0, // 3: proto.BookService.createBook:input_type -> proto.Book
0, // 4: proto.BookService.updateBook:input_type -> proto.Book
0, // 5: proto.BookService.deleteBook:input_type -> proto.Book
1, // 6: proto.BookService.getBooks:output_type -> proto.Books
0, // 7: proto.BookService.getBook:output_type -> proto.Book
0, // 8: proto.BookService.createBook:output_type -> proto.Book
0, // 9: proto.BookService.updateBook:output_type -> proto.Book
1, // 10: proto.BookService.deleteBook:output_type -> proto.Books
6, // [6:11] is the sub-list for method output_type
1, // [1:6] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_proto_model_proto_init() }
func file_proto_model_proto_init() {
if File_proto_model_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proto_model_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Book); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_model_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Books); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_model_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_proto_model_proto_goTypes,
DependencyIndexes: file_proto_model_proto_depIdxs,
MessageInfos: file_proto_model_proto_msgTypes,
}.Build()
File_proto_model_proto = out.File
file_proto_model_proto_rawDesc = nil
file_proto_model_proto_goTypes = nil
file_proto_model_proto_depIdxs = nil
}
■model_grpc.pb.go
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v4.22.0
// source: proto/model.proto
package pb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// BookServiceClient is the client API for BookService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type BookServiceClient interface {
GetBooks(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Books, error)
GetBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Book, error)
CreateBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Book, error)
UpdateBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Book, error)
DeleteBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Books, error)
}
type bookServiceClient struct {
cc grpc.ClientConnInterface
}
func NewBookServiceClient(cc grpc.ClientConnInterface) BookServiceClient {
return &bookServiceClient{cc}
}
func (c *bookServiceClient) GetBooks(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Books, error) {
out := new(Books)
err := c.cc.Invoke(ctx, "/proto.BookService/getBooks", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bookServiceClient) GetBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Book, error) {
out := new(Book)
err := c.cc.Invoke(ctx, "/proto.BookService/getBook", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bookServiceClient) CreateBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Book, error) {
out := new(Book)
err := c.cc.Invoke(ctx, "/proto.BookService/createBook", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bookServiceClient) UpdateBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Book, error) {
out := new(Book)
err := c.cc.Invoke(ctx, "/proto.BookService/updateBook", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bookServiceClient) DeleteBook(ctx context.Context, in *Book, opts ...grpc.CallOption) (*Books, error) {
out := new(Books)
err := c.cc.Invoke(ctx, "/proto.BookService/deleteBook", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// BookServiceServer is the server API for BookService service.
// All implementations must embed UnimplementedBookServiceServer
// for forward compatibility
type BookServiceServer interface {
GetBooks(context.Context, *Book) (*Books, error)
GetBook(context.Context, *Book) (*Book, error)
CreateBook(context.Context, *Book) (*Book, error)
UpdateBook(context.Context, *Book) (*Book, error)
DeleteBook(context.Context, *Book) (*Books, error)
mustEmbedUnimplementedBookServiceServer()
}
// UnimplementedBookServiceServer must be embedded to have forward compatible implementations.
type UnimplementedBookServiceServer struct {
}
func (UnimplementedBookServiceServer) GetBooks(context.Context, *Book) (*Books, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBooks not implemented")
}
func (UnimplementedBookServiceServer) GetBook(context.Context, *Book) (*Book, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetBook not implemented")
}
func (UnimplementedBookServiceServer) CreateBook(context.Context, *Book) (*Book, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateBook not implemented")
}
func (UnimplementedBookServiceServer) UpdateBook(context.Context, *Book) (*Book, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateBook not implemented")
}
func (UnimplementedBookServiceServer) DeleteBook(context.Context, *Book) (*Books, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteBook not implemented")
}
func (UnimplementedBookServiceServer) mustEmbedUnimplementedBookServiceServer() {}
// UnsafeBookServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to BookServiceServer will
// result in compilation errors.
type UnsafeBookServiceServer interface {
mustEmbedUnimplementedBookServiceServer()
}
func RegisterBookServiceServer(s grpc.ServiceRegistrar, srv BookServiceServer) {
s.RegisterService(&BookService_ServiceDesc, srv)
}
func _BookService_GetBooks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Book)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BookServiceServer).GetBooks(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/proto.BookService/getBooks",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BookServiceServer).GetBooks(ctx, req.(*Book))
}
return interceptor(ctx, in, info, handler)
}
func _BookService_GetBook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Book)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BookServiceServer).GetBook(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/proto.BookService/getBook",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BookServiceServer).GetBook(ctx, req.(*Book))
}
return interceptor(ctx, in, info, handler)
}
func _BookService_CreateBook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Book)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BookServiceServer).CreateBook(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/proto.BookService/createBook",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BookServiceServer).CreateBook(ctx, req.(*Book))
}
return interceptor(ctx, in, info, handler)
}
func _BookService_UpdateBook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Book)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BookServiceServer).UpdateBook(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/proto.BookService/updateBook",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BookServiceServer).UpdateBook(ctx, req.(*Book))
}
return interceptor(ctx, in, info, handler)
}
func _BookService_DeleteBook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Book)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BookServiceServer).DeleteBook(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/proto.BookService/deleteBook",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BookServiceServer).DeleteBook(ctx, req.(*Book))
}
return interceptor(ctx, in, info, handler)
}
// BookService_ServiceDesc is the grpc.ServiceDesc for BookService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var BookService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "proto.BookService",
HandlerType: (*BookServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "getBooks",
Handler: _BookService_GetBooks_Handler,
},
{
MethodName: "getBook",
Handler: _BookService_GetBook_Handler,
},
{
MethodName: "createBook",
Handler: _BookService_CreateBook_Handler,
},
{
MethodName: "updateBook",
Handler: _BookService_UpdateBook_Handler,
},
{
MethodName: "deleteBook",
Handler: _BookService_DeleteBook_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "proto/model.proto",
}
【Clientの実装】
■main.go
package main
import (
"grpc-test/controller"
)
func main() {
//controllerをオブジェクト化
cont := controller.NewController()
cont.Execute()
}
【Controllerの実装】
■controller.go
package controller
import (
"context"
"encoding/json"
"fmt"
"grpc-test/pb"
"log"
"net/http"
"os"
"github.com/gin-gonic/gin"
"google.golang.org/grpc"
)
type Controller struct {
}
func NewController() Controller {
//Controllerの構造体を返す
return Controller{}
}
func (c Controller) Execute() {
fmt.Println("Execute")
ctx := context.Background() //空のContextを生成
gin.SetMode(gin.ReleaseMode) //Ginのデバックモードを無効にする
router := gin.Default()
conn, err := grpc.Dial("localhost:8082", grpc.WithInsecure()) //指定したターゲット(localhost:8082)へ接続を作成している。 rpc.WithInsecure()は「認証をしないで接続する」という指定をしている。
if err != nil {
log.Fatalf("fail connect:%v", err)
}
defer conn.Close() //一通りの処理がclient側で完了したら接続を閉じる
client := pb.NewBookServiceClient(conn) //gRPCクライアントを生成している(gRPCクライアントを「localhost:8082」に接続)
//パスとメソッドを紐づけている。
//gRPC通信をする為に引数には必ずgRPCクライアントを持たせている。
//HTTP通信でGETメソッドを指定している
router.GET("/api/books", func(c *gin.Context) {
getBooks(ctx, c, client) //gin.Contextを引数に持たせてメソッドを呼び出している
})
router.GET("/api/book/:id", func(c *gin.Context) {
//パラメーターからidを取得する
getBook(ctx, c, client)
})
//HTTP通信でPOSTメソッドを指定している
router.POST("api/book/create", func(c *gin.Context) {
createBook(ctx, c, client)
})
router.POST("api/book/update", func(c *gin.Context) {
updateBook(ctx, c, client)
})
router.POST("api/book/delete", func(c *gin.Context) {
deleteBook(ctx, c, client)
})
router.Run(":8080") //ルーターをhttp.Serverに接続
}
func getBooks(ctx context.Context, c *gin.Context, client pb.BookServiceClient) {
fmt.Println("getBooks")
BookPb := pb.Book{}
res, err := client.GetBooks(context.Background(), &BookPb) //ここでサーバ側へ接続している
if err != nil {
fmt.Println(err)
}
c.JSON(http.StatusOK, &res) //サーバから正常にレスポンスが返ってきた場合JSON形式で結果が表示される。→POSTMANを使用した際に戻り値がJSONで表示される。
err = json.NewEncoder(os.Stdout).Encode(&res.BookList) //pb.Book構造体をJSONに変換。「os.Stdout」→コンソール上で結果(JSON形式)を表示。
if err != nil {
fmt.Println(err)
}
}
//以下同様
func getBook(ctx context.Context, c *gin.Context, client pb.BookServiceClient) {
fmt.Println("getBook")
id := c.Param("id")
BookPb := pb.Book{
ID: id,
}
res, err := client.GetBook(context.Background(), &BookPb)
if err != nil {
fmt.Println(err)
}
c.JSON(http.StatusOK, &res)
err = json.NewEncoder(os.Stdout).Encode(&res)
if err != nil {
fmt.Println(err)
}
}
func createBook(ctx context.Context, c *gin.Context, client pb.BookServiceClient) {
fmt.Println("crateBook")
BookPb := pb.Book{}
if err := c.ShouldBindJSON(&BookPb); err != nil {
panic(err)
}
fmt.Println("BookPb:", &BookPb)
res, err := client.CreateBook(context.Background(), &BookPb)
if err != nil {
panic(err)
}
c.JSON(http.StatusOK, &res)
err = json.NewEncoder(os.Stdout).Encode(&res)
if err != nil {
fmt.Println(err)
}
}
func updateBook(ctx context.Context, c *gin.Context, client pb.BookServiceClient) {
fmt.Println("updateBook")
BookPb := pb.Book{}
if err := c.ShouldBindJSON(&BookPb); err != nil {
panic(err)
}
fmt.Println("BookPb:", &BookPb)
res, err := client.UpdateBook(context.Background(), &BookPb)
if err != nil {
panic(err)
}
c.JSON(http.StatusOK, &res)
err = json.NewEncoder(os.Stdout).Encode(&res)
if err != nil {
fmt.Println(err)
}
}
func deleteBook(ctx context.Context, c *gin.Context, client pb.BookServiceClient) {
fmt.Println("crateBook")
BookPb := pb.Book{}
if err := c.ShouldBindJSON(&BookPb); err != nil {
panic(err)
}
fmt.Println("BookPb:", &BookPb)
res, err := client.DeleteBook(context.Background(), &BookPb)
if err != nil {
panic(err)
}
c.JSON(http.StatusOK, &res)
err = json.NewEncoder(os.Stdout).Encode(&res)
if err != nil {
fmt.Println(err)
}
}
【Serverの実装】
■main.go
package main
import (
"context"
"fmt"
"grpc-test/pb"
"grpc-test/repository"
"log"
"net"
"google.golang.org/grpc"
)
// BookServerという構造体を定義
type BookServer struct {
pb.UnimplementedBookServiceServer //gRPCサーバー実装時にUnimplementedBookServiceServerを埋め込んだ構造体を作る必要ある。
//こちらの構造体下記メソッドが定義されている。
}
// サーバー側のメソッドを定義
func (c *BookServer) GetBooks(ctx context.Context, req *pb.Book) (*pb.Books, error) {
fmt.Println("GetBooks was invoked")
repo := repository.NewRepository()
res, err := repo.FindAllBooks(ctx, req)
fmt.Println("res:", res)
if err != nil {
fmt.Println("err:", err)
}
return res, nil
}
func (c *BookServer) GetBook(ctx context.Context, req *pb.Book) (*pb.Book, error) {
fmt.Println("GetBook was invoked")
repo := repository.NewRepository()
res, err := repo.FindBookById(ctx, req)
fmt.Println("res:", res)
if err != nil {
fmt.Println("err")
}
fmt.Println("res2:", res)
return res, nil
}
func (c *BookServer) CreateBook(ctx context.Context, req *pb.Book) (*pb.Book, error) {
fmt.Println("CreateBook was invoked")
fmt.Println("req:", req)
repo := repository.NewRepository()
res, err := repo.AddBook(ctx, req)
fmt.Println("res:", res)
if err != nil {
panic("error")
}
return res, nil
}
func (c *BookServer) UpdateBook(ctx context.Context, req *pb.Book) (*pb.Book, error) {
fmt.Println("UpdateBook was invoked")
fmt.Println("req:", req)
repo := repository.NewRepository()
res, err := repo.EditBook(ctx, req)
fmt.Println("res:", res)
if err != nil {
panic("error")
}
return res, nil
}
func (c *BookServer) DeleteBook(ctx context.Context, req *pb.Book) (*pb.Books, error) {
fmt.Println("DeleteBook was invoked")
fmt.Println("req:", req)
repo := repository.NewRepository()
res, err := repo.EliminateBook(ctx, req)
fmt.Println("res:", res)
if err != nil {
panic("error")
}
return res, nil
}
func main() {
lis, err := net.Listen("tcp", "localhost:8082") //tcp通信を設定。
if err != nil {
log.Fatalf("failed to list:%v", err)
}
s := grpc.NewServer() //gRPCサーバーを作成。sはServer構造体。
pb.RegisterBookServiceServer(s, &BookServer{}) //gRPCサーバーにサービスを登録→sにBookServerを登録。
fmt.Println("server is running")
if err := s.Serve(lis); err != nil {
log.Fatalf("fail to serve:%v", err)
}
}
【Repositoryの実装】
sqlboilerコマンドが使用できるように下記コマンドを実施
go install github.com/volatiletech/sqlboiler/v4@latest
※情報によっては下記を入れると書いてある記事もあるがこちらをいれると後述するboil.SetDB()を使用する際に修正が必要になる。
go get -u -t github.com/vattle/sqlboiler
DB接続情報を記述したtomlファイルを作成する。
■sqlboiler.toml
add-global-variants=true
[mysql]
dbname = "test"
host = "localhost"
port = 3306
user = "root"
pass = ""
sslmode = "false"
sqlboilerコマンドでdbmodelを作成する。
※コマンドはtomlファイルのある階層で実施する。
sqlboiler mysql -t form --wipe --add-global-variants
--wipe
→既存のdbmodelファイルを削除するコマンドです。
--add-global-variants
→boil.SetDB()を動作させるためのコマンド。
boil.SetDB()はDBのコネクション情報をグローバル領域のメモリに保存する為の
メソッドである。
※注意
ここでboil.SetDB()を使用するのに詰まりました。
私の場合、boil.SetDB()を記述して保存をすると"github.com/vattle/sqlboiler/boil"が自動でimportされました。
boil.SetDB()はglobal.goの中で定義されているのですがvattleの配下にある
global.goファイルではうまく動作しません。
importする内容を"github.com/vattle/sqlboiler/boil"から"github.com/volatiletech/sqlboiler/v4/boil"に変更する事でboil.SetDB()を使用する事が出来るようになりました。
なのでboil.SetDB()を使用する際はimportするライブラリは必ず確認するようにして下さい。
■repository.go
package repository
import (
"context"
"database/sql"
"fmt"
"grpc-test/pb"
"log"
"grpc-test/config/models"
_ "github.com/go-sql-driver/mysql"
_ "github.com/mattn/go-sqlite3"
"github.com/volatiletech/null/v8"
"github.com/volatiletech/sqlboiler/v4/boil"
//"github.com/vattle/sqlboiler/boil"
)
// init()は必ず最初に実行されるメソッド
func init() {
con, er := sql.Open("mysql", "root:@tcp(localhost:3306)/test?charset=utf8mb4") //DBコネクションを設定
if er != nil {
panic(er)
}
boil.SetDB(con) //コネクション情報をグローバル領域に保存
}
type Repository struct {
}
func NewRepository() Repository {
return Repository{}
}
func (r Repository) FindAllBooks(ctx context.Context, req *pb.Book) (*pb.Books, error) {
var PbBooks = &pb.Books{} //空のpb.Books構造体を作成。※構造体を生成する際は{}が必要。
var PbBook = &pb.Book{}
Books, err := models.Books().AllG(ctx) //sqlboilerのメソッドはAllGにする事で引数にDB接続情報を入れなくてよくなる。
if err != nil {
return PbBooks, err
}
//DBから全件取得したBooks情報(型:models.BookSlice)を戻り値であるPbBooks(型:pb.Books)に格納する
for _, Book := range Books {
PbBook = &pb.Book{
ID: Book.ID,
Title: Book.Title.String,
}
PbBooks = &pb.Books{
BookList: append(PbBooks.BookList, PbBook), //PbBooks.BookListにPbBookを格納
}
}
return PbBooks, err
}
func (c Repository) FindBookById(ctx context.Context, req *pb.Book) (*pb.Book, error) {
var PbBook = &pb.Book{}
Book, err := models.FindBookG(ctx, req.ID)
if err != nil {
return PbBook, err
}
PbBook = &pb.Book{
ID: Book.ID,
Title: Book.Title.String,
}
return PbBook, err
}
func (c Repository) AddBook(ctx context.Context, req *pb.Book) (*pb.Book, error) {
var PbBook = &pb.Book{}
model := &models.Book{
ID: req.ID,
Title: null.StringFrom(req.Title),
}
fmt.Println("req.ID:", req.ID, "req.Title:", req.Title)
err := model.InsertG(ctx, boil.Infer())
if err != nil {
return PbBook, err
}
Book, err := models.FindBookG(ctx, req.ID)
if err != nil {
return PbBook, err
}
PbBook = &pb.Book{
ID: Book.ID,
Title: Book.Title.String,
}
return PbBook, err
}
func (c Repository) EditBook(ctx context.Context, req *pb.Book) (*pb.Book, error) {
var PbBook = &pb.Book{}
Book, err := models.FindBookG(ctx, req.ID)
if err != nil {
return PbBook, err
}
Book.ID = req.ID
Book.Title = null.StringFrom(req.Title)
rowsAff, err := Book.UpdateG(ctx, boil.Infer()) //rowsAff:アップデート件数
fmt.Println("rowAff:", rowsAff)
PbBook = &pb.Book{
ID: Book.ID,
Title: Book.Title.String,
}
return PbBook, err
}
func (c Repository) EliminateBook(ctx context.Context, req *pb.Book) (*pb.Books, error) {
var PbBooks = &pb.Books{}
var PbBook = &pb.Book{}
Book, err := models.FindBookG(ctx, req.ID)
if err != nil {
return PbBooks, err
}
rowsAff, err := Book.DeleteG(ctx)
fmt.Println("rowAff:", rowsAff)
Books, err := models.Books().AllG(ctx)
log.Print("Books:", Books)
for _, Book := range Books {
PbBook = &pb.Book{
ID: Book.ID,
Title: Book.Title.String,
}
PbBooks = &pb.Books{
BookList: append(PbBooks.BookList, PbBook),
}
}
return PbBooks, err
}
【動作確認】
POSTMANを使用して動作確認を実施します。
■全件取得(GETメソッド)
■ID指定で取得(GETメソッド)
■登録(POSTメソッド)
■変更(POSTメソッド)
■削除(POSTメソッド)
以上