Slide 1

Slide 1 text

Go APIを生成するSwagger駆動開発 on AWS Fargate Go Conference’20 in Autumn SENDAI 2020.10.10 清家史郎 1

Slide 2

Slide 2 text

自己紹介:清家 史郎 - ID - - 清家 史郎 @seike460 GitHub:seike460 Twitter:@seike460 Work at - 株式会社 Fusic (フュージック) 技術開発本部/技術開発第一部門 - チームリーダー/エバンジェリスト/プリンシパルエンジニア - Skill - - PHP/Go/AWS Personal - Go Conference ‘19 Summer in Fukuoka コアスタッフ - Fukuoka.go オーガナイザー 46fm パーソナリティ 2

Slide 3

Slide 3 text

Agenda 1. アプリケーション構成 2. Swagger駆動開発 3. Go-Swagger 4. OpenAPI Generator ✕ TypeScript 5. AWS環境へのデプロイ 6. まとめ 3

Slide 4

Slide 4 text

1 アプリケーション構成

Slide 5

Slide 5 text

アプリケーション構成 バックエンドにGo API、フロントエンドにReactを採用した SPA(Single Page Application)構成 5

Slide 6

Slide 6 text

アプリケーション構成 集中すべきはビジネスロジック それ以外の箇所に労力を使わないようにしたいと意識していました 6

Slide 7

Slide 7 text

アプリケーション構成 バックエンドとフロントエンドのAPI連携が相当数有り 今後の開発を見据え連携に対する細かい齟齬などを是正する仕組みを入れたい 7

Slide 8

Slide 8 text

2 Swagger駆動開発

Slide 9

Slide 9 text

Swagger SwaggerはOpenAPI仕様、OpenAPI Specificationと言われる、 REST APIを定義するための標準仕様に基づいた一連のオープンソースフレームワーク REST APIの設計、構築、文書化、および使用に役立つ機能を提供します 9

Slide 10

Slide 10 text

Swagger and OpenAPIの違い OpenAPI:仕様 Swagger:仕様を実装するためのツール ただし OpenAPI Specification v2.0の事を Swagger Specification、Swaggerファイルと呼ぶことがあります 参考:What Is the Difference Between Swagger and OpenAPI? 10

Slide 11

Slide 11 text

Swagger Editor 11

Slide 12

Slide 12 text

Swagger UI 12

Slide 13

Slide 13 text

Swagger Codegen 13

Slide 14

Slide 14 text

Swagger Codegen Swagger CodegenはJavaを利用する 手元にJavaを入れるのは避けたいと考え 技術選定を続けた 14

Slide 15

Slide 15 text

Swagger Codegen Dockerのイメージが存在する為、 こちらでの開発も考えたが 参考文献が少ないように感じた 15

Slide 16

Slide 16 text

フューチャー技術ブログ 別の選択肢を調査していた所、 フューチャーさんの技術ブログを拝見 go-swaggerを確認し、 こちらを採用することにした 16

Slide 17

Slide 17 text

go-swagger 注意点としては Swagger 2.0 ≒ OpenAPI Specification v2.0に 相当するツールであるということ 技術選定をする責任は 選定した本人にあるので OpenAPI Specification v3.0に対する 移行する可能性も視野に入れたかった 17

Slide 18

Slide 18 text

swagger2openapi Swaggerファイルを OpenAPI 3.0.xに変換 万一詰んでも方向転換出来る 踏ん切りがついた為、 go-swaggerでの対応を開始 18

Slide 19

Slide 19 text

3 go-swagger

Slide 20

Slide 20 text

実際のSwagger swagger: "2.0" host: "petstore.swagger.io" basePath: "/v2" tags: - name: "pet" description: "Everything about your Pets" externalDocs: description: "Find out more" url: "http://swagger.io" schemes: - "https" paths: /pet: post: tags: - "pet" summary: "Add a new pet to the store" Swagger をYAMLで記述 Swagger Editorを利用して チェックを行いながら記述する事は可能 一方で形式を覚える事にコスト感を感じていた 20

Slide 21

Slide 21 text

Stoplight Studio 夢のようなツールでは無いが、YAMLとにらめっこするよりは 快適にswagger記述が可能 21

Slide 22

Slide 22 text

Stoplight Studio 22

Slide 23

Slide 23 text

