Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

the case of microservice design using go

Imamura
September 23, 2020

the case of microservice design using go

Imamura

September 23, 2020
Tweet

Other Decks in Programming

Transcript

  1. 2
 Shoichi Imamura 今村 翔一
 freee株式会社
 2 • 金融事業部のリードエンジニア
 


    • 2017年4月にfreeeに新卒で入社(エンジニア1期目)
 
 • 入社以来アプリケーションエンジニアとして、フロントエンド、バックエンドの 開発に携わる
 
 • 2018年頃に会計freeeにある機能を別アプリケーションとして切り出す際に 設計を見直しながらGoで書き直した経験からGoが好きに
 
 • 2019年頃に金融事業部に配属され、Goでマイクロサービスを開発する
 
 • 現在は福利厚生freeeというサービス(これはRailsアプリケーション)の開 発に従事している

  2. 8 About Products
 ❂ 納税する
 ↗ 育てる
 ↻ 運営する 


    ✩ はじめる
 会社設立 freee
 開業 freee
 クラウド会計ソフト freee 
 人事労務 freee
 (マイナンバー管理 freee 含む) 
 クラウド申告 freee
 
 * 2017年8月より、クラウド給与計算ソフト freeeは、機能を強化し、「人事労務 freee」というサービス名に変更しました。
 スモールビジネスの創業からIPOまで一気通貫でサポートする7つのプロダクト
 2013.3リリース
 2014.5リリース
 2015.6リリース
 中小企業の経理業務を効率 化。帳簿や決算書作成、請求 業務に対応。
 給与計算や年末調整、入社手 続きから勤怠管理まで労務労 務管理を大幅に効率化。
 会社設立に必要な書類を5分 で作成できる無料サービス。
 2016.10リリース 2015.9リリース 2017.1リリース 低コストでマイナンバーの収集、管 理、破棄までクラウド上で完結。
 個人事業の開業手続きが無料、簡 単、最速で完了する。
 税務申告書作成業務を効率化。 法人税・消費税・法定調書・申請 届出や電子申告にも対応。
 Webで申し込みでき、最短4営業日で発 行。創業時でも本人確認書類だけで審査 可能。

  3. 12
 • デプロイ時間がかかる
 ◦ 一 回のリリースあたりに含まれるコミット量が 多い
 ◦ それらの動作が全て保証されるまで待つ必要 がある


    
 • 機能の責任の境界が不明瞭
 ◦ 本来意図しない箇所から参照
 ◦ 障害の影響調査が困難
 業務ドメインが増え続け、機能同士の癒着や関わる開発者の増加
 freeeでのマイクロサービス化の流れ
 • 財務会計 • 確定申告 • 請求書 • 口座連携 • ワークフロー • etc. 会計
 人事労務
 • 管理画面 • Public API • etc... ビジネス
 ロジック
 共通基盤
 • 認証 • 課金 • etc...
  4. 13
 サービス構成
 社外のシステム 会計freee 新サービス 会計データ 債権データ • 社外システムとのステータス連携用のAPIと社内のシ ステムにオファー可能な債権を返すAPIを提供する


    
 • 社外のシステムからアクセスされるデータを別のDBで 管理したい
 
 • 社外のシステムから債権の審査状況などの同期が行 われるので、既存プロダクトの障害などに巻き込まれ たくない
 
 • さらに連携していく構想があったので個別でデプロイ し、技術選定の自由度を確保したい
 マイクロサービスとして独立させることに

  5. 14
 利用した主な技術・ツール
 • アプリケーション
 ◦ go
 ◦ gRPC
 ◦ grpc-gateway


    ◦ wire(この記事で詳しく説明)
 • インフラ
 ◦ terraform
 ◦ helm/helmfile
 ◦ kubernetes
 アプリケーションエンジニアがコード化されたインフラの設定も行う

  6. 15
 なぜ今回のサービスでGoを利用したのか?
 • 静的型付け
 ◦ 事前に型の整合性が検証されているのでプログラムを堅牢にできる
 ◦ コードが表現する情報量が増え、何が入力されて出力されるのか理解しやすい
 
 •

    学習のしやすさ
 ◦ シンプルで理解しやすい言語設計
 ◦ gofmtなどのツールで統一的なコードフォーマットが実現
 ◦ 社内のマイクロサービスで利用されており知見を得やすい
 
 • 社内のマイクロサービス向けのGoで書かれた共通パッケージ
 ◦ いくつかのマイクロサービスで微妙に構成が異なった実装への対処
 ◦ 車輪の再発明を防ぎ、ドメインロジックに集中できる
 ◦ エラー通知、ロギング、SQLの実行 etc.
 他にもGoの魅力はあるが、今回の開発で得られた大きなメリットは以下

  7. 16
 社内の共通パッケージの存在
 gRPCサービスの呼び出し後のロギング
 package interceptor ... // ActivityLogUnaryServerInterceptor returns a

    new unary server interceptor that send activity log by logger. func ActivityLogUnaryServerInterceptor(actLogger *logger.Logger) grpc.UnaryServerInterceptor { return func(ctx ..., req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp ..., err error) { ... defer func() { ... printActivityLog(ctx, actLogger, startTime.Format(time.RFC3339), processTime, err, resp, req) }() return handler(ctx, req) } }
  8. 17
 社内の共通パッケージの存在
 // Mapper is a generalized interface to map

    between a Go struct and a SQL line. type Mapper interface { // Table returns metadata of a table that the struct corresponds to. Table() *Table // Values returns column values except for the PK. The order is as same as the Column() result. Values() []interface{} ... } // InsertStruct inserts a new entity. func InsertStruct(ctx context.Context, db Execer, mapper orm.Mapper) error { ... res, err := db.ExecContext(ctx, buf.String(), vals...) ... }
 構造体からのSQLの組み立てと実行

  9. 19
 • システム内部ではgRPCを利用
 ◦ Protocol Buffers のようなインターフェース定義語(IDL)で通信内容を定義できる
 ◦ HTTP/2で通信することができ、リクエストとレスポンスを一つのTCPコネクション上で 多重化できる


    ◦ 複数言語に対応している
 
 • システム外部用にはRESTfulでJSONをペイロードとするAPIを利用
 ◦ 連携先が使っている言語やフレームワークに依存したくない
 ▪ より一般的
 ▪ gRPCのクライアント側のコードを配布するのは現実的ではない
 gRPCとJSON APIの共存

  10. 20
 • システム内部(gRPC)からもシステム外部(JSON API) からも同じ処理をする場合、それを呼び出す別々のAPI を用意をする必要
 ◦ コントローラー層の実装量が増える
 
 •

    開発者はAPIの実装方法を切り替える必要がある
 ◦ JSON APIはURIとJSONの構造を決め、それに応じ て実装
 ◦ gRPCの場合は、Protocol Buffersを定義し、インター フェースを生成し、それに応じて実装
 それぞれgRPCとJSON API用のコントローラーを用意する必要がある
 単純に実装した場合の課題
 port A port B gRPC用のコントローラー JSON API用のコントローラー 共通の処理 Proto Request
 JSON Request

  11. 21
 単純に実装した場合の課題
 import ( ... ) type Message struct {

    ... } func echo(w http.ResponseWriter, r *http.Request) { var m Message err := json.NewDecoder(r.Body).Decode(&m) ... fmt.Fprintf(w, “%+v”, m) } func main() { mux := http.NewServeMux() // http.ServMux構造体にパスとhandlerを登録する mux.HandleFunc("/echo", echo) ... }
 JSON APIの場合は、JSONのデシリアライズをするHTTPハンドラーの実装が必要

  12. 22
 単純に実装した場合の課題
 gRPCの場合は、Protocol Buffersからインターフェースの生成と実装が必要
 // proto/echo.proto message Message { ...

    } service EchoService { rpc Echo(Message) return (Message) {} } // proto/echo.pb.go package proto type EchoService interface { Echo(context.Context, *Message) (*Message, error) } // rpc/echo.go type echoServer struct { ... } // Protocol Buffersが生成したgRPC serviceのインターフェースの実装 func (s *echoServer) Echo(ctx context.Context, msg *proto.Message) (*propto.Message, error) { return msg, nil }
  13. 23
 grpc-gatewayの導入
 • JSON API を受け取ってgRPCメ ソッドの呼び出しをするリバースプ ロキシを生成する
 
 •

    具体的には、Protocol Buffersに パスやパラメータなどの注釈を追 加すると、対応するgRPC呼び出し をするHTTPハンドラのコードを生 成する
 画像は公式ドキュメントより https://grpc-ecosystem.github.io/grpc-gateway/ 

  14. 24
 grpc-gatewayの導入
 syntax = 'proto3'; package proto; option go_package =

    "proto"; import "google/api/annotations.proto"; service EchoService { rpc Echo(...) returns (...) { // Protocol Buffersにannotationを追加 option (google.api.http) = { get: "/echo" }; } rpc UpdateMessage(...) returns (...) { option (google.api.http) = { put: "/echo/{id}" // query string でのパラメータ body: "*" // request body でのパラメータ }; } }
  15. 25
 grpc-gatewayの導入
 // 対応するパスとHTTPハンドラが生成される package proto func RegisterEchoServiceHandlerServer(ctx context.Context, mux

    *runtime.ServeMux, server EchoServiceServer) error { mux.Handle("GET", pattern_EchoService_Echo_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ... // 対応するgRPC Serviceのメソッドが呼び出される resp, md, err := local_request_EchoService_Echo_1(rctx, inboundMarshaler, server, req, pathParams) ... // gRPC Serviceのメソッド呼び出しの結果をリクエストとして返す forward_EchoService_Echo_1(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) }
  16. 26
 grpc-gatewayの導入
 webサーバーを起動する
 func Run(...) error { // grpc-gatewayのruntime.ServeMux構造体を生成する mux

    := runtime.NewServeMux(opts...) // 先ほどのパスとHTTPハンドラを登録する関数にgRPC Serviceの実装を渡す proto.RegisterEchoServiceHandlerServer(ctx, mux, newEchoService()) // runtime.ServeMux構造体(http.Handlerインターフェースを実装)をhttp.Server構造体に渡す s := &http.Server{ Addr: addr, Handler: mux, } // サーバーの起動 return s.ListenAndServe() }
  17. 29
 まとめ
 • freeeではGoを使ってマイクロサービスを開発
 ◦ 開発者と機能の増加に伴いマイクロサービス化
 ◦ 車輪の再発明を防ぐために共通パッケージの整備
 
 •

    金融サービスのGoを使った開発
 ◦ gRPCとJSON APIの共存を目指した
 
 • 課題
 ◦ 画面の表示や買取可能性の高い債権をオファーするロジックは会計側のデータを用 いるので完全にマイクロサービス化できていない