Ruby 製サーバーレスフレームワーク Jets から DynamoDB アイテムの CRUD 操作を試す

## はじめに

Ruby でサーバーレスアプリケーションを実装できるフレームワーク、 [Jets](https://github.com/tongueroo/jets)。Rails ライクに使える、サーバーレスアプリケーションに入門しやすそうなフレームワークです。

日本国内の情報はまだまだ少ないですが、**個人的にはかなりアツいフレームワークだと考えています。** 先日以下のような検証記事を書きました。これからもっと検証を進めていく予定です。

[Ruby 製サーバーレスフレームワークの Jets を検証してみたら、Rails ライクに使えていい感じだった](https://qiita.com/gotchane/items/fd3c1e54f7bdaa4ab813)

そんな Jets ですが、データベースを MySQL や PostgreSQL といった RDB だけではなく、**Key-Value-Store である DynamoDB も使える**ようです。

この記事では、Jets のドキュメントに則って、DynamoDB をデータベースとして使う場合について検証してみました。

## 検証環境情報

* Mac OS X 10.13.6
* HomeBrew 1.8.6
* ruby 2.5.1
* Jets 1.4.5
* nodejs v9.11.2
* Java version 100

## 検証の概要

以下について検証します。

> ローカル環境において、DynamoDB を設定し、Jets アプリケーションの console から DynamoDB テーブルに CRUD 操作する

## 検証手順

### Jets アプリケーション新規作成

まず、サンプルとなる Jets アプリケーションを新規作成します。

```bash
jets new jets-dynamodb-sandbox
cd jets-dynamodb-sandbox
```

### dynamodb-local インストール・起動確認

次に、ローカル環境で DynamoDB を使えるよう、dynamodb-local をインストールします。

```bash
brew cask install dynamodb-local
```

インストールできたら、dynamodb-local を起動しましょう。成功すると、8000 番ポートで起動していることが確認できます。

```bash
dynamodb-local

Initializing DynamoDB Local with the following configuration:
Port:  8000
InMemory:  false
DbPath:  null
SharedDb:  false
shouldDelayTransientStatuses:  false
CorsParams:  *
```

### dynamodb-admin インストール・起動確認

dynamodb-local で作成したテーブルの内容を確認しやすくするため、dynamodb-local 用の GUI ツール、[dynamodb-admin](https://github.com/aaronshaf/dynamodb-admin) をインストールします。

```bash
npm install dynamodb-admin -g
```

dynamodb-admin のエンドポイントを環境変数で設定し、dynamodb-admin を起動します。

```bash
export DYNAMO_ENDPOINT=http://localhost:8000
dynamodb-admin

dynamodb-admin listening on http://localhost:8001 (alternatively http://0.0.0.0:8001)
```

http://localhost:8001 にアクセスすると、dynamodb-admin の画面が表示されます。

![c1189834.png](https://qiita-image-store.s3.amazonaws.com/0/222030/c8d54ebf-b120-3d75-1956-0c76072484b0.png)

### Jets アプリケーション DynamoDB 接続設定確認

dynamodb-local の設定ができたら、Jets アプリケーションにおける DynamoDB 接続設定を確認します。

`config/dynamodb.yml` をエディタで開きます。

```ruby:config/dynamodb.yml
development:
  table_namespace: <%= Jets.config.table_namespace %>
  # More examples:
  # table_namespace: demo-dev

  endpoint: http://localhost:8000 
  # comment out if want to test with real dynamodb
  # on AWS. You can also set the DYNAMODB_ENDPOINT environment variable.
  # You can also are actually deploying a development environment is to
  # change bin/server and export DYNAMODB_ENDPOINT=http://localhost:8000 at the top
  # there.
```

`development` 以下の `endpoint` が `http://localhost:8000` となっています。

今回はdynamodb-local をポート番号を変更せず、デフォルトのまま利用しているので、特に変更は不要です。

もしdynamodb-local のポート番号を変更して起動している場合は、変更したポート番号に修正してください。

### DynamoDB テーブル マイグレーションファイル作成

それでは Jets アプリケーション用に、DynamoDB テーブルのマイグレーションファイルを作成しましょう。

今回は `posts` というテーブルを作成します。

以下コマンドでマイグレーションファイルを作成します。

```bash
jets dynamodb:generate create_posts

Migration file created: ./dynamodb/migrate/20190105131708-create_posts_migration.rb.

To run:
  jets dynamodb:migrate ./dynamodb/migrate/20190105131708-create_posts_migration.rb
```

作成されたマイグレーションファイルは以下の通りです。ActiveRecord のマイグレーションファイルと似ていますね。

```ruby:dynamodb/migrate/20190105131708-create_posts_migration.rb
class CreatePostsMigration < Dynomite::Migration
  def up
    create_table :posts do |t|
      t.partition_key "id:string:hash" # required
      t.provisioned_throughput(5) # sets both read and write, defaults to 5 when not set

      # Instead of using partition_key and sort_key you can set the
      # key schema directly also
      # t.key_schema([
      #     {attribute_name: "id", :key_type=>"HASH"},
      #     {attribute_name: "created_at", :key_type=>"RANGE"}
      #   ])
      # t.attribute_definitions([
      #   {attribute_name: "id", attribute_type: "N"},
      #   {attribute_name: "created_at", attribute_type: "S"}
      # ])

      # other ways to set provisioned_throughput
      # t.provisioned_throughput(:read, 10)
      # t.provisioned_throughput(:write, 10)
      # t.provisioned_throughput(
      #   read_capacity_units: 5,
      #   write_capacity_units: 5
      # )
    end
  end
end
```

### マイグレーション実行

紹介されているコマンドをそのまま使い、マイグレーションを実行しましょう。

```
jets dynamodb:migrate ./dynamodb/migrate/20190105131708-create_posts_migration.rb

Running database migrations
DynamoDB Table: posts Status: ACTIVE
```

実行後、dynamodb-admin をリフレッシュしてみると、テーブルが作成されているのが確認できます。

![5d2966b1.png](https://qiita-image-store.s3.amazonaws.com/0/222030/669b7e51-3f4b-2f3a-9018-115f0d15f0bb.png)

### Post モデル作成

DynamoDB のテーブル、 `posts` に対応した `Post` モデルを作成します。

`Post` モデルの継承元クラスですが、今回は `ApplicationRecord` ではなく、　`Dynomite::Item` として作成します。

[Dynomite](https://github.com/tongueroo/dynomite) は、ActiveRecord ライクに DynamoDB を操作できるラッパーとなる gem です。

Dynomite は、Jets と同じ作者 [tongueroo](https://github.com/tongueroo) さんが開発しています。

```ruby:app/models/post.rb
class Post < Dynomite::Item
end
```

### DynamoDB テーブルへの CRUD 操作確認

ここまでで、最低限の設定は完了しました。

いよいよ Jets アプリケーション上で、DynamoDB テーブルにアイテムが作成できるか確認します。

Jets アプリケーションのコンソールを開きます。

```bash
jets console
```

#### Create

Jets アプリケーションのコンソール上で、Post モデルのアイテムを作成してみます。

```ruby
post = Post.new(id: "myid", title: "my title")
post.replace

{"id"=>"b83c4d4b6fd7af91a7f44bc8df7d17d2ac63a76a", :id=>"myid", :title=>"my title", "created\_at"=>"2019-01-05T04:32:15Z", "updated\_at"=>"2019-01-05T04:32:15Z"}
```

コマンド実行後、再度 dynamodb-admin の画面をリフレッシュして確認すると、テーブルにアイテムが作成されているのが確認できます。

![6bc28574.png](https://qiita-image-store.s3.amazonaws.com/0/222030/d38aa20a-02cb-f0eb-a82a-5b9dd7a3822b.png)

#### Read

作成したアイテムは、`find` メソッドで検索できます。

```ruby
post_found = Post.find('myid')

<Post:0x00007fc07120ec00 @attrs={"created_at"=>"2019-01-05T09:40:27Z", "id"=>"myid", "title"=>"hoge", "updated_at"=>019-01-05T09:40:27Z"}>
```

#### Update

作成したアイテムにおいて、アイテムの内容全体を更新する場合は、作成時と同様 `replace` メソッドを使います。

```ruby
post = Post.new(id: 'myid', title: 'title updated')
post.replace

{"id"=>"90f716a63f24c3099135c202184ec8b37f25e737", :id=>"myid", :title=>"title updated", "created_at"=>"2019-01-05T10:10:40Z", "updated_at"=>"2019-01-05T10:10:40Z"}
```

作成したアイテムに新たな属性を追加したい場合は、`attrs` メソッドで新たな属性を追加し、`replace` メソッドを実行します。

```ruby
post_updated = Post.find('myid')
post_updated.attrs(content: 'hogehoge')
post.replace

{"id"=>"90f716a63f24c3099135c202184ec8b37f25e737", :id=>"myid", :title=>"title updated", "created_at"=>"2019-01-05T10:10:40Z", "updated_at"=>"2019-01-05T10:10:40Z", "content"=>"hogehoge"}
```

#### Delete

アイテムを削除する場合は、`delete` メソッドを実行します。

```ruby
post_deleted = Post.delete('myid')

# <struct Aws::DynamoDB::Types::DeleteItemOutput attributes=nil, consumed_capacity=nil, item_collection_metrics=nil>
```

こちらで検証は以上です！

## おわりに

今まで DynamoDB といった KVS なデータストレージに触れる機会はあまりなかったのですが、Jets を使うと実際に動かしながら試すことができて良さそうだなと思いました。

DynamoDB への操作もまだまだ基本的な操作のみしかしていないので、動かしながら基本的な概念も確認していきたいと考えています。

また別途、Jets アプリケーションの画面から、DynamoDB テーブルへの操作も試してみたいと思います。

## 参考

* [Database DynamoDB - Jets Ruby Serverless Framework](http://rubyonjets.com/docs/database-dynamodb/)
* [Dynamodb Local Setup Walkthrough · tongueroo/jets Wiki · GitHub](https://github.com/tongueroo/jets/wiki/Dynamodb-Local-Setup-Walkthrough)
* [jets dynamodb:generate](https://github.com/tongueroo/jets/blob/master/docs/_reference/jets-dynamodb-generate.md)
* [AWS Black Belt Online Seminar 2017 Amazon DynamoDB
](https://www.slideshare.net/AmazonWebServicesJapan/20170809-black-belt-dynamodb)