go-swagger go-swaggerにて型定義されたコードの自動生成 $ swagger generate server -f doc/swagger/hoge.v1.yaml -t hoge/ 2020/09/30 06:53:25 validating spec /Users/seike460/src/github.com/fusic/hoge/doc/swagger/hoge.v1.yaml 2020/09/30 06:53:25 preprocessing spec with option: minimal flattening 2020/09/30 06:53:25 building a plan for generation 2020/09/30 06:53:25 generation target hoge/ 2020/09/30 06:53:25 planning definitions 2020/09/30 06:53:25 planning operations 2020/09/30 06:53:25 grouping operations into packages 2020/09/30 06:53:25 planning meta data and facades ~ 省略 ~ 2020/09/30 06:53:26 package field operations 2020/09/30 06:53:26 creating generated file "doc.go" in "hoge/restapi" as doc 2020/09/30 06:53:26 executed template asset:serverDoc 2020/09/30 06:53:26 Generation completed! For this generation to compile you need to have some packages in your GOPATH: * github.com/go-openapi/runtime * github.com/jessevdk/go-flags You can get these now with: go get -u -f hoge/... 23

Slide 24

Slide 24 text

ディレクトリ構成 ├── cmd │ └── hoge-server │ └── main.go ├── models │ └── tag.go └── restapi ├── configure_hoge.go ├── doc.go ├── embedded_spec.go ├── operations │ ├── hoge_api.go │ └── tags │ ├── delete_tag.go │ ├── delete_tag_parameters.go │ ├── delete_tag_responses.go │ ├── delete_tag_urlbuilder.go │ ├── get_tags.go │ ├── get_tags_parameters.go │ ├── get_tags_responses.go │ ├── get_tags_urlbuilder.go │ ├── patch_tag.go │ ├── patch_tag_parameters.go │ ├── patch_tag_responses.go │ ├── patch_tag_urlbuilder.go │ ├── post_tags.go │ ├── post_tags_parameters.go │ ├── post_tags_responses.go │ └── post_tags_urlbuilder.go └── server.go cmd … サーバー起動コマンド models … modelの定義 restapi … operationsの定義 configure_hoge.goを修正していくのが一般的 24

Slide 25

Slide 25 text

configure_hoge.go 25

Slide 26

Slide 26 text

configure_hoge.go api.HogeHandler内に実際処理を記述する必要がある 「configure_hoge.go」を育てていくのが正解なのかもしれないが、 もしAPI定義が追加された際はこの部分のコードはスッと整合性が取れた状態で再生成したい 「configure_hoge.go」自体の修正は行いたいため、 何度再生成しても最短の修正で追加出来る用にしたいと考えた。 26

Slide 27

Slide 27 text

configure_hoge_ext.go - restapi/configure_hoge.go api.ServerShutdown = func() {} configureAPIExt(api) return setupGlobalMiddleware(api.Serve(setupMiddlewares)) - restapi/configure_hoge_ext.go func configureAPIExt(api *operations.HogeAPI) { api.SetHandlers() } Goがパッケージ拡張出来る事を利用して自動生成するコードを別ファイルにて拡張 「configure_hoge.go」を何度も自動生成しても辛くないようにした。 今回の場合、 Handlerは視認性の観点から別管理にしたいと考えた為、 api.SetHandlers()の様に拡張を行い、別ファイルにて HandlerのSetを行った。 ※これが正しいやり方なのかは確証があるわけではありませんのでご注意ください 27

Slide 28

Slide 28 text

models ├── cmd │ └── hoge-server │ └── main.go ├── models │ └── tag.go └── restapi ├── configure_hoge.go ├── doc.go ├── embedded_spec.go ├── operations │ ├── hoge_api.go │ └── tags │ ├── delete_tag.go │ ├── delete_tag_parameters.go │ ├── delete_tag_responses.go │ ├── delete_tag_urlbuilder.go │ ├── get_tags.go │ ├── get_tags_parameters.go │ ├── get_tags_responses.go │ ├── get_tags_urlbuilder.go │ ├── patch_tag.go │ ├── patch_tag_parameters.go │ ├── patch_tag_responses.go │ ├── patch_tag_urlbuilder.go │ ├── post_tags.go │ ├── post_tags_parameters.go │ ├── post_tags_responses.go │ └── post_tags_urlbuilder.go └── server.go Modelが生成されるディレクトリ ここにHandler内で利用するModelが入っている modelsも再生成される事を考慮して拡張する 28

Slide 29

Slide 29 text

models/hoge.go 生成されたコードには構造体がある この構造体を拡張していく事で 関数を追加していく 29

Slide 30

Slide 30 text

models/hoge_func.go models/hoge.goと同じパッケージ内にコードを配置 構造体を利用した実際のコード部分を記述する Handler内でこのmodelsのコードを 利用する事でアプリケーションを構築していく 30

Slide 31

Slide 31 text

models/hoge_func_test.go 拡張したコードは生成されたコードとは違い 安全であることは保証できない 従ってhoge_func.goを保証する為に 拡張したコードに対するテストを書く 自分自身のコードへの安全を保証する 31

Slide 32

Slide 32 text

Swaggerベースでコードを書くことにより、 ビジネスロジック部分に集中する インターフェイス部分で守られたコードで基礎がしっかりしたAPIを構築出来る 32

Slide 33

Slide 33 text

4 OpenAPI Generator ✕ TypeScript

Slide 34

