🙌

AmplifyをRubyで楽しむことはできるのか?

2021/03/21に公開

はじめに

最近、WebアプリをつくのにAmplifyをさわっています。バックエンド側をさくっと作りたいときには確かにいいですね。

私的に何がいいと思ったかというと:[1]

  1. AmplifyのCLIの質問に答えていくだけで、APIGW>Cognito>Lambda>DynamoDBまでアプリ作成に典型的に必要なものは芋づる式に作ってくれて、さらにLambdaでNodeJSを選べばDynamoDBとAPIGW経由でCRUDする関数まで自動で生成してくれます。
  2. また、サービス間のIAMロールなんかも一緒に設定してくれるので権限周りも個別に設定する必要もありません

あれ? Rubyは?

ただ、「さくっと何かを作りたい時に言語としてRubyが選べるならとりあえずRubyを選んどく」くらいにはRubyistの私にとっては、Amplify CLIでRubyが選べないのはモチベーションが下がってしまいます。amplify function addで、これをみた瞬間、.NETに負けるなんて!とさらにへこみます。(Amplifyのバージョンは4.45.2

? Choose the runtime that you want to use: (Use arrow keys)
  .NET Core 3.1 
  Go 
  Java 
❯ NodeJS 
  Python 

Rubyを使えるようにしてみる

では、なんとかRubyを使えるようにしてみましょう。AmplifyがRubyをサポートしていない以上は、Amplifyに気づかれないようにRubyを使うしかありません。ひとまずは、ランタイムとしてNodeを使うふりをして後でRubyに置き換える作戦です。

amplify init

Webアプリ用なので、こんな感じのinitです。

$ amplify init
Note: It is recommended to run this command from the root of your app directory
? Enter a name for the project todo
? Enter a name for the environment dev
? Choose your default editor: Visual Studio Code
? Choose the type of app that you're building javascript
Please tell us about your project
? What javascript framework are you using none
? Source Directory Path:  src
? Distribution Directory Path: dist
? Build Command:  npm run-script build
? Start Command: npm run-script start
Using default provider  awscloudformation
? Select the authentication method you want to use: AWS profile

For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html

? Please choose the profile you want to use default

amplify api add

APIから作っていきます。ここから芋づる式にDynamoDBまで作成です。ここではランタイムはNodeを使うふりをします。

$ amplify api add
? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the project: todoAPI
? Provide a path (e.g., /book/{isbn}): /tasks
? Choose a Lambda source Create a new Lambda function
? Provide an AWS Lambda function name: todoFunc
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: CRUD function for DynamoDB (Integration with API Gateway)
? Choose a DynamoDB data source option Create a new DynamoDB table

Welcome to the NoSQL DynamoDB database wizard
This wizard asks you a series of questions to help determine how to set up your NoSQL database table.

? Please provide a friendly name for your resource that will be used to label this category in the project: todoDB
? Please provide table name: todoTable

You can now add columns to the table.

? What would you like to name this column: task-id
? Please choose the data type: string
? Would you like to add another column? No

Before you create the database, you must specify how items in your table are uniquely organized. You do this by specifying a primary key. The primary key uniquely identifies each item in the table so that no two items can have the same key. This can be an individual column, or a combination that includes a primary key and a sort key.

To learn more about primary keys, see:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.PrimaryKey

? Please choose partition key for the table: task-id
? Do you want to add a sort key to your table? No

You can optionally add global secondary indexes for this table. These are useful when you run queries defined in a different column than the primary key.
To learn more about indexes, see:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html#HowItWorks.CoreComponents.SecondaryIndexes

? Do you want to add global secondary indexes to your table? No
? Do you want to add a Lambda Trigger for your Table? No
Successfully added DynamoDb table locally

Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration

? Do you want to configure advanced settings? No
? Do you want to edit the local lambda function now? No
Successfully added resource todoFunc locally.

Next steps:
Check out sample function code generated in <project-dir>/amplify/backend/function/todoFunc/src
"amplify function build" builds all of your functions currently in the project
"amplify mock function <functionName>" runs your function locally
"amplify push" builds all of your local backend resources and provisions them in the cloud
"amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud
Succesfully added the Lambda function locally
? Restrict API access No
? Do you want to add another path? No
Successfully added resource todoAPI locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud

芋づるが長いのですが、ポイントは以下でしょうか

? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: CRUD function for DynamoDB (Integration with API Gateway)

ここは後でRubyのものに置き換えるつりなので、なんでもいい気もしますが、これを選んでおくことで、Amplify側が関数にDynamoDBにアクセスするロールを付与してくれるので、こうしておくと楽です

? Restrict API access No

認証は後でも足すことができるので、ひとまず余計なところでハマらないためにもこうしておきます

amplify push

ひとまずここでpushしておきます

$ amplify push
✔ Successfully pulled backend environment dev from the cloud.

Current Environment: dev

| Category | Resource name | Operation | Provider plugin   |
| -------- | ------------- | --------- | ----------------- |
| Storage  | todoDB        | Create    | awscloudformation |
| Function | todoFunc      | Create    | awscloudformation |
| Api      | todoAPI       | Create    | awscloudformation |

ランタイムをRubyに置き換える

さて、ここでこっそりランタイムとハンドラをRuby用に置き換えます。Amplifyに気づかれないように素早くおこないます。

$ aws lambda update-function-configuration --function-name todoFunc-dev --runtime ruby2.7 --handler lambda_function.lambda_handler
{
    "FunctionName": "todoFunc-dev",
    "FunctionArn": "arn:aws:lambda:us-east-2:578170637269:function:todoFunc-dev",
    "Runtime": "ruby2.7",
    "Role": "arn:aws:iam::578170637269:role/todoLambdaRolee9f33d88-dev",
    "Handler": "lambda_function.lambda_handler",
    "CodeSize": 11369236,
    "Description": "",
    "Timeout": 25,
    "MemorySize": 128,
    "LastModified": "2021-03-21T01:54:56.507+0000",
    "CodeSha256": "3avEYotMLxKa7lXTN4P0CUPACv4ds14mjePO2IEzKms=",
    "Version": "$LATEST",
    "Environment": {
        "Variables": {
            "ENV": "dev",
            "REGION": "us-east-2"
        }
    },
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "f89f9d07-cf82-4f68-a51e-ff2246962fe6",
    "State": "Active",
    "LastUpdateStatus": "Successful"
}

どうなったか

これで、FunctionはAmplifyの管理から外れましたので、今後はFunctionはAmplifyでは触らず、別で管理します

$ amplify status

Current Environment: dev

| Category | Resource name | Operation | Provider plugin   |
| -------- | ------------- | --------- | ----------------- |
| Storage  | todoDB        | No Change | awscloudformation |
| Function | todoFunc      | No Change | awscloudformation | ← 別管理
| Api      | todoAPI       | No Change | awscloudformation |

たとえば、先に設定したハンドラ名でメソッドをつくって

lambda_function.rb
require 'json'

def lambda_handler(event:, context:)
  { statusCode: 200, body: JSON.generate(event) }
end

こんなシェルでアップロードすれば動くと思います。

update.sh
#!/bin/bash

FUNCTION_NAME=todoFunc-dev

if [ $# -lt 1 ]; then
  echo "Usage: $0 code_file"
else
  zip lambda_function.zip $1 &&
  aws lambda update-function-code --function-name $FUNCTION_NAME --zip-file fileb://lambda_function.zip
fi
$ ./update.sh lambda_function.rb 

とりあえず動きました。

$ curl -s https://<api-id>.execute-api.us-east-2.amazonaws.com/dev/tasks | jq
{
  "resource": "/tasks",
  "path": "/tasks",
  "httpMethod": "GET",
  "headers": {
    "Accept": "*/*",
    :
    :

うまくいかなかったら

{
  "message": "Internal server error"
}

になったら

aws logs tail /aws/lambda/todoFunc-dev

でログを確認します。よくわからないけど、こんなハンドラが見つからない旨のエラーが出てたときがあったので

2021-03-21T02:20:35.729000+00:00 2021/03/21/[$LATEST]79ed2ec4cb494600bcb8db3e59b95c91 Critical exception from handler
2021-03-21T02:20:35.729000+00:00 2021/03/21/[$LATEST]79ed2ec4cb494600bcb8db3e59b95c91 {
  "errorMessage": "undefined method `lambda_handler' for #<LambdaHandler:0x00000000020f0cb0>",
  "errorType": "Function<NoMethodError>",
  "stackTrace": [

  ]
}

以下のラムダのGUIから、Edit > Save したら動くようになりました

さて、もうここまでくると、Amplifyでやる意味あるのかと半分くらい思いますが、これでRubyで進められそうです。

脚注
  1. 一方で、あくまで各設定はざっくりであり、細かいところはamplifyの外で個別に設定する必要がありそうです。 ↩︎

Discussion