SAMとGoのテンプレートエンジンでSSR

静的なテンプレートを読み込んでSSRを行うWebアプリケーションを作成します。


## 前提知識

* AWS SAM と Golang を利用して Severless なWebアプリケーションを作成することができる。
* Golang の html/template パッケージを利用してHTML形式の文字列を標準出力することができる。

（以後AWSとlangは省略して記述します）


## Lambdaで静的なファイルを扱う

様々な方法があるかもしれませんが、今回は `AWS Lambda Layer`[^1] を利用します。
この機能を利用することで、 HTMLやCSS等の静的なデータをLambdaのzipファイルに含めることができます。

SAMでは、SAMのテンプレートファイル内に設定を記述することでLambda Layerを構築することができます[^2]。


## 構築

早速SAMでLambda Layerを利用したアプリケーションを構築してみましょう。
今回は以下のような単純なページを作成していきます。

<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/425840/0fd1a4ee-129f-9cf1-cfbb-6136d36b4401.png">


### 手順

まずはいつも通りSAMのプロジェクトを作成します。

```bash
$ sam --version
SAM CLI, version 1.36.0

$ for var in 1 1 4 sam_ssr 1; do echo $var; done | sam init
```

次に静的なテンプレートを作成します。
今回は `{{.Title}}` と `{{.Body}}` に値を埋め込みます。

```contents/index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>{{.Title}}</title>
  </head>
  <body>
    <h1>{{.Title}}</h1>
    <div>{{.Body}}</div>
  </body>
</html>
```

次にレンダリング用のコードを作成します。
Lambda は、関数の実行環境を設定する際に、レイヤーの内容を `/opt` ディレクトリに抽出します[^3]。したがって、テンプレートを読み込む処理は `/opt` を意識して記述するようにしましょう。

```hello-world/main.go
package main

import (
	"html/template"
	"strings"

	"github.com/aws/aws-lambda-go/events"
	"github.com/aws/aws-lambda-go/lambda"
)

type Template struct {
	Title string
	Body  string
}

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
	parsedObject, err := template.ParseFiles("/opt/index.html")
	if err != nil {
		panic(err)
	}

	template := Template{"title_string", "body_string"}
	writer := new(strings.Builder)
	err = parsedObject.Execute(writer, template)
	if err != nil {
		panic(err)
	}

	return events.APIGatewayProxyResponse{
		Body: writer.String(),
		Headers: map[string]string{
			"Content-Type": "text/html; charset=utf-8",
		},
		StatusCode: 200,
	}, nil
}

func main() {
	lambda.Start(handler)
}
```


これでLambdaで実行される処理を記述することができました。
次に、Lambda Layerに含めるファイルを処理するためのMakefileを作成します。
今回は単純にMakefileが存在するディレクトリから、成果物のディレクトリへコピーする処理を書きました。

```contents/makefile
.PHONY: build-HelloWorldFunctionLayer

MAKEFILE_PATH:=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))

build-HelloWorldFunctionLayer:
	cp -r $(MAKEFILE_PATH)/* $(ARTIFACTS_DIR)/
```

最後に、Lambda Layer用の設定をSAMのテンプレートに記述します。

```templete.yaml.diff
--- a/sam_ssr/template.yaml
+++ b/sam_ssr/template.yaml
@@ -29,6 +29,16 @@ Resources:
       Environment: # More info about Env Vars: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object
         Variables:
           PARAM1: VALUE
+      Layers:
+        - !Ref HelloWorldFunctionLayer
+
+  HelloWorldFunctionLayer:
+    Type: AWS::Serverless::LayerVersion
+    Properties:
+      Description: HelloWorldFunction layer.
+      ContentUri: contents/
+    Metadata:
+      BuildMethod: makefile
 
 Outputs:
   # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
```

最終的なディレクトリ構成は以下の通りです。

```bash
.
├── Makefile
├── README.md
├── contents
│   ├── index.html   # 追加
│   └── makefile     # 追加
├── hello-world
│   ├── go.mod
│   ├── go.sum
│   ├── main.go      # 修正
│   └── main_test.go
└── template.yaml    # 修正
```


### 実行

早速実行してみましょう。

```bash
$ sam build && sam local start-api
# => http://127.0.0.1:3000/hello/
```

うまくいけば前述の完成イメージのような画面が表示されるはずです。
おつかれさまでした。


[^1]: https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html<br> > Lambda レイヤーは、追加のコードやデータを含めることができる .zip ファイルアーカイブです。
[^2]: https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/building-layers.html<br> > カスタムレイヤーを構築するには、それを AWS Serverless Application Model (AWS SAM) テンプレートファイルで宣言し、BuildMethod エントリがある Metadata リソース属性セクションを含めます。
[^3]: https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html<br> > Lambda は、関数の実行環境を設定する際に、レイヤーの内容を /opt ディレクトリに抽出します。