LoginSignup
0
1

【Go】Gin + Fx on AWS Lambda

Last updated at Posted at 2023-06-04

【Go】Gin + Fx on AWS Lambda

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

DIを取り入れたモチベーション

  1. (主にLambdaに依存する)実装を隠蔽したかった
  2. テスト容易にしたかった(テストしてるとは言っていない)
  3. なんとなくカッコいいから

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

参考

Dependency Injection with Go-Fx

  1. miyamo2theppl/go-gin-fx-on-lambda-template

  2. uber-go/fx

  3. awslabs/aws-lambda-go-api-proxy

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1