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

Lambda関数をGoで実装してみた話

 Lambda関数をGoで実装してみた話

社内エンジニアの方々にGoとGopherくんを布教しようと試みましたʕ◔ϖ◔ʔ

個人リポジトリ:
https://github.com/hiroki-it

プロフィール:
https://www.wantedly.com/id/h_hasegawa

7cb78d6108cc74bec0f2f001127f662a?s=128

Hiroki Hasegawa

March 26, 2021
Tweet

Transcript

  1. Lambda関数を Goで実装してみた話 株式会社ユニクエスト 長谷川広樹 github.com/hiroki-it

  2. 自己紹介 長谷川広樹(はせがわひろき) ▪ 経歴 『生き物の研究者になるぞ! ▶ IT技術おもしろいな... ▶ エンジニアになるぞ!』 を経て,バックエンドエンジニアになった.

    現在は,SRE をやっている. ▪ 関心のある技術領域 拡張性,保守性,可読性を高める技術: ドメイン駆動設計,アーキテクチャ,クラウドインフラ,IaC,コンテナ,CI/CD,Go,etc... github.com/hiroki-it
  3. 目次 ▪ Goの概要 ▪ ディレクトリ構造 ▪ Goの基本文法 ▪ Goファイルの要素 ▪

    Lambdaを使用した通知処理
  4. Goの概要(1) 手続き型言語のため,オブジェクト機能なし. 第9回 UMTPモデリング技術 ワークショップ https://umtp-japan.org/

  5. Goの概要(2) 静的型付け言語のため,ソースの実行前にビルドが必要. ビルドの成果物 = アーティファクト https://www.atmarkit.co.jp/ait/articles/1105/23/news128.html

  6. Goの概要(3) 実装方法が強制されるため,可読性が高く,後続者が開発がしやすい 実装がスケーリングしやすい 静的解析のルールが厳しいため,バグを事前に見つけられる バグが許されない基盤部分に適している

  7. ディレクトリ構造(1) ・bin  ビルドされたアーティファクト(バイナリファイル)を配置. ・pkg  アーティファクトとは別に生成されるファイルを配置. ・src  ソースコードを配置. $GOPATH # 決まりは無いが,$HOME/go

    とすることが多い ├── bin ├── pkg └── src
  8. ディレクトリ構造(2) notify-slack-of-amplify-events # srcに相当 │ ├── build │  └── Dockerfile ├──

    cmd │  ├── main.go │  ├── controllers │  ├── entities │  └── usecases │ ├── config ├── test ├── .air.toml ├── docker-compose.yml ├── go.mod └── go.sum https://github.com/hiroki-it/notify-slack-of-amplify-events ディレクトリ構造ベストプラクティス:https://github.com/golang-standards/project-layout
  9. ディレクトリ構造(3) ・build  Dockerfileを配置. ・cmd  main.goファイルや,サブmainパッケージを配置. src # ソースコードを配置 ├── build

    └── cmd    ├── main.go    ├── controllers    ├── entities    └── usecases
  10. Goファイルの要素(1) package <パッケージ名> // 名前空間の宣言 import "<パッケージ名>" // パッケージの読み込み func

    Xxx(){ // 関数 } ・一つのディレクトリ内では一つのパッケージ名のみ. ・関数名は,頭文字が大文字だとパブリック,小文字だとプライベートになる.
  11. Goファイルの要素(2) package main func main(){ } ・エントリポイントは,cmdディレクトリまたはその子ディレクトリに配置. ・パッケージ名はmain. ・関数名はmain.

  12. Goファイルの要素(3) func Division(x int, y int) (int, int) { //

    商を計算する. quotient := x / y // 余りを計算する. remainder := x % y // 商と余りを返却する. return quotient, remainder } ・引数と返却値の型に厳格. ・複数の値を返却可能.
  13. Goの環境構築(1) FROM golang:1.15 ENV CGO_ENABLED=0 # C言語製のライブラリの有効化 ENV GOOS=linux #

    Goが稼働するOS(※GoはOSに縛られない) ENV GOARCH=amd64 # CPUアーキテクチャ ENV GO111MODULE=on # go.modの有効化 WORKDIR ${GOPATH}/src 最近,『go.mod(≒ composer.json)』『go.sum(≒ composer.lock)』が導入
  14. Goの環境構築(2) module github.com/hiroki-it/notify-slack-of-amplify-events go 1.15 require ( # プロトコルを除いたURLとバージョンでパッケージを必ず指定 github.com/aws/aws-lambda-go

    v1.23.0 github.com/stretchr/testify v1.7.0 ) replace ( # mainパッケージの共通部品も,インターネット上に存在することを強制 github.com/hiroki-it/notify-slack-of-amplify-events/cmd/entities/xxx => /cmd/entities/xxx ) あらゆるパッケージはインターネット上にリリースされるべきという思想 go.modファイル
  15. Goの環境構築(3) # インストールのキャッシュを活用するためにコピーしておく. COPY go.mod go.sum ./ # パッケージをインストールする. RUN

    go get -u github.com/cosmtrek/air \ && go mod download -x COPY . . ・mod downloadコマンド  go.modファイルに実装したパッケージをインストール
  16. Goの環境構築(4) https://github.com/cosmtrek/air ソースコード変更の度にビルドを行うホットリロードツール『Air』

  17. Goの環境構築(5) # ビルドのアーティファクトを/go/binに配置する. RUN go build -x -o go/bin ./cmd

    CMD ["/go/bin/cmd"] ・cmdディレクトリ  ベストプラクティスに則り,cmdディレクトリにソースコードを配置. ・buildコマンド  ソースコードをビルドし,binディレクトリにアーティファクトを配置.
  18. Lambdaを使用した通知処理(1) package main import ( "github.com/aws/aws-lambda-go/lambda" "github.com/hiroki-it/notify-slack-of-amplify-events/cmd/controllers/handler" ) // lambdaパッケージからStartメソッドをコール

    func main() { lambda.Start(handler.HandleRequest) // slackパッケージのHandler関数をコール } main.goファイル
  19. Lambdaを使用した通知処理(2) func HandleRequest(request Request) error { var event eventbridge.Event //

    EventBridgeから転送されたJSONを受信し,構造体にマッピングします. err := json.Unmarshal([]byte(request.Records[0].EventBridge.Event), &event) slackClient := slack.NewSlackClient() message := slackClient.BuildMessage(event) return slackClient.PostMessage(message) } 構造体?? 構造体とJSONのマッピング?? handler.goファイル
  20. Lambdaを使用した通知処理(3) type Person struct { Name string // 構造体が持つデータの型 }

    func main() { person := Person{"Hiroki"} // 構造体にデータを設定する. fmt.Printf("%#v\n", person.Name) // "Hiroki" } ・構造体とは,メソッドは持たず,データのみを持つもの. ・関数に構造体を関連付けることで,オブジェクトを擬似的に表現可能.
  21. Lambdaを使用した通知処理(4) type Person struct { // 構造体 Name string }

    func (person Person) SetName(name string) { // セッター関数を構造体に関連付ける person.Name = name } func (person Person) GetName() string { // ゲッター関数を構造体に関連付ける return person.Name } func main() { person := Person{Name: "Gopher"} person.SetName("Hiroki") // セッター関数で値を上書きする fmt.Printf("%#v\n", person.GetName()) // "Gopher" }
  22. Lambdaを使用した通知処理(5) type Event struct { Version string `json:"version"` ... Detail

    struct { AppId string `json:"appId"` BranchName string `json:"branchName"` JobId string `json:"jobId"` JobStatus string `json:"jobStatus"` } `json:"detail"` } JSONと構造体で相互変換するため,構造体の定義時にJSONマッピングが必要 { "event": { "version": "0", ... "detail": { "appId": "dd31ugx1agx51", "branchName": "feature/293_deploy_to_amplify", "jobId": "2", "jobStatus": "SUCCEED" } } }
  23. Lambdaを使用した通知処理(6) func Handler(request Request) error { var event eventbridge.Event err

    := json.Unmarshal([]byte(request.Records[0].EventBridge.Event), &event) slackClient := slack.NewSlackClient() // 通知メッセージのJSONを構成します. message := slackClient.BuildMessage(event) return slackClient.PostMessage(message) } https://github.com/hiroki-it/notify-slack-of-amplify-events/blob/develop/cmd/ buildMessageメソッドで構成される構造体は,slackのtype.goの閲覧を推奨󰢛
  24. Lambdaを使用した通知処理(7) func postMessage(message Message) error { json, err := json.Marshal(message)

    // マッピングを元に,構造体をJSONに変換する. request, err := http.NewRequest( // リクエストメッセージを定義する. "POST", os.Getenv("SLACK_API_URL"), bytes.NewBuffer(json), ) ... ・JSONをMessage構造体にマッピング. ・POSTリクエストの必要なパラメータを設定. post.goファイル
  25. Lambdaを使用した通知処理(8) … request.Header.Set("Content-Type", "application/json") // ヘッダーを定義する. request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("SLACK_API_TOKEN")))

    client := &http.Client{} response, err := client.Do(request) // HTTPリクエストを送信する. defer response.Body.Close() // deferで宣言しておき,HTTP通信を必ず終了できるようにする. return nil } ・クライアントを起動し,リクエストを送信. ・defer宣言された関数は,たとえ処理が停止しても最後に必ず実行される.
  26. おまけ:例外処理 json, err := json.Marshal(...) if err != nil {

    return err } request, err := http.NewRequest(...) if err != nil { return err } ・Goの思想では例外処理は無駄なものとされ,try-catchが無い. ・多くの関数は二つ目の返却値にエラーを返すため,毎回検証するしかない.
  27. 最後に Gopherくん 超かわいい!!! by Takuya Ueda (https://twitter.com/tenntenn) The Gopher character

    is based on the Go mascot designed by Renée French.