Go+Gin+DynamoDBでWeb APIサーバを作る

仕事でGoを使うこととなりましたので、年明けから勉強しておりました。
一つの区切りとしてナンチャッテToDo Web APIサーバを作りましたので、GitHubで公開しておきます。

github.com

参考文献

Goは↓の本を読んでました。
他の言語と比較しながらGoの考え方やクセを細かく説明してくれるので、CやJavaの経験者であれば効率的に吸収できるかと思います。
Amazon CAPTCHA

GinはREADME、DynamoDBはAWSのドキュメントとQiita投稿で使い方を学びました。

github.com

dynamodb - Amazon Web Services - Go SDK

あえて aws-sdk-go で dynamoDB を使うときの基本操作 - Qiita

開発環境

Visual Studio Code(Windows)で構築しました。
↓の記事でコーディングからデバッグまでの一式できるようになるかと思います。
WindowsのVisual Studio CodeでGo言語の開発環境を作る - 素敵なおひげですね

構成

ユーザ登録、ログイン/ログアウト、ToDoの投稿と取得ができる簡単なWeb APIです。
Web APIフレームワークとしてGin、DBはDynamoDBを使っています。

Gin

Ginは高速さを売りとしているGoのWeb APIフレームワークです。
↓をビルド・実行するだけで、さくっとWeb APIサーバになります。

package main

import "gopkg.in/gin-gonic/gin.v1"

func main() {
    r := gin.Default()
    r.GET("/hello", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "Hello, World!",
        })
    })
    r.Run()
}

Goはnet/httpというHTTPサーバを標準搭載していますが、GinはAPIに特化していますのでそこは作りやすいようになっています。

  • 例えば、net/httpだと、リクエストbodyを文字列として読み込んでからデシリアライズ(アンマーシャル)する必要がありますが、
  • Ginでは、c.BindJSON(&request)だけでrequestへ値が詰められます。

DynamoDB

このAPIでは2つのDynamoDBテーブルにアクセスします。
テーブル名(とリージョン)はconfig.jsonで変更できます。

テーブル名 パーティションキー ソートキー
todo-user-table Id -
todo-todo-table Id UserId

GoからのDynamoDBのアクセスには素のaws-sdk-goを使っています。 なかなかツライです。

  • ソースコードを見ていただければわかるかと思いますが、DynamoDBへのCRUDでかなりの行数となっている上、ポインタとおまじないチックな手順が入り乱れています。
    • ExpressionAttributeNamesで項目名のプレースホルダ、ExpressionAttributeValuesで値のプレースホルダを指定したり、
    • map[string]*dynamodb.AttributeValue{ ":value": { S: aws.String(value),},},とかしんどいです。
  • 何らかのライブラリでラッピングしたくなる。

宿題

  • デプロイ
    • ローカルでサーバを実行し、DynamoDBのみAWSへアクセスしていました。
    • AWS ElasticBeanstalkにコマンドからデプロイできるようしたいところです。
  • テスト
    • 標準で揃っていますが、まだ手を出せてないです。

↓の本を読んで勉強します。
みんなのGo言語【現場で使える実践テクニック】 | 松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧 大輔, 鈴木健太, 稲葉貴洋 |本 | 通販 | Amazon

