Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GoでAPIサーバをはやくつくる

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

 GoでAPIサーバをはやくつくる

Avatar for Jumpei Takiyasu

Jumpei Takiyasu

May 18, 2019
Tweet

More Decks by Jumpei Takiyasu

Other Decks in Technology

Transcript

  1. name <- "Jumpei Takiyasu" twitter <- "juntaki" company <- "M3,

    Inc." title <- "Software Engineer/TL/PM" website <- "https://juntaki.com" Me
  2. impl:interfaceをみたすための関数群を自動生成 https://github.com/josharian/impl 依存性逆転のために各レイヤでinterface定義・実装を分けるとき便利 $ impl 'i *QRCodeReposioryImpl' \ github.com/juntaki/techbook-qrcode/src/domain.QRCodeRepository >>

    infra/qrcode.go func (i *QRCodeReposioryImpl) GetQRCodes(ctx context.Context) ([]*domain.QRCode, error) { panic("not implemented") } func (i *QRCodeReposioryImpl) IsExistQRCode(ctx context.Context, code *domain.QRCode) bool { panic("not implemented") } ...
  3. protoファイル 扱うべき構造とエンドポイントの Request/Responseを定義 syntax = "proto3"; package qrcode; message URL

    { string url = 1; } message QRCode { string id = 1; string url = 2; bytes image = 3; } message QRCodeList { repeated QRCode QRCodes = 1; } message Empty {} service QRCodeService { rpc GetURL(Empty) returns (URL) {} rpc UpdateURL(URL) returns (Empty) {} rpc GetQRCodes(Empty) returns (QRCodeList) {} rpc AddQRCodes(Empty) returns (Empty) {} }
  4. 自動生成のコマンドと生成されるもの一覧 • gRPCサーバのためのGoのinterface • Goのクライアントライブラリ(今回は使わない) • JavaScriptのクライアントライブラリ • TypeScriptの型定義 $

    protoc \ -I proto \ --go_out=plugins=grpc:src/lib/qrcode \ --plugin=protoc-gen-ts=front/node_modules/.bin/protoc-gen-ts \ --js_out=import_style=commonjs,binary:./front/src/proto \ --ts_out=service=true:./front/src/proto \ proto/*.proto
  5. APIエンドポイントはinterfaceを実装するだけ impl+fillstructでダミーの値をかえすように実装しておく APIサーバとして成立するので、フロントエンドと結合できる // QRCodeServiceServer is the server API for

    QRCodeService service. type QRCodeServiceServer interface { GetURL(context.Context, *Empty) (*URL, error) UpdateURL(context.Context, *URL) (*Empty, error) GetQRCodes(context.Context, *Empty) (*QRCodeList, error) AddQRCodes(context.Context, *Empty) (*Empty, error) }
  6. ドメイン層で決めること • 実装詳細と関係ないビジネスルールのための構造を定義 • 永続化のためのインターフェース定義 type QRCode struct { ID

    string Index int } func (q *QRCode) GetURL() string { ... } func (q *QRCode) GetQRCode() []byte { ... } type QRCodeRepository interface { GetQRCodes(ctx context.Context) ([]*QRCode, error) ... } これを利用して アプリケーション層が作れる
  7. xormでDBスキーマからモデルを生成する https://github.com/go-xorm/cmd xorm以外のORM用のモデルも、ある程度はテンプレートの工夫だけでつくれる ちょっと改造すれば何でもいける(例:gorp用→https://s.juntaki.com/hAU $ mysql -uroot -pmysql -h 127.0.0.1

    < ddl.sql $ xorm reverse mysql "root:mysql@tcp(127.0.0.1:3306)/mydb" template package models type Qrcode struct { Id string `db:"id"` } CREATE TABLE IF NOT EXISTS `mydb`.`qrcode` ( `id` CHAR(36) NOT NULL COMMENT 'ID', PRIMARY KEY (`id`))
  8. wireへの入力 //+build wireinject //+build wireinject package main ... func InitializeQRCodeServiceServer(db

    *gorp.DbMap) *application.QRCodeServiceServer { wire.Build(application.NewQRCodeServiceServer, infra.NewQRCodeRepositoryImpl) return &application.QRCodeServiceServer{} } ビルドタグがwireinjectなので、ビルド対象にならない →Goとしてみても一応問題ないので、コード補完などは効くようになる
  9. wireが生成するもの 依存関係に応じた初期化コードが自動生成される // Code generated by Wire. DO NOT EDIT.

    //go:generate wire //+build !wireinject func InitializeQRCodeServiceServer(db *gorp.DbMap) *application.QRCodeServiceServer {  ... }
  10. gotestsでテストを生成する func TestQRCode_GetQRCode(t *testing.T) { type fields struct { ID

    string } tests := []struct { name string fields fields want []byte }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { q := &QRCode{ ID: tt.fields.ID, } if got := q.GetQRCode(); !reflect.DeepEqual(got, tt.want) { t.Errorf("QRCode.GetQRCode() = %v, want %v", got, tt.want) } }) } } $ gotests -all -w domain/qrcode.go ここにテスト条件を書く
  11. juntaki/fixでGolden file testing https://github.com/juntaki/fix テストの正解を「前と変わらないこと」にする func TestQRCodeServiceServer_GetURL(t *testing.T) { ret,

    err := qrCodeServiceServer.GetQRCodes(ctx, &qrcode.Empty{}) if err != nil { t.Fatal(err) } // 1回目の実行では ret をシリアライズして保存します。 // 2回目からは同様にシリアライズして、保存されたファイルとの一致をチェックします。 err = fix.Fix(ret) if err != nil { t.Fatal(err) } }
  12. まとめ 1. 全体で使えるツールを準備する:impl, fillstruct 2. スキーマファーストで外部インタフェースを整理:protoc 3. アプリケーション層で↑の実装をつくる:grpcweb 4. ドメイン層でリポジトリのインタフェースをつくる

    5. インフラストラクチャ層で↑の実装を作る:xorm 6. DIする:wire 7. テストをなるべく生成する:gotests, fix 紹介したテクニックを活用してGoでAPI サーバをつくってみてください!!