Zenn
👏

作って学ぶ API GatewayとBFF:gRPCでPing Pong

2024/12/24に公開

インターンでマイクロサービス化されたシステムに触れる機会があり、コードを読みつつ理解を深めようとしましたが、「実際に作ってみる方が早い!」と考え、簡単なPing-PongアプリケーションをAPI GatewayとBFFの構成で作成しました。この記事では、その過程と実装内容を紹介します。

※勉強中の備忘録的な内容も含まれるため、誤りや改善点があればご指摘いただけると幸いです。

使用環境

  • 言語: Go
  • 通信方式: http, gRPC
  • OS: Mac

API Gateway と BFF とは

API Gateway

クライアントからのリクエストを適切なマイクロサービスにルーティングする統一エントリーポイント。
それぞれのマイクロサービスが同じ建物に入っている別の会社だとしたら、API Gatewayはそのオフィスビルのレセプション的な感じで案内してくれる、みたいな感じと理解。

BFF(Backend for Frontend)

API Gateway の一種。リクエストを受け取り、フロントエンドの要件に合わせてマイクロサービスから得たデータを統合・変換する。いろんな人から箇条書きで返ってきた情報を、上司向けには敬語で、同期向けには砕けた言葉に変換して返してくれるみたいな感じと理解。

実装方針

目標

「Ping」を送ると「Pong!」が返ってくるシンプルなやり取りを通して、API GatewayとBFFの基本的な役割を学びます。

システム構成

  • API Gateway: クライアントのリクエストを受け取り、BFFにルーティング (HTTP)。
  • BFF: マイクロサービスにリクエストを送信し、結果を統合・加工 (gRPC)。
  • マイクロサービス:
    • Pong Service: BFFからのリクエストに対して「Pong」を返す。
    • Exclamation Service: 「!」を返す。

ディレクトリ構成

api-gateway/
    main.go
bff-server/
    main.go
bff/
    bff.proto
    (bff.pb.go)
    (bff_grpc.pb.go)
pong-service/
    main.go
pong/
    pong.proto
    (pong.pb.go)
    (pong_grpc.pb.go)
exclamation-service/
    main.go
exclamation/
    exclamation.proto
    (exclamation.pb.go)
    (exclamation_grpc.pb.go)
go.mod
go.sum

具体的な実装

環境構築

  • Go
  • protoc(Protocol Buffersのコンパイラ)のインストール
    macOSの場合:
    brew install protobuf
    
  • Go用のgRPCコード生成プラグインのインストール
    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
    go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
    go mod tidy
    
  • P$GOPATH/bin をPATHに追加
    PATH=$PATH:$(go env GOPATH)/bin
    
  • コードを反映
    source ~/.zshrc
    
  • インストール確認
    which protoc-gen-go
    which protoc-gen-go-grpc
    

コード例

API Gateway: クライアントリクエストを受け取り、BFFに転送

api-gateway/main.go
package main

import (
	"context"
	"encoding/json"
	"io"
	"log"
	"net/http"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	bffpb "ping-pong/bff"
)

type PingRequest struct {
	Message string `json:"message"`
}

type PingResponse struct {
	Response string `json:"response"`
}

func main() {
	http.HandleFunc("/ping", handlePing) // HTTPエンドポイントを設定

	log.Println("API Gateway running on port 8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatalf("Failed to start server: %v", err)
	}
}

