自己紹介
初めまして、今年度21卒として入社したテクノロジー戦略本部の早瀬です。 バイセルではインターン生として2020年の11月から活動しており、今年の4月に入社しました。 入社してからアサインされたプロジェクトでサーバーレスアーキテクチャの実装にチャレンジさせていただく機会を頂いたので、今回はその一部を紹介したいと思います。
背景
今回は以前の記事に関連する新システムを開発することになり、前回同様サーバーレスで構築することになりました。 tech.buysell-technologies.com ただ今回はRubyでなくGolangでバックエンドを実装することになったので、フレームワークはRuby on JetsではなくAWS SAMを採用しました。
SAMとは?
SAM(Serverless Application Model)はサーバーレスアプリケーションを構築するためのAWS公式のフレームワークです。yaml形式で簡単にリソースを定義でき、SAM CLIを使用することでローカルにもlambdaの実行環境を作ることができます。他にフレームワークの候補としてServerlessFrameworkがありましたが、ローカルでの開発環境の構築のしやすさからSAMを使用することにしました。
OpenAPIとは?
OpenAPIはREST APIのためのAPI記述フォーマットで、APIの仕様をyaml形式で定義することができます。またopenapi-generatorなどのツールを使用することで定義したOpenAPIからクライアントコードなどを自動生成することができます。今回のプロジェクトでもOpenAPIからiosとGolangのコードを自動生成して使用したので、その辺はまた別の記事で紹介したいと思います。 本記事ではAPIの定義とAPIGatewayの生成に使用します。
環境
- SAM CLI 1.23.0
- Golang 1.16
SAMプロジェクトの準備
下記コマンドでプロジェクトを作成します。コマンド入力後、対話形式で必要な情報を入力していきます。
$ sam init ----------------------- Generating application: ----------------------- Name: sam-app Runtime: go1.x Dependency Manager: mod Application Template: hello-world Output Directory: .
ディレクトリ構成
生成されたプロジェクトを元に大枠を作成していきます。今回はGETとPOSTで呼び出す用のlambdaを2つ作成します。 変更後のディレクトリは下記のようになります。
├── get │ └── main.go ├── post │ └── main.go ├── go.mod ├── go.sum ├── openapi.yaml └── template.yaml
各lambda定義
次にlambdaの定義をしていきます。今回はシンプルにHelloWorldと返す関数と、送られてきたパラメーターを少し加工して返す関数を定義します。
// get/main.go package main import ( "encoding/json" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) type Response struct { Message string `json:"message"` } func handler() (events.APIGatewayProxyResponse, error) { body := Response{Message: "Hello World"} jsonBody, err := json.Marshal(body) if err != nil { panic(err) } return events.APIGatewayProxyResponse{ Body: string(jsonBody), StatusCode: 200, }, nil } func main() { lambda.Start(handler) }
// post/main.go package main import ( "encoding/json" "fmt" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) type Person struct { Name string `json:"name"` Age int `json:"age"` } type Response struct { Message string `json:"message"` } func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { var p Person b := []byte(request.Body) err := json.Unmarshal(b, &p) if err != nil { panic(err) } body := Response{Message: fmt.Sprintf("Hello %v : %v", p.Name, p.Age)} jsonBody, err := json.Marshal(body) if err != nil { panic(err) } return events.APIGatewayProxyResponse{ Body: string(jsonBody), StatusCode: 200, }, nil } func main() { lambda.Start(handler) }
APIの設計
lambdaの定義が終わったので本題のAPIの定義をしていきます。
template.yaml
# template.yaml AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > sam-app Sample SAM Template for sam-app Globals: Function: Timeout: 5 Runtime: go1.x Resources: ApiRole: Type: AWS::IAM::Role Properties: RoleName: api-execution-role AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - apigateway.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: api-execution-role-policy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - lambda:InvokeFunction Resource: - Fn::Sub: ${GetFunction.Arn} - Fn::Sub: ${PostFunction.Arn} Api: Type: AWS::Serverless::Api Properties: Name: "rest-api" StageName: dev DefinitionBody: Fn::Transform: Name: AWS::Include Parameters: Location: ./openapi.yaml # 参照するyamlファイルを指定 GetFunction: Type: AWS::Serverless::Function Properties: CodeUri: get/ Handler: get PostFunction: Type: AWS::Serverless::Function Properties: CodeUri: post/ Handler: post
APIGatewayに付与するロールとAPIGateway、各lambdaを定義しています。DefinitionBodyでopenapi.yaml
のディレクトリを指定することでopenapi.yaml
の定義を元にAPIGatewayを生成することができます。またFn::Transformを使用することで、参照したopenapi.yaml
内でCloudFormation固有の変数が使用可能になります。
openapi.yaml
# openapi.yaml openapi: 3.0.0 info: title: OpenAPI sam-app description: sam-appのAPI version: 1.0.0 paths: /hello: get: summary: GETサンプル description: GETのサンプルです responses: '200': description: 成功レスポンス content: application/json: schema: $ref: '#/components/schemas/Success' x-amazon-apigateway-integration: credentials: Fn::Sub: ${ApiRole.Arn} uri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetFunction.Arn}/invocations passthroughBehavior: when_no_templates httpMethod: POST type: aws_proxy post: summary: POSTサンプル description: POSTのサンプルです requestBody: required: true content: application/json: schema: type: object properties: name: description: 名前 type: string age: description: 年齢 type: integer format: int32 required: - name - age responses: '200': description: 成功レスポンス content: application/json: schema: $ref: '#/components/schemas/Success' x-amazon-apigateway-integration: credentials: Fn::Sub: ${ApiRole.Arn} uri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PostFunction.Arn}/invocations passthroughBehavior: when_no_templates httpMethod: POST type: aws_proxy components: schemas: Success: description: 成功の場合のレスポンス type: object properties: message: type: string required: - message
x-amazon-apigateway-integrationはOpenAPI の仕様に対するAPI Gatewayの拡張です。credentials
プロパティでtemplate.yaml
で定義したロール、uri
プロパティでそのエンドポイントに対応するlambdaを指定してます。
動作確認
上記の実装が完了したらビルドを実行します。
$ sam build
ビルドが完了したらAPIをローカルでホストします。下記コマンドでtemplate.yaml
とopenapi.yaml
を元にDockerコンテナを立てて、すべての関数をホストするローカル HTTP サーバーが作成されます!
$ sam local start-api Mounting GetFunction at http://127.0.0.1:3000/hello [GET] Mounting PostFunction at http://127.0.0.1:3000/hello [POST] You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template 2021-06-08 02:16:59 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
上記のようにエンドポイントが表示されたらcurlを叩いて繋ぎこみが完了しているか確認します。
$ curl --request GET --url http://127.0.0.1:3000/hello {"message": "Hello World"} $ curl --request POST --url http://127.0.0.1:3000/hello --data '{"name": "Taro", "age": 20}' {"message":"Hello Taro : 20"}
レスポンスが返って来てたらOKです!
デプロイ
ローカルでの動作確認ができたら最後にデプロイします。デプロイといっても下記コマンドだけで完了します。 コマンド入力後いくつかパラメーターの入力を求められますが全てデフォルト値でOKです!
$ sam deploy --capabilities CAPABILITY_NAMED_IAM --guided
作成したAPIのエンドポイントを叩いてレスポンスが返ってきたら全て完了です!
まとめ
今回はSAMとOpenAPIを組み合わせてサーバーレスなAPIを作成しました。 SAMとOpenAPIを活用することで簡単にAPIを作成でき、スキーマファーストな開発ができると思います! 次回はaws-sdk-go-v2を使ってS3に画像をアップロードするところまでを記事にしようかなと思ってます。
ほとんどが初めて触る技術ばかりだったので大変でしたが、その分かなり成長できたのではないかなと感じています! そして、バイセルでは新卒でもこのような機会を与えてもらえるチャンスがあります! ご興味のある方は是非ご連絡ください。バイセルテクノロジーズではエンジニアを募集しています。