robert-min / handson-go

Go-lang hands on guide
0 stars 0 forks source link

Chapter8. gRPC - Basic, Json to protobuf #23

Open robert-min opened 1 year ago

robert-min commented 1 year ago

RPC(Remote Procedure Call)

gRPC

robert-min commented 1 year ago

gRPC 서버 구축을 위한 프로토콜 버퍼 언어로 서비스 정의

// ./service/users.proto
syntax = "proto3";

option go_package = "github.com/handson-go/chap8/user_service/service";

service Users {
    rpc GetUser (UserGetRequest) returns (UserGetReply) {}
}

message UserGetRequest {
    string email = 1;
    string id = 2;
}

message User {
    string id = 1;
    string first_name = 2;
    string last_name = 3;
    int32 age = 4;
}

message UserGetReply {
    User user = 1;
}
robert-min commented 1 year ago

위에서 생성한 프로토콜 버퍼를 사용하는 서버 구축

go get google.golang.org/grpc@v1.37.0
package main

import (
    "context"
    "errors"
    "log"
    "net"
    "os"
    "strings"

    users "github.com/handson-go/chap8/user_service/service"
    "google.golang.org/grpc"
)

type userService struct {
    users.UnimplementedUsersServer
}

func (s *userService) GetUser(ctx context.Context, in *users.UserGetRequest) (*users.UserGetReply, error) {
    log.Printf(
        "Received request for user with Email: %s Id: %s\n",
        in.Email,
        in.Id,
    )
    components := strings.Split(in.Email, "@")
    if len(components) != 2 {
        return nil, errors.New("invalid email address")
    }

    u := users.User{
        Id:        in.Id,
        FirstName: components[0],
        LastName:  components[1],
        Age:       36,
    }
    return &users.UserGetReply{User: &u}, nil
}

func registerServices(s *grpc.Server) {
    users.RegisterUsersServer(s, &userService{})
}

func startServer(s *grpc.Server, l net.Listener) error {
    return s.Serve(l)
}

func main() {
    listenAddr := os.Getenv("LISTEN_ADDR")
    if len(listenAddr) == 0 {
        listenAddr = ":50051"
    }

    lis, err := net.Listen("tcp", listenAddr)
    if err != nil {
        log.Fatal(err)
    }
    s := grpc.NewServer()
    registerServices(s)
    log.Fatal(startServer(s, lis))
}
// ./go.mod
replace github.com/handson-go/chap8/user_service/service => ../service

// sh
go mod tidy
robert-min commented 1 year ago

서버 테스트를 위한 Client 생성

import ( "context" "fmt" "log" "os"

users "github.com/handson-go/chap8/user_service/service"
"google.golang.org/grpc"

)

// Set up Grpc Connection with Server func setupGrpcConn(addr string) (*grpc.ClientConn, error) { return grpc.DialContext( context.Background(), // 공백의 Context addr, // 연결할 서버의 주소 grpc.WithInsecure(), // 전송계층보안 사용하지 않고 서버와 통신을 수립하기 위해 사용 grpc.WithBlock(), // 함수가 반환되기 전 연결이 먼저 수립외더 연결 객체가 반환되도록 설정 ) }

func getUserServiceClient(conn *grpc.ClientConn) users.UsersClient { return users.NewUsersClient(conn) }

func getUser( client users.UsersClient, u users.UserGetRequest, ) (users.UserGetReply, error) { return client.GetUser(context.Background(), u) }

func main() { if len(os.Args) != 2 { log.Fatal( "Must specify a gRPC server address", ) } conn, err := setupGrpcConn(os.Args[1]) if err != nil { log.Fatal(err) } defer conn.Close()

c := getUserServiceClient(conn)

result, err := getUser(
    c,
    &users.UserGetRequest{Email: "kim@naver.com"},
)
if err != nil {
    log.Fatal(err)
}

fmt.Fprintf(
    os.Stdout, "User: %s %s \n",
    result.User.FirstName, result.User.LastName,
)

}

robert-min commented 1 year ago

Server test code

package main

import (
    "context"
    "log"
    "net"
    "testing"

    users "github.com/handson-go/chap8/user_service/service"
    "google.golang.org/grpc"
    "google.golang.org/grpc/test/bufconn"
)

// Start test GRPC server with real network listener
func startTestGrpcServer() (*grpc.Server, *bufconn.Listener) {
    l := bufconn.Listen(10)
    s := grpc.NewServer()
    registerServices(s)
    go func() {
        err := startServer(s, l)
        if err != nil {
            log.Fatal(err)
        }
    }()
    return s, l
}

