Slide 1

Slide 1 text

~コミュニケーションを支える技術~ Protobufスキーマによる明確なAPI定義 Encraft #2 サーバーとクライアントを結ぶ技術 2023/04/25(Tue) @IT_parsely / 樋口 陽介(hiro)

Slide 2

Slide 2 text

© Know ledge Work Inc. 自己紹介 樋口 陽介(hiro) @IT_parsely ● 株式会社ナレッジワーク ● バックエンドエンジニア ● 今年のテーマの一つは「がんばって登壇してみる」 ○ Go1.20リリースパーティ ○ つきなみGo ○ Go Conferenceは落ちました

Slide 3

Slide 3 text

© Know ledge Work Inc. ● backendとfrontendを型安全に連携できる ● シンプルにスキーマが書ける ● 自動生成の文化があり、周辺ツールがそろっている ● RESTで開発できる(ESPでHTTP↔️gRPC変換) 3 protobuf採用の背景

Slide 4

Slide 4 text

© Know ledge Work Inc. 4 セッション概要 コミュニケーションツールとしてのprotobuf .protoファイルを起点としてどのように開発しているか 話すこと データ通信に関すること 話さないこと protobuf駆動の開発例をもとに嬉しさ・辛さを知ってもらう 今日のゴール

Slide 5

Slide 5 text

© Know ledge Work Inc. ● protobuf駆動の開発紹介 ○ Frontend ○ Backend ● 権限管理の拡張 ● エラーハンドリング ● 今後検討したいこと 5 Agenda

Slide 6

Slide 6 text

© Know ledge Work Inc. protobuf駆動の開発紹介

Slide 7

Slide 7 text

© Know ledge Work Inc. 7 protobuf駆動の開発 全体像 DesignDoc API定義(protobuf) DesignDocでAPI定義を含む 全体設計についてやりとり protobuf から自動生成された インタフェースを基準に実装 開発の最初に .protoを作成 backend 今日のトピック frontend

Slide 8

Slide 8 text

© Know ledge Work Inc. 8 protobuf駆動で開発をすすめるにあたって 自然とprotobuf駆動になるような仕組み作り .protoファイルの利便性・重要性を高める とにかく型安全に 自動生成を最大限に活かす 重要な情報は.protoファイルに明示

Slide 9

Slide 9 text

© Know ledge Work Inc. 型定義の生成はts-protoを採用 EnumはTypeScriptのstring unionで出力できるように実装 をOSS Contribute --ts_proto_opt=enumsAsLiterals=true https://github.com/stephenh/ts-proto/pull/450 @otofu-square 9 protobuf駆動の開発 Frontend message DividerData { enum DividerType { DOUBLE = 0; SINGLE = 1; DASHED = 2; DOTTED = 3; } DividerType type = 1; } export const DividerData_DividerType = { DOUBLE: 'DOUBLE', SINGLE: 'SINGLE', DASHED: 'DASHED', DOTTED: 'DOTTED', UNRECOGNIZED: 'UNRECOGNIZED', } as const; export type DividerData_DividerType = typeof DividerData_DividerType[keyof typeof DividerData_DividerType]; 型の強みを最大限活かす

Slide 10

Slide 10 text

