DynamoDB Query APIのパラメータを生成しましょ(プレースホルダをぶっ飛ばせ)
photo credit: Kurayba Micro Machine via photopin (license)
AWS のNoSQLデータベース DynamoDB のテーブルを Query する場合のパラメータを生成するWebアプリをつくりました。
DynamoDBでテーブルの項目を取りだすには Query API を使いますが、このパラメータがちょっと曲者なんですよね。
属性名が予約済みのキーワードにマッチしていると条件式などに直接記述できなくて「属性名のプレースホルダ」を定義して、読み替える必要があるのです。 さらに条件式では具体的な値(文字列や数値)が直接使えず「属性値のプレースホルダ」に変換しないといけません。
実際、慣れるとたいしたことではないのですけど最初はかなり悩みました。 今でも困ってる人はいるんじゃないかと思います。
DynamoDBはよくできたサービスですけど、これ(↑)って参入障壁かもしれない。 かつて悩んで解決したので、このようなソリューションを公開する責任があるのではないだろうか?と鼻息荒く意識は高く、以下に公開する次第ですー・・・
使い方
少しUIがしょぼいですが、徐々に改善するつもりです(ゆるして)。
左にチェックボックスがついているのは省略可能なパラメータ。最初はチェックが外れていますがチェックをつけるとサンプルで入力した内容が復活します。
各テキストボックスを編集すると即座に結果のパラメータが変化します(エラーがあると変化しないので気を付けてください)。
属性名にDynamoDBのキーワードが含まれていると属性名のプレースホルダが生成されるのを確認してみてください。
生成されたパラメータはDynamoDBのQuery APIの第一引数にそのまま指定できるJSONです。[Copy]ボタンでクリップボードにコピーできまーす。
- TableName は必須です。テーブル名を指定します。
- KeyConditionExpression も必須で、テーブルのキーに対する抽出条件を指定します。キー以外の属性は使えません。パーティションキーには、
=
しか使えません。レンジキーには、範囲指定の演算子、<
,<=
,=
,>=
,>
, そして、BETWEEN <s> AND <e>
が使えます。両者はAND条件で組み合わせます(ORは無理)。パーティションキーの条件は省略可能です。 - FilterExpression は、KeyConditionExpressionで抽出した結果に対してフィルターをかける機能。DynamoDBではSCANした量によって課金されますからFilterで絞っても対象外です。でもFilterはAWS側で行われるので、インターネットの転送量は絞ったほうが少なくなります。まあややこしいところですが、そのうち理解できますよ(多分)。
- Limit は抽出する行数を指定します。
- ProjectionExpression はSQLのSELECT句。取り出したい属性値の名称をカンマ区切りで指定します。省略すれば属性値全体を取り出します。属性名がキーワードかどうかを気にする必要はありません。
関連サイト
npm aws-node-util でSQL的な構文でDynamoDBを操作する機能を提供していますが、この度こちらにこのページで使っているWebAppを公開してるのでよろしくね。
AWS SAMテンプレートでREST APIの複数メソッドをひとつのLambdaに統合するには?
photo credit: Andreas Komodromos Urban Nights - Hudson Yards, New York City via photopin (license)
ホントちょっとしたことなんですが、SAMのHelloWorldのチュートリアルをやっただけではハッキリわからなくて、少しだけ試行錯誤が必要でしたので書いておきます。
API Gateway REST APIの複数メソッド(GET,PUT,POST,DELETEなど)を、ひとつのLambda関数に統合(Lambdaプロキシ統合)する場合の AWS SAMのテンプレートの書き方です。
API Gatewayのコンソールでは、各メソッドの設定で、同じLambda関数を指定するだけですが、じゃあSAMのテンプレートではどう書くのでしょうか?ってところです。
SAMテンプレートの書き方(例)
以下のYamlでリソースパス /hello
に対する GETリクエストとPUTリクエストを、Lambda関数 HelloWorldFunction
に統合しています。
sam-app/template.yaml:
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > sam-app Sample SAM Template for sam-app # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 3 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: Events: #ココ↓と HelloWorldGet: Type: Api Properties: Path: /hello Method: get #ココ↓ HelloWorldPut: Type: Api Properties: Path: /hello Method: put
一般化すると、AWS::Serverless::Function
のProperties/Events
以下にメソッド単位で別々のApiとして宣言すればよいのです。
APIのリソースパスが同じでもメソッドが別なら複数のApi定義が必要ということですね。
SAMテンプレートでは独立したREST APIを宣言しているのではなくて、あくまでもサーバーレス関数の定義の一部なのでこうなるのでしょう(多分)。
今となっては上の定義が理解できていますが、当初「Events 以下のエントリがAPIのリソースパスを一意に表している」と思い込んでいて、Methodを配列にしてみたりして失敗しました。
Lambdaプロキシ統合のパラメータで分岐する
Lambda関数側では、引数のeventのhttpMethod
にHTTPメソッドが文字列で格納されていますので、これによって分岐します。
exports.lambdaHandler = async (event) => { try { const response = { statusCode: 200 }; switch(event.httpMethod) { case "PUT": await put(JSON.parse(event.body)); break; case "GET": await get(event.queryStringParameters); break; } return response; } catch (err) { console.log(err); return err; } };
リクエストパラメータは、同じくeventのqueryStringParameter
やbody
に格納されています。
queryStringParameter
はObjectですがbodyは文字列(JSON)なので気を付けましょう。
これらパラメータの使い方はメソッドの設計に依存するので一概に「こうしましょう」とは言えません。
他にも pathParameters
やheaders
を利用することがあるかもしれませんね。
Lambdaプロキシ統合されたLambda関数の入力形式については、以下の公式ページに説明があります。 実際に動かしてみて把握する必要はあるかもしれませんよ。
あとがき
普通はメソッドごとにLambda関数を分けることが多いかもしれません。 そのほうがLambdaでの分岐が不要で多少はパフォーマンスが良いはずですし。
しかし、各メソッド間で同じデータモデルを扱うなど共通処理が必要な場合は一つにまとめるとスッキリすると思います。
共通処理についてはLambda Layersを使うのも一つの解です。 ここでは、「とりあえず手軽にやるには?」という観点でサクッと調べた結果を書いておきました。
AWS SAMがAWS CLIを見失う?ならPATH設定を要チェック!
photo credit: dolbinator1000 Over The Hill via photopin (license)
LinuxにAWS CLIをインストールしてコンソールから普通に使えていたのに、SAMでサーバーレスアプリのパッケージを作成しようとすると「AWS CLIが見つからない」というエラーが出て失敗しました。ビルドできるのになんでやねんと。
どうやら、AWS CLIのPATH設定と、AWS SAMのPATH検索方法の行き違いみたいなことで発生した問題のようでした。
実行環境
$ sam --version SAM CLI, version 0.22.0) $ $ aws --version aws-cli/1.16.258 Python/3.6.5 Linux/4.19.78-2-lts botocore/1.12.248 $ $ uname -srm Linux 4.19.78-2-lts x86_64 $
sam package でエラー
sam package
でパッケージを作ろうとするとエラーを履いて失敗しました。
$ sam package --s3-bucket <bucket-name> --output-template-file packaged.yaml Error: Cannot find AWS CLI installation, was looking at executables with names: ['aws'] $
エラーメッセージは「AWS CLIのインストールが見つからない。コマンド 名 aws
を探していましたが・・・」みたいな感じ。
いやしかし、AWS CLI はインストールしており、コンソールから普通に使えている状態。以下のようにPATHも通っていますよ。
$ which aws /home/<user>/.local/bin/aws
ただ、気になったのは aws
がホームディレクトリ以下の自宅の床下みたいな所 にあるということ。
どうやら、pipで自分だけ使えるようにインストールしたら、こうなるようです。
全ユーザーが使えるようにしたらこうはならないらしいです。
とりあえずの対策
Cannot find AWS CLI installation, was looking at executables with names
エラーメッセージで検索すると、Close済のGitのIssueがヒット。 文言が少し違っていますが、同じ問題のように思えます(↓これ)。
「とりあえず /usr/bin
や /usr/local/bin
に上記 aws
コマンドを置けばOK」とありますので、シンボリックリンクを張ってみると、、、
あっさり解決・・・
[~] $ cd /usr/local/bin [/usr/local/bin] $ sudo ln -S aws /home/<user>/.local/bin/aws [/usr/local/bin] $ cd ~/sam-app [~/sam-app] $ sam package --s3-bucket my-sam-app-bucket --output-template-file \ packaged.yaml Successfully packaged artifacts and wrote output template to file packaged.yaml. Execute the following command to deploy the packaged template aws cloudformation deploy --template-file /home/<user>/..../packaged.yaml --stack-name <YOUR STACK NAME>
しかしこれ、やっぱり aws
コマンドの場所が気持ち悪い。
街の通り(/usr/local/bin
)から自宅の床下(~/.local/bin
)に抜け穴を掘ってるみたいです。
正しい解決法
さらにIssueを読み進めると、ヒントがもひとつ書いてあった。
SAMはaws
のありかを自前でチェックしているらしいのです。
なんでそんなことしてるのかは知りませんが、そのおかげで PATH に含まれてる ~
を正しく解釈できないらしい。
ほら言わんこっちゃない。なんで自前でやっちゃうかな。本来それはOSに任せるべきなのでは?
とか言ってても仕方がない。
とにかくそういうことらしいので、自分の .bashrc を確認すると、・・・
.bashrc
export PATH="~/.local/bin:$PATH"
普通に ~
を使っていましたね。
結局このせいでAWS SAMはAWS CLIを見つけられず、インストールされていないなんて言っていたわけです。
以下のように ~
を使わないように修正すれば問題解決。
上に書いた /usr/local/bin
からのシンボリックリンクも不要です。
export PATH="/home/<user>/.local/bin:$PATH"
結構時間がかかって解決しました。割と大きめの落とし穴にハマった気分でした。
AWS SAM/CloudFormationのテンプレートでDynamoDBテーブル名をLambdaの環境変数に設定するには?
photo credit: SurfaceWarriors 190711-N-WI365-2119 via photopin (license)
AWS SAM/CloudFormationのテンプレートで定義されるDynamoDBのテーブル名を、同じくテンプレート内に定義したLambda関数の環境変数に設定する方法です。
技術評論社
売り上げランキング: 1,406
はじめに
SAM/CloudFormationのテンプレートで、テーブル名を指定せずにDynamoDBのテーブルリソースを定義すると、サーバーレスアプリの初回デプロイ時に自動的に命名されます。
このテーブル名には、SAMのアプリ名やリソース名に加えて、デプロイ時にハッシュみたいな一意なIDが使われますので、Lambdaの実装時には不明です。
テンプレートでは、DynamoDBのテーブルリソースを参照することで、まだ作成されていないテーブル名をLambdaの環境変数に設定できます。 Lambda関数では、テーブル名を環境変数から取得すればOKですね。
ということで、その書き方を以下で説明いたします。
テンプレートでLambdaの環境変数を設定する
テンプレートでLambda関数の環境変数を宣言するには、リソースの Properties/Environment/Variables
に、 <環境変数名>: <環境変数の値>
と記述します。
例ではリソースTypeが AWS::Serverless::Function となっていますが、AWS::Lambda::Function でも同じはず。
template.yaml
Resources: #サーバーレス関数の定義(API Gateway REST API+Lambda Function) MyFunction: Type: AWS::Serverless::Function Properties: Environment: #環境変数の宣言 Variables: <環境変数名>: <環境変数の値>
環境変数にDynamoDBテーブル名を設定する
Lambdaの環境変数にDynamoDBのテーブル名を設定するには、<環境変数の値>
に !Ref <DyanamoDBリソース名>
とします。
※ DynamoDBリソースでテーブル名をProperties/TableName
で明示的に指定していても同様に参照可能です。
template.yaml
Resources: #サーバーレス関数の定義(API Gateway REST API+Lambda Function) MyFunction: Type: AWS::Serverless::Function Properties: Environment: Variables: #↓DynamoDBテーブルのテーブル名を参照 TableName: !Ref DynamoDBTable ・ ・ ・ #DynamoDBテーブルリソースの宣言 DynamoDBTable: Type: AWS::DynamoDB::Table Properties: ・ ・ ・
デプロイするとLambda関数の設定で、こう(↓)なります。
おまけ:Lambda関数実行時に環境変数を参照する
Node.js の Lambda で実行時に<環境変数の値>
を参照するには process.env["<環境変数名>"]
とします。
const tableName = process.env["TableName"];
Pythonならば、import os
でosモジュールをインポートして os.environ['<環境変数名>']
です。
import os tableName = os.environ['TableName'];
参考リンク
Git BashやMSYS2の行頭で、Tab押しちゃって固まっちゃった時の対処法
photo credit: verchmarco Close-up of the On / Off Button and Laptop keyboard via photopin (license)
少なくとも、ひと月に1記事は書こうとしていたのですが、2週間前に交通事故に遭っちゃいまして、1週寝込んでさらに1週、痛みと後遺障害の予感に怯えて過ごして全く書けず。気づけば9月最終日。慌てて小ネタを出しておきます。
ということで、コンソールでたまに頭を悩ませる困ったことの解消法。
環境によるかも知れませんが、 Git Bash (Git for Windows 同梱の MSYS2) の行頭(何も入力していない状態の入力待ち)で Tab を入力してしまうと、結構長時間無反応になっちゃいます。
ハングしているわけではないので、実はしばらく待つと戻ってきます(特に2回目以降はキャッシュされているのか、それなりに早く戻ってきますね)。 Bash のコマンド入力補完機能がPATH上にある全コマンドを列挙していると思っていますが・・・。
これ、即刻復帰させるには、CTRL+Cを押してからコンソールのウィンドウサイズを変えればOKなんですよ。
なんでそうなるのかは全くわかっていませんが・・・(そのうち調べておきます)。