[gRPC] Simple gRPC calls with Golang and Flutter


Post Series (click to expand)

Implementing gRPC server with Protobuf, Golang, Flutter

πŸ‘‰ Part 1 - [gRPC] Making gRPC API with Protocol buffers
πŸ‘‰ Part 2 - [gRPC] Simple gRPC calls with Golang and Flutter

gRPC Server

이전 ν¬μŠ€νŠΈμ—μ„œ 컴파일된 μ½”λ“œλ₯Ό μ΄μš©ν•˜μ—¬ Golang 으둜 gRPC μ„œλ²„λ₯Ό λ§Œλ“€μ–΄λ³΄μž.

Go Modules

ν”„λ‘œμ νŠΈ 루트의 go.mod νŒŒμΌμ— μ•„λž˜ λ‚΄μš©μ„ μΆ”κ°€ν•œλ‹€.

require (
  ...
  protocols v1.0.0
)
...

replace (
  protocols => ../protocols
)
go.mod

아직은 Github λ“±μ˜ git repository 에 μ˜¬λ¦¬μ§€ μ•Šκ³ , λ‘œμ»¬μ—μ„œ μž„ν¬νŠΈν•  것이기 λ•Œλ¬Έμ— replace ꡬ문을 μ΄μš©ν•˜μ—¬ μƒλŒ€κ²½λ‘œλ₯Ό 지정해쀀닀. 둜컬 μƒλŒ€ 경둜둜 replace ν•  경우 버전 μŠ€νŠΈλ§μ€ vX.Y.Z 의 포맷만 μ§€μΌœμ„œ μž„μ˜λ‘œ λ„£μ–΄μ£Όλ©΄ λœλ‹€.

전체 Project 의 root 디렉토리에 server, client, protocols 의 디렉토리λ₯Ό λ§Œλ“€κ³  κ·Έ μ•ˆμ— 각각의 ν”„λ‘œμ νŠΈλ₯Ό μƒμ„±ν•˜λ©΄ μƒλŒ€ 경둜둜 μ°Έμ‘°ν•˜κΈ°κ°€ νŽΈν•˜λ‹€.

example
β”œβ”€β”€ client
β”œβ”€β”€ protocols
└── server

API server

RPC API 의 λ‘œμ§μ„ μ‹€ν–‰ν•˜λŠ” λΆ€λΆ„. 데이터λ₯Ό μ €μž₯ν•˜κ³ , μ°Ύμ•„μ˜€κ³ , μˆ˜μ •ν•˜λŠ” λ“± API ν˜ΈμΆœμ— λ”°λ₯Έ business logic 이 λ“€μ–΄κ°ˆ 뢀뢄이닀. λ¨Όμ € μ•žμ—μ„œ protobuf λ₯Ό μ΄μš©ν•΄ μƒμ„±ν•œ μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄λ©΄,

...
// UserApiServer is the server API for UserApi service.
// All implementations must embed UnimplementedUserApiServer
// for forward compatibility
type UserApiServer interface {
	GetUser(context.Context, *GetUserRequest) (*GetUserReply, error)
	AddUser(context.Context, *AddUserRequest) (*AddUserReply, error)
	mustEmbedUnimplementedUserApiServer()
}

// UnimplementedUserApiServer must be embedded to have forward compatible implementations.
type UnimplementedUserApiServer struct {
}

func (UnimplementedUserApiServer) GetUser(context.Context, *GetUserRequest) (*GetUserReply, error) {
	return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented")
}
func (UnimplementedUserApiServer) AddUser(context.Context, *AddUserRequest) (*AddUserReply, error) {
	return nil, status.Errorf(codes.Unimplemented, "method AddUser not implemented")
}
func (UnimplementedUserApiServer) mustEmbedUnimplementedUserApiServer() {}
...
api_user_grpc.pb.go

β€˜service UserApi’ 둜 μ„€μ •ν•œ rpc ν˜ΈμΆœμ„ 받을 수 μžˆλŠ” interface κ°€ μ •μ˜λ˜μ–΄ μžˆλ‹€. μ£Όμ„μ˜ μ„€λͺ…λŒ€λ‘œ UnimplementedUserApiServer interface λ₯Ό μž„λ² λ“œν•˜μ—¬ API server ꡬ쑰체λ₯Ό μ •μ˜ν•˜κ³ , ν•„μš”ν•œ API handler function 듀을 κ΅¬ν˜„ν•œλ‹€.

