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

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

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

社内LTにて、Goを布教しようと試みましたʕ◔ϖ◔ʔ

More Decks by Hiroki Hasegawa (長谷川広樹)

Other Decks in Programming

Transcript

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

    View Slide

  2. 自己紹介
    ▼ お仕事
    最近:クラウドインフラ、IaC、CICD、...
    以前:DDD
    ▼ 関心のある技術領域
    ・クラウドインフラ
    ・IaC
    ・DDD
    github.com/hiroki-it
    @Hiroki__IT
    長谷川 広樹 (はせがわ ひろき)
    株式会社ユニクエスト

    View Slide

  3. 目次
    ■ Goの概要
    ■ ディレクトリ構造
    ■ Goの基本文法
    ■ Goファイルの要素
    ■ Lambdaを使用した通知処理

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  9. ディレクトリ構造(3)
    ・build
     Dockerfileを配置.
    ・cmd
     main.goファイルや,サブmainパッケージを配置.
    src # ソースコードを配置
    ├── build
    └── cmd
       ├── main.go
       ├── controllers
       ├── entities
       └── usecases

    View Slide

  10. Goファイルの要素(1)
    package // 名前空間の宣言
    import "" // パッケージの読み込み
    func Xxx(){ // 関数
    }
    ・一つのディレクトリ内では一つのパッケージ名のみ.
    ・関数名は,頭文字が大文字だとパブリック,小文字だとプライベートになる.

    View Slide

  11. Goファイルの要素(2)
    package main
    func main(){
    }
    ・エントリポイントは,cmdディレクトリまたはその子ディレクトリに配置.
    ・パッケージ名はmain.
    ・関数名はmain.

    View Slide

  12. Goファイルの要素(3)
    func Division(x int, y int) (int, int) {
    // 商を計算する.
    quotient := x / y
    // 余りを計算する.
    remainder := x % y
    // 商と余りを返却する.
    return quotient, remainder
    }
    ・引数と返却値の型に厳格.
    ・複数の値を返却可能.

    View Slide

  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)』が導入

    View Slide

  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ファイル

    View Slide

  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ファイルに実装したパッケージをインストール

    View Slide

  16. Goの環境構築(4)
    https://github.com/cosmtrek/air
    ソースコード変更の度にビルドを行うホットリロードツール『Air』

    View Slide

  17. Goの環境構築(5)
    # ビルドのアーティファクトを/go/binに配置する.
    RUN go build -x -o go/bin ./cmd
    CMD ["/go/bin/cmd"]
    ・cmdディレクトリ
     ベストプラクティスに則り,cmdディレクトリにソースコードを配置.
    ・buildコマンド
     ソースコードをビルドし,binディレクトリにアーティファクトを配置.

    View Slide

  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ファイル

    View Slide

  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ファイル

    View Slide

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

    View Slide

  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"
    }

    View Slide

  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"
    }
    }
    }

    View Slide

  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の閲覧を推奨󰢛

    View Slide

  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ファイル

    View Slide

  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宣言された関数は,たとえ処理が停止しても最後に必ず実行される.

    View Slide

  26. おまけ:例外処理
    json, err := json.Marshal(...)
    if err != nil {
    return err
    }
    request, err := http.NewRequest(...)
    if err != nil {
    return err
    }
    ・Goの思想では例外処理は無駄なものとされ,try-catchが無い.
    ・多くの関数は二つ目の返却値にエラーを返すため,毎回検証するしかない.

    View Slide

  27. 最後に
    Gopherくん
    超かわいい!!!
    by Takuya Ueda (https://twitter.com/tenntenn)
    The Gopher character is based on the Go mascot designed by Renée French.

    View Slide