func handlePing(w http.ResponseWriter, r *http.Request) {
	// HTTPリクエストのパース
	if r.Method != http.MethodPost {
		http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Failed to read request body", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()

	var req PingRequest
	if err := json.Unmarshal(body, &req); err != nil {
		http.Error(w, "Invalid JSON format", http.StatusBadRequest)
		return
	}

	// gRPCクライアントでBFFに転送
	response, err := forwardToBFF(req.Message)
	if err != nil {
		http.Error(w, "Failed to forward request to BFF", http.StatusInternalServerError)
		return
	}

	// HTTPレスポンスを返す
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(PingResponse{Response: response})
}

func forwardToBFF(message string) (string, error) {
	// gRPC接続を確立
	conn, err := grpc.NewClient("dns:///localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Printf("Failed to connect to BFF: %v", err)
		return "", err
	}
	defer conn.Close()

	client := bffpb.NewBFFServiceClient(conn)

	// gRPCリクエストを作成して送信
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	req := &bffpb.BFFRequest{Message: message}
	res, err := client.ForwardPing(ctx, req)
	if err != nil {
		log.Printf("Failed to call BFF: %v", err)
		return "", err
	}

	return res.Response, nil
}

BFF: BFFの役割を担い、PongサービスとExclamationサービスを呼び出してレスポンスを統合

bff-server/main.go
package main

import (
	"context"
	"log"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	bffpb "ping-pong/bff"
	exclamationpb "ping-pong/exclamation"
	pongpb "ping-pong/pong"
)

type BFFServer struct {
	bffpb.UnimplementedBFFServiceServer
}

func (s *BFFServer) ForwardPing(ctx context.Context, req *bffpb.BFFRequest) (*bffpb.BFFResponse, error) {
	// Pongサービスにリクエスト
	pongResponse, err := callPongService(req.Message)
	if err != nil {
		return nil, err
	}

	// Exclamationサービスにリクエスト
	exclamationResponse, err := callExclamationService(req.Message)
	if err != nil {
		return nil, err
	}

	// レスポンスを統合
	combinedResponse := pongResponse + exclamationResponse
	return &bffpb.BFFResponse{Response: combinedResponse}, nil
}

// callPongServiceはPongサービスにgRPCリクエストを送信します
func callPongService(message string) (string, error) {
	// gRPC接続を確立
	conn, err := grpc.Dial("localhost:50052", grpc.WithTransportCredentials(insecure.NewCredentials())) // Pongサービスのアドレス
	if err != nil {
		log.Printf("Failed to connect to Pong Service: %v", err)
		return "", err
	}
	defer conn.Close()

	client := pongpb.NewPongServiceClient(conn)
	req := &pongpb.PongRequest{Message: message}
	res, err := client.GetPong(context.Background(), req)
	if err != nil {
		log.Printf("Error calling Pong Service: %v", err)
		return "", err
	}

	return res.Response, nil
}

// callExclamationServiceはExclamationサービスにgRPCリクエストを送信します
func callExclamationService(message string) (string, error) {
	// gRPC接続を確立
	conn, err := grpc.Dial("localhost:50053", grpc.WithTransportCredentials(insecure.NewCredentials())) // Exclamationサービスのアドレス
	if err != nil {
		log.Printf("Failed to connect to Exclamation Service: %v", err)
		return "", err
	}
	defer conn.Close()

	client := exclamationpb.NewExclamationServiceClient(conn)
	req := &exclamationpb.ExclamationRequest{Message: message}
	res, err := client.GetExclamation(context.Background(), req)
	if err != nil {
		log.Printf("Error calling Exclamation Service: %v", err)
		return "", err
	}

	return res.Response, nil
}

func main() {
	// gRPCサーバーを設定
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}

	s := grpc.NewServer()
	bffpb.RegisterBFFServiceServer(s, &BFFServer{})

	log.Println("BFF running on port 50051")
	if err := s.Serve(lis); err != nil {
		log.Fatalf("Failed to serve: %v", err)
	}
}
bff/bff.proto
syntax = "proto3";

package bff;

option go_package = "ping-pong/bff;bff";

service BFFService {
  rpc ForwardPing (BFFRequest) returns (BFFResponse);
}

message BFFRequest {
  string message = 1;
}

message BFFResponse {
  string response = 1;
}

Pong Service: 「Pong」と応答するサービス

pong-service/main.go
package main

import (
	"context"
	"log"
	"net"

	pongpb "ping-pong/pong"

	"google.golang.org/grpc"
)

type server struct {
	pongpb.UnimplementedPongServiceServer
}

func (s *server) GetPong(ctx context.Context, req *pongpb.PongRequest) (*pongpb.PongResponse, error) {
	return &pongpb.PongResponse{Response: "Pong"}, nil
}

func main() {
	lis, err := net.Listen("tcp", ":50052")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}

	s := grpc.NewServer()
	pongpb.RegisterPongServiceServer(s, &server{})

	log.Println("Pong Service running on port 50052")
	if err := s.Serve(lis); err != nil {
		log.Fatalf("Failed to serve: %v", err)
	}
}
pong/pong.proto
syntax = "proto3";

package pong;

option go_package = "ping-pong/pong-service;pong";

service PongService {
  rpc GetPong (PongRequest) returns (PongResponse);
}

message PongRequest {
  string message = 1;
}

message PongResponse {
  string response = 1;
}

