AWS Lambda(C#)からAuroraにつないでみる

Serverless Meetup Tokyo #4に参加してきたら

* AWS SAから「ServerlessにはAuroraは不向き」的な話をされた
* ENI作成時間が気になるなら、定期的にLambdaの暖機が必要

のような逆風にも負けずLambdaからAuroraに接続するアツい話を聞いて、無性にLambda(C#)からAuroraに接続したくなったので試してみました。

そう言えば「LambdaならC#よりnode.js/Pythonの方がオススメ」的な話も聞いたことがあるような...

# 開発環境
* Mac
* .Net Core 2.0.0 / .Net Core 1.0.5共存環境

本来Lambdaの作成には.Net Core 2.0.0は不要ですが
　・自分の環境は1.0と2.0の共存環境で
　・2.0.0になってdotnet restoreの振る舞いが変わった
ため、dotnetコマンドのバージョンが2.0.0の場合で記載します。

# 作業概要
1. .Net Coreのインストール
2. Auroraの作成
3. C#コード作成
4. デプロイ用パッケージの作成
5. Lambdaの実行

# 1. .Net Coreのインストール
MicrosoftのサイトからMac用をダウンロードしてインストールします。

[2.0.0](https://www.microsoft.com/net/core#macos)
[1.0.5 with SDK 1.0.4](https://github.com/dotnet/core/blob/master/release-notes/download-archive.md)

# 2. Auroraの作成
AWSマネジメントコンソールからAuroraを構築します。

※留意点

* VPC内からアクセスするため、Publicly Accessible：Noにします
* お試しなら、インスタンスタイプは一番料金が安いdb.t2.smallがオススメです

# 3. C#コード作成
適当な作業ディレクトリを作成し、その配下に.csファイルを作成します。
またAuroraに接続するために、下記値を設定します。

* クラスタエンドポイント
* ユーザ / パスワード
* データベース名

```MyFunction.cs
using Amazon.Lambda.Core;
using MySql.Data.MySqlClient;

namespace sample_aurora
{
    public class MyFunction
    {
        const string ConnectionString = "<<クラスタエンドポイント>>;user=<<ユーザ>>;password=<<パスワード>>;port=3306;database=<<データベース名>>;SslMode=None";

		[LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
        public string MyHandler(LambdaEvent lambdaEvent, ILambdaContext context)
		{
            string command = lambdaEvent.Command;
			LambdaLogger.Log("Command: " + command + "\n");
            switch(command){
                case "select":
                    Select();
                    break;
				case "insert":
					Insert();
					break;
				case "init":
					Init();
					break;
                default:
                    LambdaLogger.Log("Do nothing.\n");
                    break;
                    
            }
			return "Sample function was executed.";
		}

        private void Select()
        {
            MySqlConnection connection = new MySqlConnection(ConnectionString);
			connection.Open();

			MySqlCommand command = new MySqlCommand("select id from test_tbl;", connection);

            string message = "Data: ";

			using (MySqlDataReader reader = command.ExecuteReader())
			{
				while (reader.Read())
				{
					string row = $"{reader["id"]}";
                    message = message + "," + row;
				}
			}

            connection.Close();

            LambdaLogger.Log(message + "\n");
        }

		private void Insert()
		{
			MySqlConnection connection = new MySqlConnection(ConnectionString);
			connection.Open();

			MySqlCommand command = new MySqlCommand("insert into test_tbl values ();", connection);

            command.ExecuteNonQuery();

			connection.Close();

            LambdaLogger.Log("A record has been inserted.\n");
		}

		private void Init()
		{
			MySqlConnection connection = new MySqlConnection(ConnectionString);
			connection.Open();

			MySqlCommand command = new MySqlCommand("create table if not exists test_tbl (id int auto_increment primary key);", connection);

			command.ExecuteNonQuery();

			connection.Close();

            LambdaLogger.Log("A table has been created.\n");
		}
    }

	public class LambdaEvent
	{
		public string Command { get; set; }
	}
}
```

# 4. デプロイ用パッケージの作成

### 4-1. .csprojファイルの作成
同じ作業ディレクトリに.csprojファイルを作成します。

```sample_aurora.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp1.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.TestUtilities" Version="1.0.0" />
    <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="1.1.0" />
    <PackageReference Include="Amazon.Lambda.Core" Version="1.0.0" />
    <PackageReference Include="MySql.Data.Core" Version="7.0.4-IR-191" />
  </ItemGroup>
</Project>
```

### 4-2. デプロイパッケージの作成
作業ディレクトリ内で以下コマンドを実行します。

```bash
$ dotnet restore
$ dotnet publish --output publish
$ cp ~/.nuget/packages/system.data.sqlclient/4.1.0/runtimes/unix/lib/netstandard1.3/System.Data.SqlClient.dll publish/
$ cp ~/.nuget/packages/system.diagnostics.tracesource/4.0.0/runtimes/unix/lib/netstandard1.3/System.Diagnostics.TraceSource.dll pulish/
$ cd publish
$ zip ../sample_aurora.zip *
```
※留意点

* System.Data.SqlClient.dllとSystem.Diagnostics.TraceSource.dllはdotnet publishでは含まれなかったため、手動でコピーしています。(無いとLambda実行時にエラーになります)

### 4-3. Lamdbaのデプロイ
AWSマネジメントコンソールからLambdaを選んで、「sample_aurora.zip」をデプロイします。

* VPC対応にします
* Auroraに接続できるsecurity groupを指定します
* もしくは、指定したsubnet groupがaurora側のsecurity groupで許可されていれば、lamdbaで指定するsecurity grouopはダミーで構いません

# 5. Lambdaの実行
AWSマネジメントコンソールからLambdaを実行します。

### 5-1. テーブル作成
test eventに以下のjsonを指定してLambdaを実行します。

```json
{
  "Command": "init"
}
```
Log outputに以下出力されます。

```
Command: init
A table has been created.
```

### 5-2. Insert
test eventに以下のjsonを指定してLambdaを実行します。

```json
{
  "Command": "insert"
}
```
Log outputに以下出力されます。

```
Command: insert
A record has been inserted.
```

実行した回数分テーブルにレコードが追加されます。

### 5-3. Select
test eventに以下のjsonを指定してLambdaを実行します。

```json
{
  "Command": "select"
}
```
Log outputに以下出力されます。

```
Command: select
Data: ,1,2,3
```

# 雑感
案外簡単にC# LambdaからAuroraへ接続できました。