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

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

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

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 サーバをつくってみてください!!