Exclamation Service: 「!」を応答するサービス

exclamation-service/main.go
package main

import (
	"context"
	"log"
	"net"

	exclamationpb "ping-pong/exclamation"

	"google.golang.org/grpc"
)

type server struct {
	exclamationpb.UnimplementedExclamationServiceServer
}

func (s *server) GetExclamation(ctx context.Context, req *exclamationpb.ExclamationRequest) (*exclamationpb.ExclamationResponse, error) {
	return &exclamationpb.ExclamationResponse{Response: "!"}, nil
}

func main() {
	lis, err := net.Listen("tcp", ":50053")
	if err != nil {
		log.Fatalf("Failed to listen: %v", err)
	}

	s := grpc.NewServer()
	exclamationpb.RegisterExclamationServiceServer(s, &server{})

	log.Println("Exclamation Service running on port 50053")
	if err := s.Serve(lis); err != nil {
		log.Fatalf("Failed to serve: %v", err)
	}
}
exclamation/exclamation.proto
syntax = "proto3";

package exclamation;

option go_package = "ping-pong/exclamation-service;exclamation";

service ExclamationService {
  rpc GetExclamation (ExclamationRequest) returns (ExclamationResponse);
}

message ExclamationRequest {
  string message = 1;
}

message ExclamationResponse {
  string response = 1;
}

protocコマンドにより生成される部分

  • 生成方法
    protoc --proto_path={プロトファイルがあるディレクトリのパス} --go_out={Protobufメッセージ関連のコードを生成するディレクトリのパス} --go-grpc_out={gRPCサーバー/クライアント関連のコードを生成するディレクトリのパス} {protoファイルのパス}
    
  • できるコード例
pong/pong.pb.go
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// 	protoc-gen-go v1.36.0
// 	protoc        v5.29.1
// source: ping-pong/pong/pong.proto