Lambda(C#)の環境変数にアクセスする

今回はLambdaの環境変数C#からアクセスしてみます。 接続するDBを環境毎に切り替える場合などに役立ちます。

AWS Lambda の新機能 – 環境変数とサーバーレスアプリケーションモデル (SAM) | Amazon Web Services ブログ (結構最近追加された機能だったのですね。)

Lambdaの実装

まずはLambdaの実装です。
といってもこれまでのC#プログラミングと同じで、以下のようにEnvironment.GetEnvironmentVariableメソッドでアクセスできます。

// 環境変数"Path"にアクセスする場合
var pathValue = Environment.GetEnvironmentVariable("Path");

今回は"ENV_NAME"という名前で設定された環境変数にコンストラクタからアクセスしています。

using System;
using System.Collections.Generic;
using System.Net;
using Amazon.Lambda.Core;
using Amazon.Lambda.APIGatewayEvents;

[assembly: LambdaSerializerAttribute(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace AWSServerless
{
    public class Functions
    {
        private readonly string envName;
        
        public Functions()
        {
            envName = Environment.GetEnvironmentVariable("ENV_NAME");
        }
        
        public APIGatewayProxyResponse Get(APIGatewayProxyRequest request, ILambdaContext context)
        {
            var response = new APIGatewayProxyResponse
            {
                StatusCode = (int)HttpStatusCode.OK,
                Body = $"This environment is {envName}",
                Headers = new Dictionary<string, string> { { "Content-Type", "text/plain" } }
            };

            return response;
        }
    }
}

Serverlessでデプロイ

AWSコンソールからでも設定可能ですが、前回同様Serverlessでデプロイしてみます。

ohke.hateblo.jp

serverless.templateのResources.Get.Properties.Environment.Variablesで、環境変数"ENV_NAME"に"test"という値で定義しています。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Transform" : "AWS::Serverless-2016-10-31",
  "Description" : "An AWS Serverless Application.",
  "Resources" : {
    "Get" : {
      "Type" : "AWS::Serverless::Function",
      "Properties": {
        "Handler": "AWSServerless::AWSServerless.Functions::Get",
        "Runtime": "dotnetcore1.0",
        "CodeUri": "",
        "MemorySize": 256,
        "Timeout": 30,
        "Role": null,
        "Policies": [ "AWSLambdaBasicExecutionRole" ],
        "Events": {
          "PutResource": {
            "Type": "Api",
            "Properties": {
              "Path": "/",
              "Method": "GET"
            }
          }
        },
        "Environment": {
          "Variables": {
            "ENV_NAME": "test"
          }
        }
      }
    }
  },
  "Outputs" : {
  }
}

これでAWS ExplorerからPublishして、AWSコンソールからデプロイしたLambdaを見ると環境変数が設定されていることを確認できます。 (Serverlessを使わない場合はこの画面から設定します。)

f:id:ohke:20170127223433j:plain

リリースしたURLにアクセスしてみると、"test"を取得できていることも確認できます。

f:id:ohke:20170127223539j:plain

Visual StudioのAWS Serverlessテンプレートを使ってLambdaの環境を作る

12月にAWS LambdaがC#でも使えるようになって、Serverlessでも1.4.0からC#用のテンプレートが提供されましたが、プロジェクトファイルは別で準備しないといけなかったり、ちょっと不便でした。

ところが、AWS SDKでServerless用のプロジェクトテンプレートが提供されていましたので、改めて使ってみました。

プロジェクトの作成

Visual Studioを立ち上げてプロジェクトの新規作成から「AWS Serverless Application(.NET Core)」を作成します(プロジェクト名はAWSServerlessとします)。

f:id:ohke:20170120002943j:plain:w450

肝はserverless.templateで、デプロイするLambdaの設定値やトリガとなるイベントなどを定義します。
サンプルではAPI GatewayでのGetリクエストをトリガとする1個のLambdaが定義されています。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Transform" : "AWS::Serverless-2016-10-31",
  "Description" : "An AWS Serverless Application.",

  "Resources" : {

    "Get" : {
      "Type" : "AWS::Serverless::Function",
      "Properties": {
        "Handler": "AWSServerless::AWSServerless.Functions::Get",
        "Runtime": "dotnetcore1.0",
        "CodeUri": "",
        "MemorySize": 256,
        "Timeout": 30,
        "Role": null,
        "Policies": [ "AWSLambdaBasicExecutionRole" ],
        "Events": {
          "PutResource": {
            "Type": "Api",
            "Properties": {
              "Path": "/",
              "Method": "GET"
            }
          }
        }
      }
    }

  },

  "Outputs" : {
  }
}

デプロイ

AWSServerlessプロジェクトを右クリックして、「Publish AWS Lambda...」をクリックするとデプロイ用の画面が開きます。

f:id:ohke:20170120003416j:plain:w450

試しにtest-stackと適当に決めてPublishすると、Visual Studio上でCloudFormationと同じようなウィンドウが表示され、たちまちS3へのアップロード、IAMの設定、LambdaとAPI Gatewayの作成とそれらの紐付けが行われます。

f:id:ohke:20170120003115j:plain

test-stackの作成が完了し、URLにブラウザからアクセスしてみると、Hello AWS Serverlessと表示されます。

AWSコンソールからtest-stackを作成できたことを確認できます。

f:id:ohke:20170120003200j:plain:w450

まとめ

サンプルそのままとは言え、Visual Studio内で完結してノーエラーで環境構築ができたので、個人的にはかなり驚きました。
CloudWatch、DynamoDB、Kinesisともそのうち連携させてみたいと思います。