Slide 1

Slide 1 text

はじめての Go 言語 のプロジェクトを AWS Lambda + API Gateway でやったので パッケージ構成 を晒すよ Go(Un)Conference(Goあんこ)LT大会 5kg @okashoi

Slide 2

Slide 2 text

岡田 正平(おかだ しょうへい)@okashoi • 株式会社ウィルゲート • Gopher にもなりたい PHPer • 2019 年は技術書執筆にチャレンジ! 2 自己紹介

Slide 3

Slide 3 text

• 構成: Go + AWS Lambda + Amazon API Gateway + DynamoDB • 一部に web クローリング処理を含む社内システム • 小規模(8 エンドポイント程度) • 社内初のフル Go 言語プロジェクト • Go 言語未経験者も多い 3 プロジェクト概要

Slide 4

Slide 4 text

• 書籍『みんなのGo言語』の内容を倣った • レイヤードアーキテクチャを採用 • Semantic Import Versioning 4 全体像

Slide 5

Slide 5 text

• 書籍『みんなのGo言語』の内容を倣った • レイヤードアーキテクチャを採用 • Semantic Import Versioning 5 全体像 依 存 の 方 向

Slide 6

Slide 6 text

• 書籍『みんなのGo言語』の内容を倣った • レイヤードアーキテクチャを採用 • Semantic Import Versioning 6 全体像 • import path にメジャーバージョンを含める • ほぼ確実に v2 が作られることはないが メンバーに「推奨されているやりかた」 を知ってもらう目的

Slide 7

Slide 7 text

• controller, usecase, presenter • presenter は interface を定義して メディアタイプごとに実装 (json, xml, text) 7 application 画像出典:Clean Coder Blog https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html

Slide 8

Slide 8 text

• 他のパッケージに依存しない • import するのは標準パッケージに限定 • データ操作に関する interface を定義 • クローリングのための HTTP リクエストを CQRS における Query とみなした • 今思えば ~Repository じゃなくて ~Command という方が分かりやすかった? 8 domain

Slide 9

Slide 9 text

• domain で定義した interface を実装 • DynamoDB への Read/Write • HTTP クライアント + HTML パーサ • 実際のクローリング対象ページの HTML を testdata 下に設置してパーサのテストを書いた 9 infrastructure

Slide 10

Slide 10 text

• request/response を独自のものに変換してから application.controller に渡す • ルーティング • DI 10 main.go

Slide 11

Slide 11 text

• request/response を独自のものに変換してから~ • パッケージを AWS のものに依存させないため 11 main.go func newRequest(awsRequest events.APIGatewayProxyRequest) application.Request { return application.Request{ Path: awsRequest.Path, HTTPMethod: awsRequest.HTTPMethod, Headers: awsRequest.Headers, QueryStringParameters: awsRequest.QueryStringParameters, PathParameters: awsRequest.PathParameters, } } func convertToAwsResponse(res application.Response) events.APIGatewayProxyResponse { return events.APIGatewayProxyResponse{ StatusCode: res.StatusCode, Headers: res.Headers, Body: res.Body, } }

Slide 12

Slide 12 text

• ルーティング • 愚直に文字列比較の switch case • これで充分な用途だったため 12 main.go switch { case req.HTTPMethod == "GET" && req.Path == "/v1/hoge": res = hogeController.Hoge(req) case req.HTTPMethod == "GET" && req.Path == "/v1/fuga": res = fugaController.Fuga(req) // ...(略) }

Slide 13

Slide 13 text

• DI • 詳細→ • シンプルで Go らしくて素敵だと思った (初心者並感) 13 main.go https://speakerdeck.com/morikuni/golang-dot-tokyo-number-11

Slide 14

Slide 14 text

いまのところこんな感じで大きく破綻はしていない • これ以上大きくなるとやや自信ない(もうちょっと細分化したい) Go 未経験者がいるプロジェクトでもタスクが振りやすかった • まずは repository の 1 メソッドからやってもらう • 習熟してきたら usecase 以下をまるごと任せる • 型 + ビルドが通っている事実 + テストコード = レビューするのも楽 14 やってみて所感

Slide 15

Slide 15 text

JSON(HTTP レスポンス)←→ DynamoDB のマッピング方法悩む • 構造体定義にタグをつければ変換できるのは便利 • domain に定義したエンティティにタグをつけるとよさそうだが HTTP や DB に関する知識が domain に漏れ出すことになる • 最終的にはエンティティにつけることにしたが... • そういうことは考えずにシンプルにやろうというのが Go の思想っぽい? エラーハンドリングどうしようか悩む • 独自エラーを定義してログなどに欲しい情報を出力するなどした • 下位で生成したエラーを上位に渡していくのがやや煩雑 15 やってみて所感