貧乏エンジニアリングGopherの強力な武器vercel serverless functions
はじめに
おはようございます
推しの村山美羽ちゃんによる致死量を超えるかわいい写真の供給が始まったので自動で収集したいという記事を書いた際にvercel
のserverless-functions
が貧乏エンジニアリングにとって非常に強力なツールとなることがわかりました。
そこで今回はもう少し深掘りしてvercel
のserverless-functions
を使ってGoサーバーを構築してみたのでご紹介します。
version
言わずもがな。
❯ go version
go version go1.19.7 darwin/arm64
vercel
を利用するのに必要です。
❯ node --version
v18.15.0
型情報が欲しいのでopenapi
を使います。また、コードの自動生成が欲しいのでoapi-codegenを使います。
❯ oapi-codegen -version
github.com/deepmap/oapi-codegen/cmd/oapi-codegen
v1.12.4
準備
vercel
CLIを利用するので以下の公式ドキュメントを参考にインストールします。
npm install -g vercel
❯ vercel --version
Vercel CLI 28.17.0
28.17.0
❯ vercel login
Vercel CLI 28.17.0
? Log in to Vercel
● Continue with GitHub
○ Continue with GitLab
○ Continue with Bitbucket
○ Continue with Email
○ Continue with SAML Single Sign-On
さまざまな方法でログインができますが私はGitHub
にてログインしました。
プロジェクトの開始
とりあえずプロジェクトを作成するために公式ドキュメントにあるコードを用意します。
mkdir <project>
cd <project>
go mod init <project>
mkdir api
touch api/index.go
package api
import (
"fmt"
"net/http"
)
func Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>Hello from Go!</h1>")
}
こちらも公式サイトにある設定を用いてvercel.json
を用意します。
touch vercel.json
{
"build": {
"env": {
"GO_BUILD_FLAGS": "-ldflags '-s -w'"
}
}
}
一旦動作確認のためにデプロイします。
コマンドの実行履歴は以下となります。
> vercel --prod
Vercel CLI 28.17.0
? Set up and deploy “~/Work/<project>”? [Y/n] y
? Which scope do you want to deploy to? <github_account>
? Link to existing project? [y/N] n
? What’s your project’s name? <project>
? In which directory is your code located? ./
Local settings detected in vercel.json:
No framework detected. Default Project Settings:
- Build Command: `npm run vercel-build` or `npm run build`
- Development Command: None
- Install Command: `yarn install`, `pnpm install`, or `npm install`
- Output Directory: `public` if it exists, or `.`
? Want to modify these settings? [y/N] n
🔗 Linked to <github_account>/<project> (created .vercel and added it to .gitignore)
🔍 Inspect: https://vercel.com/<github_account>/<project>/D3CoTPf9zgaD8nhvhc5RGggn1YSH [1s]
✅ Production: https://<project>.vercel.app [16s]
> curl https://<project>.vercel.app/api
<h1>Hello from Go!</h1>
curl
を用いて動作することを確認します。
index.go
で設定したレスポンスが取得できたことが確認できました。
実装
実践的なサーバーを構築するために
- 複数エンドポイントの対応
- 複数メソッドの対応
を実装してみます。
routingについては以下の記事を参照したところvercel.json
に設定すればできるようです。
(公式ドキュメントで記載を見つけられず。。。)
最終的に以下のディレクトリ構成にしました。
.
├── api
│ └── v1
│ ├── health
│ │ └── index.go
│ └── users
│ └── index.go
├── internal
│ ├── handler
│ │ ├── health.go
│ │ └── user.go
│ └── openapi
│ └── types.gen.go
├── openapi.yaml
└── vercel.json
-
api
ディレクトリ配下にserverless-functions
のエントリーポイントを実装します。(公式に合わせてindex.go
を採用しています。) -
internal/handler
ディレクトリ配下に実際の処理を記載しています。 -
internal/openapi
ディレクトリ配下にopenapi.yaml
から自動生成した型を配置しています。
エンドポイントのroutingはディレクトリ構成およびvercel.json
設定により実現しています。
実際に用いている設定は以下となります。
{
"build": {
"env": {
"GO_BUILD_FLAGS": "-ldflags '-s -w'"
}
},
"routes": [
{ "src": "/api/v1/health", "dest": "/api/v1/health" },
{ "src": "/api/v1/users", "dest": "/api/v1/users" }
]
}
Pathパラメータを利用したところうまくroutingされなかったためQueryパラメータを用いています。
メソッドの分岐は愚直にコード内で記載しています。
index.go
(エントリーポイント)内で分岐を行い実際の処理を呼び出すように実装しました。
package users
import (
"log"
"net/http"
"github.com/takokun778/samplegovercel/internal/handler"
)
func Handler(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL)
switch r.Method {
case http.MethodGet:
handler.GetUser(w, r)
case http.MethodPost:
handler.PostUser(w, r)
case http.MethodPut:
handler.PutUser(w, r)
case http.MethodDelete:
handler.DeleteUser(w, r)
case http.MethodOptions:
w.Write([]byte(""))
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
動作確認
GET api/v1/health
curl -X GET 'https://<project>.vercel.app/api/v1/health' -i
HTTP/2 200
OK
POST api/v1/users
> curl -X POST -H 'Content-Type: application/json' -d '{"name": "村山美羽"}' 'https://<project>.vercel.app/api/v1/users' -i
HTTP/2 200
{"user":{"id":"8c710495-15b9-47d8-8771-52d26f9ffb23","name":"村山美羽"}}
POST api/v1/users(bodyなし)
❯ curl -X POST -H 'Content-Type: application/json' 'https://<project>.vercel.app/api/v1/users' -i
HTTP/2 500
POST api/v1/users(body空)
❯ curl -X POST -H 'Content-Type: application/json' -d '{}' 'https://samplegovercel.vercel.app/api/v1/users' -i
HTTP/2 400
GET api/v1/users?id=1
❯ curl -X GET 'https://<project>.vercel.app/api/v1/users?id=1' -i
HTTP/2 200
{"user":{"id":"1","name":"村山美羽"}}
GET api/v1/users(id指定なし)
❯ curl -X GET 'https://<project>.vercel.app/api/v1/users' -i
HTTP/2 404
PUT api/v1/users?id=1
❯ curl -X PUT -H 'Content-Type: application/json' -d '{"name": "村山美羽"}' 'https://<project>.vercel.app/api/v1/users?id=1' -i
HTTP/2 200
{"user":{"id":"1","name":"村山美羽"}}
PUT api/v1/users(id指定なし)
❯ curl -X PUT -H 'Content-Type: application/json' -d '{"name": "村山美羽"}' 'https://<project>.vercel.app/api/v1/users' -i
HTTP/2 404
PUT api/v1/users(bodyなし)
❯ curl -X PUT -H 'Content-Type: application/json' 'https://<project>.vercel.app/api/v1/users?id=1' -i
HTTP/2 500
PUT api/v1/users(body空)
❯ curl -X PUT -H 'Content-Type: application/json' -d '{}' 'https://<project>.vercel.app/api/v1/users?id=1' -i
HTTP/2 400
DELETE api/v1/users?id=1
❯ curl -X DELETE 'https://<project>.vercel.app/api/v1/users?id=1' -i
HTTP/2 200
OK
DELETE api/v1/users(id指定なし)
❯ curl -X DELETE 'https://<project>.vercel.app/api/v1/users' -i
HTTP/2 404
PATCH api/v1/users
❯ curl -X PATCH 'https://<project>.vercel.app/api/v1/users' -i
HTTP/2 405
注意点
-
go mod vendor
を利用するとdeploy
時にError: Unexpected error. Please try again later. ()
が発生してデプロイされませんでした。 -
npm init -y
&npm install --save-dev vercel
により環境を汚さない方法も考えましたが、go mod tidy
の際にnode_modules
が邪魔になりエラーが発生したのでグローバルにインストールしています。 - Pathパラメーターで
id
指定をしたかったがうまくroutingできなかったのでQueryパラメータを利用しています。 -
root
配下にindex.go
を配置してデプロイしてアクセスするとindex.go
ファイルがダウンロードされてしまいます。 -
vercel deploy
コマンド(--prod
なし)を実行するとpreview
環境へデプロイされいちいち動作確認するのがめんどくさかったので--prod
を指定しています。
お片付け
サンプル実装とはいえ、GitHub
のアカウントはリポジトリからエンドポイントが推測できてしまうのでお掃除しておきます。
vercel project rm <project-name>
おわりに
以前cyclic
というサービスを利用して無理やりFaaS
にGoサーバーを建てる方法を考えてみましたがvercel
を用いてGoサーバーを簡単に構築できました!
vercel serverless functions
のツボがある程度わかりました。非常に魅力的なサービスです。
CLI
を眺めているとdev
コマンドなどもありローカル環境に閉じて開発もできそうです。
また、使ってみて知見が溜まったら共有します。
また、vercel serverless functions
はどうやらAWS Lambda
みたいです。
実装する際にはLambda
を意識しながら実装する必要がありそうです。
- コードはなるべく小さく(コールドスタートが早くなる)
- 同時実行数
- 実行時間の上限
- ペイロードサイズ
- コネクション爆発 etc...
今回実装したコードはこちらに置いておきます。
Discussion