func TestUserService(t *testing.T) {
    s, l := startTestGrpcServer()
    defer s.GracefulStop()

    // 다이얼러 생성
    bufconnDialer := func(ctx context.Context, add string) (net.Conn, error) {
        return l.Dial()
    }

    // 테스트서버 연결을 위한 클라이언트 생성
    client, err := grpc.DialContext(
        context.Background(),
        "",
        grpc.WithInsecure(),
        grpc.WithContextDialer(bufconnDialer), // 네트워크를 인메모리 연결로 구성
    )
    if err != nil {
        t.Fatal(err)
    }

    usersClient := users.NewUsersClient(client)
    resp, err := usersClient.GetUser(
        context.Background(),
        &users.UserGetRequest{
            Email: "kim@naver.com",
            Id:    "foo-bar",
        },
    )
    if err != nil {
        t.Fatal(err)
    }
    if resp.User.FirstName != "kim" {
        t.Errorf(
            "Expected FirstName to be : kim, Got : %s",
            resp.User.FirstName,
        )
    }
}
robert-min commented 1 year ago

Client test code

package main

import (
    "context"
    "log"
    "net"
    "testing"

    users "github.com/handson-go/chap8/user_service/service"
    "google.golang.org/grpc"
    "google.golang.org/grpc/test/bufconn"
)

type dummyUserService struct {
    users.UnimplementedUsersServer
}

func (s *dummyUserService) GetUser(ctx context.Context, in *users.UserGetRequest) (*users.UserGetReply, error) {
    u := users.User{
        Id:        "user-123-a",
        FirstName: "kim",
        LastName:  "min",
        Age:       36,
    }
    return &users.UserGetReply{User: &u}, nil
}

func startTestGrpcServer() (*grpc.Server, *bufconn.Listener) {
    l := bufconn.Listen(10)
    s := grpc.NewServer()
    users.RegisterUsersServer(s, &dummyUserService{})
    go func() {
        err := s.Serve(l)
        if err != nil {
            log.Fatal(err)
        }
    }()
    return s, l
}

func TestGetUser(t *testing.T) {
    s, l := startTestGrpcServer()
    defer s.GracefulStop()

    // 다이얼러 생성
    bufconnDialer := func(ctx context.Context, add string) (net.Conn, error) {
        return l.Dial()
    }

    // 테스트서버 연결을 위한 클라이언트 생성
    conn, err := grpc.DialContext(
        context.Background(),
        "",
        grpc.WithInsecure(),
        grpc.WithContextDialer(bufconnDialer), // 네트워크를 인메모리 연결로 구성
    )
    if err != nil {
        t.Fatal(err)
    }

    c := getUserServiceClient(conn)
    result, err := getUser(
        c,
        &users.UserGetRequest{Email: "kim@min.com"},
    )
    if err != nil {
        t.Fatal(err)
    }

    if result.User.FirstName != "kim" || result.User.LastName != "min" {
        t.Fatalf(
            "Expected: kim doe, min: %s %s",
            result.User.FirstName,
            result.User.LastName,
        )
    }
}
robert-min commented 1 year ago

HTTP vs gRPC

robert-min commented 1 year ago

json 포맷 문자열 프로토콜 버퍼으로 변환

robert-min commented 1 year ago

json 포맷 문자열 프로토콜 버퍼 객체로 언마셜링, 마셜링 코드

// 위 코드 동일

func createUserRequest(jsonQuery string) (*users.UserGetRequest, error) {
    u := users.UserGetRequest{}
    input := []byte(jsonQuery)
    return &u, protojson.Unmarshal(input, &u)
}

func getUserResponseJson(result *users.UserGetReply) ([]byte, error) {
    return protojson.Marshal(result)
}

func main() {
    if len(os.Args) != 3 {
        log.Fatal(
            "Must specify a gRPC server address and search query",
        )
    }
    serverAddr := os.Args[1]

    u, err := createUserRequest(os.Args[2])
    if err != nil {
        log.Fatalf("Bad user input : %v", err)
    }

    conn, err := setupGrpcConn(serverAddr)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    c := getUserServiceClient(conn)

    result, err := getUser(
        c,
        u,
    )
    if err != nil {
        log.Fatal(err)
    }

    data, err := getUserResponseJson(result)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Fprint(
        os.Stdout, string(data),
    )
}
robert-min commented 1 year ago

프로토콜 버퍼 메시지 주의사항