【Go】Gin + Fx on AWS Lambda 

# 【Go】Gin + Fx on AWS Lambda 

Goの勉強を兼ねて個人でWebアプリの開発を進めているのですが、
表題の構成が今後テンプレートとして使いまわせそうだったので
Publicリポジトリ[^1]として公開しました。今回はその覚書も兼ねてです。

## DIを取り入れたモチベーション
1. (主にLambdaに依存する)実装を隠蔽したかった
1. テスト容易にしたかった(テストしてるとは言っていない)
1. なんとなくカッコいいから

## fxとは？
Uberが公開しているDI用のOSSです。[^2]
GoogleのWireが有名なようですが、
fxの方がリリースが活発だったので今回はfxを採用しました。

## ソースコード
#### エントリポイント
Cold Start...と標準出力している点を除けば
一見Lambdaのソースコードとはわからないと思います。
```github.com/miyamo2theppl/go-gin-fx-on-lambda-template/api/hello/main.go
package main

import (
	"fmt"

	"github.com/miyamo2theppl/go-gin-fx-on-lambda-template/api/hello/configure/fxapp"
	"go.uber.org/fx"
)

var app *fx.App

func init() {
	app = fxapp.FxApp
}

func main() {
	fmt.Println("Cold Start...")
	app.Run()
}
```

#### fxapp
mainで使用しているfx.Appはここで初期化しています。
依存関係はここに閉じ込めてあるので
LambdaからEC2等へ移管する際もソースの修正は①②を変更して
バニラgin用の実装に差し替えるだけで完了します。
```github.com/miyamo2theppl/go-gin-fx-on-lambda-template/api/hello/configure/fxapp.go
package fxapp

import (
	"github.com/miyamo2theppl/go-gin-fx-on-lambda-template/api/hello/application"
	"github.com/miyamo2theppl/go-gin-fx-on-lambda-template/api/hello/presentation"
	"github.com/miyamo2theppl/go-gin-fx-on-lambda-template/fxapp/runner"
	"github.com/miyamo2theppl/go-gin-fx-on-lambda-template/lambda" // ①
	"go.uber.org/fx"
)

var FxApp *fx.App

func init() {
	FxApp = fx.New(
		fx.Options(
			application.Module,
			presentation.Module,
			lambda.Module, // ②
		),
		fx.Invoke(
			func(lifecycle fx.Lifecycle, runner runner.FxAppRunner) {
				runner.Run(lifecycle)
			},
		),
	)
}
```

#### fx_provider
各実装と同一パッケージ内にfx.Option型の変数 Moduleを定義しています。
このModuleをfx.App生成時に読み込ませることで依存関係を解決してくれます。
```github.com/miyamo2theppl/go-gin-fx-on-lambda-template/lambda/runner/fx_provider.go
package runner

import "go.uber.org/fx"

var Module = fx.Options(fx.Provide(NewLambdaRunner))
```

#### FxAppRunner
FxAppRunner interfaceではfx.appの開始/終了にフックする処理を定義しています。
LambdaRunnerではLambdaの起動、GinRunnerではginの起動をそれぞれOnStartイベントにフックする実装になっています。

繰り返しになりますが、実行環境がLambdaでなくなった場合は、
注入する実装をLambdaRunner→GinRunnerに差し替えるだけです。
```github.com/miyamo2theppl/go-gin-fx-on-lambda-template/lambda/runner/runner.go
package runner

import (
	"context"
	"fmt"

	"github.com/aws/aws-lambda-go/lambda"
	"github.com/miyamo2theppl/go-gin-fx-on-lambda-template/fxapp/runner"
	"github.com/miyamo2theppl/go-gin-fx-on-lambda-template/lambda/handler"
	"go.uber.org/fx"
)

type LambdaRunner struct {
	handler *handler.Handler
}

func (r *LambdaRunner) Run(lifecycle fx.Lifecycle) {
	lifecycle.Append(
		fx.Hook{
			OnStart: func(context.Context) error {
				fmt.Println("Starting Lambda")
				go lambda.Start(r.handler.Execute)
				return nil
			},
			OnStop: func(context.Context) error {
				fmt.Println("Stopping Lambda")
				return nil
			},
		},
	)
}

func NewLambdaRunner(handler *handler.Handler) runner.FxAppRunner {
	return &LambdaRunner{handler}
}
```
```github.com/miyamo2theppl/go-gin-fx-on-lambda-template/fxapp/runner/gin/runner.go
package gin

import (
	"context"
	"fmt"

	"github.com/gin-gonic/gin"
	"github.com/miyamo2theppl/go-gin-fx-on-lambda-template/fxapp/runner"
	"go.uber.org/fx"
)

type GinRunner struct {
	engine *gin.Engine
	uri    []string
}

func (r *GinRunner) Run(lifecycle fx.Lifecycle) {
	lifecycle.Append(
		fx.Hook{
			OnStart: func(context.Context) error {
				fmt.Println("Starting Gin")
				if len(r.uri) == 1 {
					go r.engine.Run(r.uri[0])
				} else {
					go r.engine.Run()
				}
				return nil
			},
			OnStop: func(context.Context) error {
				fmt.Println("Stopping Gin")
				return nil
			},
		},
	)
}

func NewGinRunner(engine *gin.Engine, uri ...string) runner.FxAppRunner {
	uri_len := len(uri)
	if uri_len > 1 {
		panic(fmt.Sprintf("Too many arguments for NewGinRunner: %d", uri_len))
	}
	return &GinRunner{engine, uri}
}
```

もう一度main.goに戻るとこんなイメージの挙動です。
```github.com/miyamo2theppl/go-gin-fx-on-lambda-template/api/hello/main.go
func main() {
	fmt.Println("Cold Start...")
	app.Run() // ← ここでOnStartが実行される
}
```

#### lambda-gin
Lambda上でginを実行するための実装についてはほぼサンプル[^3]通りですが
fxの恩恵を受けるためにHandlerはラッパーとなる構造体を用意しています。

```github.com/miyamo2theppl/go-gin-fx-on-lambda-template/lambda/handler/handler.go
package handler

import (
	"context"

	"github.com/aws/aws-lambda-go/events"
	ginadapter "github.com/awslabs/aws-lambda-go-api-proxy/gin"
)

type Handler struct {
	ginLambda *ginadapter.GinLambda
}

func (r Handler) Execute(ctx context.Context, req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	return r.ginLambda.ProxyWithContext(ctx, req)
}

func NewHandler(ginLambda *ginadapter.GinLambda) *Handler {
	return &Handler{ginLambda}
}
```

[^1]: [miyamo2theppl/go-gin-fx-on-lambda-template](https://github.com/miyamo2theppl/go-gin-fx-on-lambda-template)
[^2]: [uber-go/fx](https://github.com/uber-go/fx)
[^3]: [awslabs/aws-lambda-go-api-proxy](https://github.com/awslabs/aws-lambda-go-api-proxy#gin)
## 参考
 [Dependency Injection with Go-Fx](https://articles.wesionary.team/dependency-injection-with-go-fx-b698a6585cf0)