Slide 34 text

OpenAPI Generator OpenAPI Generator Swagger形式で記述されたYAMLやJSONから コード生成してくれるOSS(OpenAPI Specification V3にも対応) $ openapi-generator generate -g typescript-axios -i doc/swagger/hoge.v1.yaml -o frontend/src/api [main] INFO o.o.codegen.DefaultGenerator - Generating with dryRun=false [main] INFO o.o.codegen.DefaultGenerator - OpenAPI Generator: typescript-axios (client) [main] INFO o.o.codegen.DefaultGenerator - Generator 'typescript-axios' is considered stable. [main] INFO o.o.c.l.AbstractTypeScriptClientCodegen - Hint: Environment variable 'TS_POST_PROCESS_FILE' (optional) not defined. E.g. to format the source code, please try 'export TS_POST_PROCESS_FILE="/usr/local/bin/prettier --write"' (Linux/Mac) [main] INFO o.o.c.l.AbstractTypeScriptClientCodegen - Note: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI). [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/seike460/src/github.com/fusic/hoge/frontend/src/api/index.ts [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/seike460/src/github.com/fusic/hoge/frontend/src/api/base.ts [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/seike460/src/github.com/fusic/hoge/frontend/src/api/api.ts [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/seike460/src/github.com/fusic/hoge/frontend/src/api/configuration.ts [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/seike460/src/github.com/fusic/hoge/frontend/src/api/git_push.sh [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/seike460/src/github.com/fusic/hoge/frontend/src/api/.gitignore [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/seike460/src/github.com/fusic/hoge/frontend/src/api/.npmignore [main] INFO o.o.codegen.AbstractGenerator - writing file /Users/seike460/src/github.com/fusic/hoge/frontend/src/api/.openapi-generator/VERSION 34

Slide 35

Slide 35 text

OpenAPI Generator ✕ TypeScript OpenAPI Generator ✕ TypeScriptでカッチリ型定義を行いながら開発することで 定まったコードを生成し、細かなミスを排除する →ビジネスロジックに集中する →コードの質とスピードを加速させる import { DefaultApi, Tag } from "../api/api"; ~ 省略 ~ const api = new DefaultApi(); ~ 省略 ~ const searchTags = (keyword: string) => { api .getTags(keyword) .then((res) => { setTags(res.data); }) .catch((err) => { alert(err.message); // エラー処理が甘いことはこの場では目をつぶってください }); }; 35

Slide 36

Slide 36 text

AWS Amplify モバイルおよびフロントエンドのウェブデベロッパーが AWS を利用して安全でスケーラブルなフルスタックアプリケーションを 構築できるようにするツールとサービスのセット 36

Slide 37

Slide 37 text

5 デプロイ

Slide 38

Slide 38 text

マルチステージビルド ビルドと、ビルドしたバイナリを利用するコンテナを分離 ビルドに必要なGoが入っていないalpineにバイナリを配置 -> 最小のコンテナサイズで実行ファイルが利用可能 ※利用OSに注意 FROM golang:1.15.2 as build WORKDIR /go/src COPY . . RUN CGO_ENABLED=0 go build -o hoge-server cmd/hoge-server/main.go FROM alpine:edge WORKDIR /root/ COPY --from=build /go/src/hoge-server . CMD ["./hoge-server", "--host", "0.0.0.0", "--port", "80"] 38

Slide 39

Slide 39 text

マルチステージビルド 39

Slide 40

Slide 40 text

デプロイ 40

Slide 41

Slide 41 text

AWS CodeBuild 今回 AWS Fargateを利用する為、ECRにPushしておく必要がある CodePipeline でGitHubよりコード取得 -> CodeBuildを通してECRにPushする version: 0.2 phases: pre_build: commands: - $(aws ecr get-login --no-include-email --region ap-northeast-1) build: commands: - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG --build-arg BUILDSTAGE=$BUILDSTAGE . - echo docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG post_build: commands: - echo Build completed on `date` - echo Pushing the Docker image... - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG - printf '[{"name":"","imageUri":"%s"}]' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > artifacts.json 41

Slide 42

Slide 42 text

まとめ Point 1 SPAを作る上でのフロントエンドとバックエンドとの連携をSwaggerで決めて開発を進める Point 2 Go API を 構築するにあたり、コード生成を行いインターフェイス部分をgo-swaggerに任せる 合わせてGo APIへの接続はOpenAPI generator x TypeScriptに任せて、ビジネスロジックに集中する Point 3 Go のビジネスロジック部分の構築は生成されたコードをパッケージ拡張して構築する Point 4 AWS FargateへのデプロイはECRに配置して利用する マルチステージビルドでGoの利点を活かし、小さなコンテナサイズで実現 42

Slide 43

Slide 43 text

Thank You ご清聴いただきありがとうございました We are Hiring ! https://recruit.fusic.co.jp/