© Know ledge Work Inc. google.api.httpオプションでREST対応 REST通信用のAPI Clientを自作pluginで生成 10 protobuf駆動の開発 Frontend async getUser(req: GetUserRequest): Promise<{ data: GetUserResponse, resHeaders: { [key: string]: string }, }> { const response = await requester.request( "GET", "user", "/v1/users/$id", "application/json", GetUserRequest.toJSON(req) as{ [key: string]: string | string[] }, "", ) if (response.ok) { return { data: GetUserResponse.fromJSON(awaitresponse.json()), resHeaders: headersToMap(response.headers), } } RESTの通信部分は自動生成 service UserService { rpc GetUser(GetUserRequest) returns (GetUserResponse) { option (google.api.http) = {get: "/v1/users/{id}"}; } }

Slide 11

Slide 11 text

© Know ledge Work Inc. 基本的なコード群はprotocで生成 ServiceのImplementsを自作pluginで生成 Service共通の処理 認証処理(詳細後述) 11 protobuf駆動の開発 Backend type UserServiceServer interface { GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error) } func (s *userServiceServer) GetUser( ctx context.Context, req *appservice.GetUserRequest, ) (*appservice.GetUserResponse, error) { userID, err := idtype.UserIDFromString(req.Id) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid user id (%s)", req.Id) } a, err := s.authorizer.Authorize( ctx, userID, roleactionmodel.UsersGet.ID, ) if err != nil { return nil, err } return s.getUser(ctx, a, req) } Serverの共通処理を自動生成

Slide 12

Slide 12 text

© Know ledge Work Inc. protoファイルの変更を検知 CIで自動生成を実行してPR作成 12 protobuf駆動の開発 CI コード生成も自動化

Slide 13

Slide 13 text

© Know ledge Work Inc. 13 小括(protobuf駆動の開発紹介) 開発者間のAPIに関する認識ずれ API変更時のフィールド修正漏れなど、 単純な実装ミスに対するレビューのやり とり 実現する状態 明確なAPI定義による共通認識 単純な実装ミスは型の力をかりてコンパイ ル時に検出でレビューコスト低減 よくある課題

Slide 14

Slide 14 text

© Know ledge Work Inc. 権限管理の拡張

Slide 15

Slide 15 text

© Know ledge Work Inc. 認証処理を自動生成することで実装ミスによるセキュリティインシデントを防ぐ 15 権限仕様をprotoで明示する嬉しさ Frontend、Backend間でAPI実行権限に共通認識を持ち、不具合を減らす セキュアな実装 API実行権限の共通認識

Slide 16

Slide 16 text

© Know ledge Work Inc. 16 具体的な方法 service UserService { rpc GetUser(GetUserRequest) returns (GetUserResponse) { option (google.api.http) = { get: "/v1/users/{id}" }; option (kw.auth_options) = { auth_type: AUTH_TYPE_USER action_name: "users.get" }; } } protobufのメソッド定義に認証用のオプション を追加 リソースとアクションを明示 extensionsによる拡張

Slide 17

Slide 17 text

© Know ledge Work Inc. 17 Backendでの活用 func (s *userServiceServer) GetUser( ctx context.Context, req *appservice.GetUserRequest, ) (*appservice.GetUserResponse, error) { userID, err := idtype.UserIDFromString(req.Id) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid user id (%s)", req.Id) } a, err := s.authorizer.AuthorizeUserRole( ctx, userID, roleactionmodel.UsersGet.ID, ) if err != nil { return nil, err } return s.getUser(ctx, a, req) } 認証処理を行うpublicメソッドを自動生成 開発者は認証以降の処理を実装する 認証処理の自動生成

Slide 18

Slide 18 text

© Know ledge Work Inc. 18 Frontendでの活用 権限に合わせたUIの出し分けに活用 ユーザーに権限のあるaction一覧をserverから取得 protobufの情報と合わせてAPIの実行権限を確認 API実行権限とUI

Slide 19

Slide 19 text

© Know ledge Work Inc. 19 小括(権限管理の拡張) 詳しい人に聞かないとAPIの実行権限が わからない 実現する状態 protoファイルをみるだけで実行権限が わかる protoに明示された権限情報を参照して UIをコントロールできる よくある課題

Slide 20

Slide 20 text

© Know ledge Work Inc. エラーハンドリング

Slide 21

Slide 21 text

© Know ledge Work Inc. 特定のエラーが起きた時にユーザーに復旧方法をフィードバックしたい エラー情報のやりとりも型安全に行いたい 21 エラーハンドリングの背景 エラーのフィードバック gRPCのカスタムエラーの辛み type Status struct { Code int32 `json:"code,omitempty"` Message string `json:"message,omitempty"` Details []*anypb.Any `json:"details,omitempty"` } Detailsに任意のmessage型を詰め込む仕様 型安全にエラーを連携するのが難しい

Slide 22

Slide 22 text

© Know ledge Work Inc. 22 カスタムエラーのハンドリング エラー用の型を定義して、型安全に通信できる Detailsの中身をエラー用のmessage型にパースするコード を自作pluginで生成 import type { AppServiceError } from './appservice_error_type' import { ServiceLinkageError, } from './appservice_error'; export const parseAppServiceErrorBody = (body: { details?: any[] }): AppServiceError[] => { const { details } = body if (!details || !Array.isArray(details)) { return [] } return details.map(parseAppServiceError).filter((error): error is AppServiceError => !!error) } 型安全なエラーのやり取り message ServiceLinkageError { ServiceLinkageErrorType type = 1; string service_name = 2; google.protobuf.StringValue cause_detail = 3; }

Slide 23

Slide 23 text

© Know ledge Work Inc. 23 小括(エラーハンドリング) 実現する状態 どんなエラーが返ってくるかわからない エラーハンドリングが煩雑 エラーの型も明確に定義されている エラーレスポンスも正常系と同じように パースできる よくある課題

Slide 24

Slide 24 text

© Know ledge Work Inc. 24 エラーハンドリングでできていないこと ● APIごとにどのようなエラーを返すのかprotoに明示 ● Serverからエラーを返す時に、Detailsに含めるmessage型を制限

Slide 25

Slide 25 text

© Know ledge Work Inc. 今後検討したいこと

Slide 26

Slide 26 text

© Know ledge Work Inc. 26 今後検討したいこと ● protoc-gen-validateの導入 ● より型安全なエラーハンドリング ● gRPC-Web

Slide 27

Slide 27 text

No content