package pong

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 PongRequest struct {
	state         protoimpl.MessageState `protogen:"open.v1"`
	Message       string                 `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
	unknownFields protoimpl.UnknownFields
	sizeCache     protoimpl.SizeCache
}

func (x *PongRequest) Reset() {
	*x = PongRequest{}
	mi := &file_ping_pong_pong_pong_proto_msgTypes[0]
	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
	ms.StoreMessageInfo(mi)
}

func (x *PongRequest) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*PongRequest) ProtoMessage() {}

func (x *PongRequest) ProtoReflect() protoreflect.Message {
	mi := &file_ping_pong_pong_pong_proto_msgTypes[0]
	if x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use PongRequest.ProtoReflect.Descriptor instead.
func (*PongRequest) Descriptor() ([]byte, []int) {
	return file_ping_pong_pong_pong_proto_rawDescGZIP(), []int{0}
}

func (x *PongRequest) GetMessage() string {
	if x != nil {
		return x.Message
	}
	return ""
}

type PongResponse struct {
	state         protoimpl.MessageState `protogen:"open.v1"`
	Response      string                 `protobuf:"bytes,1,opt,name=response,proto3" json:"response,omitempty"`
	unknownFields protoimpl.UnknownFields
	sizeCache     protoimpl.SizeCache
}

func (x *PongResponse) Reset() {
	*x = PongResponse{}
	mi := &file_ping_pong_pong_pong_proto_msgTypes[1]
	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
	ms.StoreMessageInfo(mi)
}

func (x *PongResponse) String() string {
	return protoimpl.X.MessageStringOf(x)
}

func (*PongResponse) ProtoMessage() {}

func (x *PongResponse) ProtoReflect() protoreflect.Message {
	mi := &file_ping_pong_pong_pong_proto_msgTypes[1]
	if x != nil {
		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
		if ms.LoadMessageInfo() == nil {
			ms.StoreMessageInfo(mi)
		}
		return ms
	}
	return mi.MessageOf(x)
}

// Deprecated: Use PongResponse.ProtoReflect.Descriptor instead.
func (*PongResponse) Descriptor() ([]byte, []int) {
	return file_ping_pong_pong_pong_proto_rawDescGZIP(), []int{1}
}

func (x *PongResponse) GetResponse() string {
	if x != nil {
		return x.Response
	}
	return ""
}

var File_ping_pong_pong_pong_proto protoreflect.FileDescriptor

var file_ping_pong_pong_pong_proto_rawDesc = []byte{
	0x0a, 0x19, 0x70, 0x69, 0x6e, 0x67, 0x2d, 0x70, 0x6f, 0x6e, 0x67, 0x2f, 0x70, 0x6f, 0x6e, 0x67,
	0x2f, 0x70, 0x6f, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x70, 0x6f, 0x6e,
	0x67, 0x22, 0x27, 0x0a, 0x0b, 0x50, 0x6f, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
	0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
	0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2a, 0x0a, 0x0c, 0x50, 0x6f,
	0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65,
	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65,
	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x3f, 0x0a, 0x0b, 0x50, 0x6f, 0x6e, 0x67, 0x53, 0x65,
	0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x30, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x6e, 0x67,
	0x12, 0x11, 0x2e, 0x70, 0x6f, 0x6e, 0x67, 0x2e, 0x50, 0x6f, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75,
	0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x70, 0x6f, 0x6e, 0x67, 0x2e, 0x50, 0x6f, 0x6e, 0x67, 0x52,
	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x1d, 0x5a, 0x1b, 0x70, 0x69, 0x6e, 0x67, 0x2d,
	0x70, 0x6f, 0x6e, 0x67, 0x2f, 0x70, 0x6f, 0x6e, 0x67, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
	0x65, 0x3b, 0x70, 0x6f, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}

var (
	file_ping_pong_pong_pong_proto_rawDescOnce sync.Once
	file_ping_pong_pong_pong_proto_rawDescData = file_ping_pong_pong_pong_proto_rawDesc
)

func file_ping_pong_pong_pong_proto_rawDescGZIP() []byte {
	file_ping_pong_pong_pong_proto_rawDescOnce.Do(func() {
		file_ping_pong_pong_pong_proto_rawDescData = protoimpl.X.CompressGZIP(file_ping_pong_pong_pong_proto_rawDescData)
	})
	return file_ping_pong_pong_pong_proto_rawDescData
}

var file_ping_pong_pong_pong_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_ping_pong_pong_pong_proto_goTypes = []any{
	(*PongRequest)(nil),  // 0: pong.PongRequest
	(*PongResponse)(nil), // 1: pong.PongResponse
}
var file_ping_pong_pong_pong_proto_depIdxs = []int32{
	0, // 0: pong.PongService.GetPong:input_type -> pong.PongRequest
	1, // 1: pong.PongService.GetPong:output_type -> pong.PongResponse
	1, // [1:2] is the sub-list for method output_type
	0, // [0:1] is the sub-list for method input_type
	0, // [0:0] is the sub-list for extension type_name
	0, // [0:0] is the sub-list for extension extendee
	0, // [0:0] is the sub-list for field type_name
}

func init() { file_ping_pong_pong_pong_proto_init() }
func file_ping_pong_pong_pong_proto_init() {
	if File_ping_pong_pong_pong_proto != nil {
		return
	}
	type x struct{}
	out := protoimpl.TypeBuilder{
		File: protoimpl.DescBuilder{
			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
			RawDescriptor: file_ping_pong_pong_pong_proto_rawDesc,
			NumEnums:      0,
			NumMessages:   2,
			NumExtensions: 0,
			NumServices:   1,
		},
		GoTypes:           file_ping_pong_pong_pong_proto_goTypes,
		DependencyIndexes: file_ping_pong_pong_pong_proto_depIdxs,
		MessageInfos:      file_ping_pong_pong_pong_proto_msgTypes,
	}.Build()
	File_ping_pong_pong_pong_proto = out.File
	file_ping_pong_pong_pong_proto_rawDesc = nil
	file_ping_pong_pong_pong_proto_goTypes = nil
	file_ping_pong_pong_pong_proto_depIdxs = nil
}

``` 
pong/pong_grpc.pb.go
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc             v5.29.1
// source: ping-pong/pong/pong.proto

package pong

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.64.0 or later.
const _ = grpc.SupportPackageIsVersion9

const (
	PongService_GetPong_FullMethodName = "/pong.PongService/GetPong"
)

// PongServiceClient is the client API for PongService 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 PongServiceClient interface {
	GetPong(ctx context.Context, in *PongRequest, opts ...grpc.CallOption) (*PongResponse, error)
}

type pongServiceClient struct {
	cc grpc.ClientConnInterface
}

func NewPongServiceClient(cc grpc.ClientConnInterface) PongServiceClient {
	return &pongServiceClient{cc}
}

