社内LTにて、Goを布教しようと試みましたʕ◔ϖ◔ʔ
Lambda関数をGoで実装してみた話株式会社ユニクエスト長谷川広樹github.com/hiroki-it@Hiroki__IT
View Slide
自己紹介▼ お仕事最近:クラウドインフラ、IaC、CICD、...以前:DDD▼ 関心のある技術領域・クラウドインフラ・IaC・DDDgithub.com/hiroki-it@Hiroki__IT長谷川 広樹 (はせがわ ひろき)株式会社ユニクエスト
目次■ Goの概要■ ディレクトリ構造■ Goの基本文法■ Goファイルの要素■ Lambdaを使用した通知処理
Goの概要(1)手続き型言語のため,オブジェクト機能なし.第9回UMTPモデリング技術ワークショップhttps://umtp-japan.org/
Goの概要(2)静的型付け言語のため,ソースの実行前にビルドが必要.ビルドの成果物 = アーティファクトhttps://www.atmarkit.co.jp/ait/articles/1105/23/news128.html
Goの概要(3)実装方法が強制されるため,可読性が高く,後続者が開発がしやすい実装がスケーリングしやすい静的解析のルールが厳しいため,バグを事前に見つけられるバグが許されない基盤部分に適している
ディレクトリ構造(1)・bin ビルドされたアーティファクト(バイナリファイル)を配置.・pkg アーティファクトとは別に生成されるファイルを配置.・src ソースコードを配置.$GOPATH # 決まりは無いが,$HOME/go とすることが多い├── bin├── pkg└── src
ディレクトリ構造(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.sumhttps://github.com/hiroki-it/notify-slack-of-amplify-eventsディレクトリ構造ベストプラクティス:https://github.com/golang-standards/project-layout
ディレクトリ構造(3)・build Dockerfileを配置.・cmd main.goファイルや,サブmainパッケージを配置.src # ソースコードを配置├── build└── cmd ├── main.go ├── controllers ├── entities └── usecases
Goファイルの要素(1)package // 名前空間の宣言import "" // パッケージの読み込みfunc Xxx(){ // 関数}・一つのディレクトリ内では一つのパッケージ名のみ.・関数名は,頭文字が大文字だとパブリック,小文字だとプライベートになる.
Goファイルの要素(2)package mainfunc main(){}・エントリポイントは,cmdディレクトリまたはその子ディレクトリに配置.・パッケージ名はmain.・関数名はmain.
Goファイルの要素(3)func Division(x int, y int) (int, int) {// 商を計算する.quotient := x / y// 余りを計算する.remainder := x % y// 商と余りを返却する.return quotient, remainder}・引数と返却値の型に厳格.・複数の値を返却可能.
Goの環境構築(1)FROM golang:1.15ENV 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)』が導入
Goの環境構築(2)module github.com/hiroki-it/notify-slack-of-amplify-eventsgo 1.15require ( # プロトコルを除いたURLとバージョンでパッケージを必ず指定github.com/aws/aws-lambda-go v1.23.0github.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ファイル
Goの環境構築(3)# インストールのキャッシュを活用するためにコピーしておく.COPY go.mod go.sum ./# パッケージをインストールする.RUN go get -u github.com/cosmtrek/air \&& go mod download -xCOPY . .・mod downloadコマンド go.modファイルに実装したパッケージをインストール
Goの環境構築(4)https://github.com/cosmtrek/airソースコード変更の度にビルドを行うホットリロードツール『Air』
Goの環境構築(5)# ビルドのアーティファクトを/go/binに配置する.RUN go build -x -o go/bin ./cmdCMD ["/go/bin/cmd"]・cmdディレクトリ ベストプラクティスに則り,cmdディレクトリにソースコードを配置.・buildコマンド ソースコードをビルドし,binディレクトリにアーティファクトを配置.
Lambdaを使用した通知処理(1)package mainimport ("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ファイル
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ファイル
Lambdaを使用した通知処理(3)type Person struct {Name string // 構造体が持つデータの型}func main() {person := Person{"Hiroki"} // 構造体にデータを設定する.fmt.Printf("%#v\n", person.Name) // "Hiroki"}・構造体とは,メソッドは持たず,データのみを持つもの.・関数に構造体を関連付けることで,オブジェクトを擬似的に表現可能.
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"}
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"}}}
Lambdaを使用した通知処理(6)func Handler(request Request) error {var event eventbridge.Eventerr := 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の閲覧を推奨
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ファイル
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宣言された関数は,たとえ処理が停止しても最後に必ず実行される.
おまけ:例外処理json, err := json.Marshal(...)if err != nil {return err}request, err := http.NewRequest(...)if err != nil {return err}・Goの思想では例外処理は無駄なものとされ,try-catchが無い.・多くの関数は二つ目の返却値にエラーを返すため,毎回検証するしかない.
最後にGopherくん超かわいい!!!by Takuya Ueda (https://twitter.com/tenntenn)The Gopher character is based on the Go mascot designed by Renée French.