import(
  ...
  "google.golang.org/grpc/codes"
  "google.golang.org/grpc/status"
  "protocols/goapi"
)

// API server struct.
type UserApi struct {
  goapi.UnimplementedUserApiServer
}

func (u *UserApi) GetUser(ctx context.Context, request *goapi.GetUserRequest) (*goapi.GetUserReply, error) {
  // rpc GetUser(GetUserRequest) returns (GetUserReply) {}
  ...
  // example success return
  return &goapi.GetUserReply{
    Users: []*goapi.User{user},
  }, nil

  // example error return
  return nil, status.Errorf(codes.NotFound, "email: %s", email)
}

func (u *UserApi) AddUser(ctx context.Context, request *goapi.AddUserRequest) (*goapi.AddUserReply, error) {
  // rpc AddUser(AddUserRequest) returns (AddUserReply) {}
}
user_api.go

grpc Server

API server λ₯Ό μ •μ˜ν•˜κ³  κ΅¬ν˜„ν•œ λ’€, 객체λ₯Ό μƒμ„±ν•˜μ—¬ grpc server 객체에 등둝해주면 ν•΄λ‹Ήν•˜λŠ” rpc call 이 λΌμš°νŒ…λ˜μ–΄ handler function 이 μ‹€ν–‰λœλ‹€.

import (
	...
	"google.golang.org/grpc"
	"protocols/goapi"
)

func main() {
  // grpc server 객체λ₯Ό 생성
  grpcServer := grpc.NewServer()
  // UserApi 객체λ₯Ό μƒμ„±ν•˜μ—¬ grpc server 에 등둝
  goapi.RegisterUserApiServer(grpcServer, &UserApi{})

  lis, err := net.Listen("tcp", ":8080")
  if err != nil {
    log.Fatal(err)
  }
  if err := grpcServer.Serve(lis); err != nil {
    log.Fatal(err)
  }
}
main.go

gRPC Client

Flutter μ—μ„œ grpc client λ₯Ό λ§Œλ“€κ³ , rpc call 을 톡해 데이터λ₯Ό λ°›μ•„ 좜λ ₯ν•΄λ³΄μž.

Import protocols

Protocol Buffer compiler κ°€ μƒμ„±ν•œ νŒŒμΌμ„ μž„ν¬νŠΈν•œλ‹€. pubspec.yaml 의 dependencies μ„Ήμ…˜μ— μ•„λž˜ λ‚΄μš©μ„ μΆ”κ°€.

  grpc: ^3.0.2
  dartapi:
    path: ../protocols/dartapi
pubspec.yaml

gRPC Client

Future<List<User>> getUser() async {
  final channel = ClientChannel('localhost',
    port: 8080,
    options: ChannelOptions(
      credentials: const ChannelCredentials.insecure(),
    )
  );

  UserApiClient userApiClient = UserApiClient(channel);
  final resp = await userApiClient.getUser(
    GetUserRequest()..email="user@email.com"
  );
  return resp.users;
}

μ„œλ²„ μͺ½μ— λΉ„ν•˜λ©΄ μƒλ‹Ήνžˆ λ‹¨μˆœν•œλ‹€. 둜컬 ν…ŒμŠ€νŠΈ λ‹¨κ³„μ—μ„œ μ•”ν˜Έν™”λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šμ„ κ²ƒμ΄λ―€λ‘œ, ChannelCredentials.insecure() μ˜΅μ…˜μ„ μ§€μ •ν•˜κ³  grpc.ClientChannel 을 μƒμ„±ν•œλ‹€. 이 채널을 μ΄μš©ν•˜λŠ” UserApiClient λ₯Ό μƒμ„±ν•˜κ³ , ν΄λΌμ΄μ–ΈνŠΈ 객체의 member function 을 ν˜ΈμΆœν•˜λ©΄ rpc κ°€ ν˜ΈμΆœλœλ‹€.