func (c *pongServiceClient) GetPong(ctx context.Context, in *PongRequest, opts ...grpc.CallOption) (*PongResponse, error) {
	cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
	out := new(PongResponse)
	err := c.cc.Invoke(ctx, PongService_GetPong_FullMethodName, in, out, cOpts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

// PongServiceServer is the server API for PongService service.
// All implementations must embed UnimplementedPongServiceServer
// for forward compatibility.
type PongServiceServer interface {
	GetPong(context.Context, *PongRequest) (*PongResponse, error)
	mustEmbedUnimplementedPongServiceServer()
}

// UnimplementedPongServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedPongServiceServer struct{}

func (UnimplementedPongServiceServer) GetPong(context.Context, *PongRequest) (*PongResponse, error) {
	return nil, status.Errorf(codes.Unimplemented, "method GetPong not implemented")
}
func (UnimplementedPongServiceServer) mustEmbedUnimplementedPongServiceServer() {}
func (UnimplementedPongServiceServer) testEmbeddedByValue()                     {}

// UnsafePongServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to PongServiceServer will
// result in compilation errors.
type UnsafePongServiceServer interface {
	mustEmbedUnimplementedPongServiceServer()
}

func RegisterPongServiceServer(s grpc.ServiceRegistrar, srv PongServiceServer) {
	// If the following call pancis, it indicates UnimplementedPongServiceServer was
	// embedded by pointer and is nil.  This will cause panics if an
	// unimplemented method is ever invoked, so we test this at initialization
	// time to prevent it from happening at runtime later due to I/O.
	if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
		t.testEmbeddedByValue()
	}
	s.RegisterService(&PongService_ServiceDesc, srv)
}

func _PongService_GetPong_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(PongRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(PongServiceServer).GetPong(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: PongService_GetPong_FullMethodName,
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(PongServiceServer).GetPong(ctx, req.(*PongRequest))
	}
	return interceptor(ctx, in, info, handler)
}

// PongService_ServiceDesc is the grpc.ServiceDesc for PongService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var PongService_ServiceDesc = grpc.ServiceDesc{
	ServiceName: "pong.PongService",
	HandlerType: (*PongServiceServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "GetPong",
			Handler:    _PongService_GetPong_Handler,
		},
	},
	Streams:  []grpc.StreamDesc{},
	Metadata: "ping-pong/pong/pong.proto",
}

``` 

動作確認

サービスの起動

順番に以下を実行:

# Exclamation Service
go run exclamation-service/main.go

# Pong Service
go run pong-service/main.go

# BFF Server
go run bff-server/main.go

# API Gateway
go run api-gateway/main.go

リクエストの送信

API Gatewayにリクエストを送信し、期待するレスポンスを確認します。

curl -X POST -H "Content-Type: application/json" -d '{"message": "Ping"}' http://localhost:8080/ping

レスポンス

{"response": "Pong!"}

名前空間の仕組み

go.mod の module

プロジェクトのルートパスを定義します。例えば:
module ping-pong
他のコードでは、ping-pong/サブパッケージ名 の形式で参照します。

.proto の package

.proto ファイルの名前空間を定義します。例えば:
package bff;
これにより、生成される .pb.go ファイルで bff 名前空間を使用できます。

.proto の option go_package

.pb.go ファイルのインポートパスとパッケージ名を指定します。例えば:
option go_package = "ping-pong/bff;bff";

  • ping-pong/bff: 他のGoコードから参照するときのインポートパス。
  • bff: .pb.go 内でのパッケージ名。

具体例

他のコードから bff を参照する場合:

import bffpb "ping-pong/bff"

func main() {
    req := &bffpb.BFFRequest{Message: "Ping"}
}
  • import bffpb: ping-pong/bff パッケージをインポート。
  • bffpb.BFFRequest: .pb.go ファイルで定義された型を利用

感想と次のステップ

今回のプロジェクトを通じて、以下の点を学びました:

  • API GatewayとBFFの役割:それぞれの役割とシステム間の連携。
  • gRPC通信:Protocol Buffersを用いた効率的なデータやり取りの実践。
  • goの名前空間

次のステップとして、以下のような課題に取り組みたいと考えています:

  • セキュリティ対策(認証・認可)の実装。
  • 複雑なデータフローを伴うユースケースの実装。
  • サービス間で発生するエラー処理の強化とログの整備。

Discussion

ログインするとコメントできます