結論
- 入力
- Lambda 関数の第一引数
event
は、各サービスからのpayloadが "配列" で引き渡される。
- Lambda 関数の第一引数
- 出力
-
return ...
で返した内容が、EventBridge Pipes のターゲットに引き渡される。
-
解説
何もしない = ソースもしくはフィルタから event
を受け取って、何もせずにターゲットへ event
を引き渡す Lambda 関数を基に解説します。
def lambda_handler(event, context):
print(event)
return event
export const handler = async (event, context) => {
console.log(event);
return event;
};
入力
Lambda 関数の第一引数 event
には、ソースもしくはフィルタからのpayloadが引き渡されます。第二引数 context
も使用可能です。
例えば Amazon SQS をソースに設定し、MessageBody を {"v":1}
としたデータをSQSキューに入れると print(event)
の結果は以下のようになります。
[
{
'messageId': '8c8b72a2-85f0-47be-bf9f-81edff5197e9',
'receiptHandle': '.....',
'body': '{"v":1}',
'attributes': {
'ApproximateReceiveCount': '1',
'SentTimestamp': '1672477371472',
'SenderId': 'AID................KM',
'ApproximateFirstReceiveTimestamp': '1672477371473'
},
'messageAttributes': {},
'md5OfMessageAttributes': None,
'md5OfBody': '455c0c10d425837001a72a84239ac362',
'eventSource': 'aws:sqs',
'eventSourceARN': 'arn:aws:sqs:REGION:............:SQS_NAME',
'awsRegion': 'REGION'
}
]
payloadはソースのサービスで異なりますが、EventBridge Pipes のターゲットにおける「サンプルイベント/イベントペイロード」で確認できるものとほぼ同じです。
"ほぼ" と表現した理由は event
には配列として渡されるからです。よって Lambda 関数内での参照は、例えば messageId であれば event[0]['messageId']
として参照することになります。
def lambda_handler(event, context):
print(event)
print(event[0]['messageId'])
return event
event
は配列であるため、複数のpayloadが引き渡されそうな気もしますが、今のところ複数payloadが入ってくる様子はありませんでした。例えばSQSに send-message-batch
で3つのデータを一度にキューへ入れても、3回個別にEnrichmentに指定された Lambda 関数が実行されました。
出力
return ...
で返した内容が、EventBridge Pipes のターゲットに引き渡されます。
そして "空" を return すると、ターゲットは実行されません。空とは {}
[]
""
になります。
以下のコードは、ターゲットを一切実行しない Lambda 関数です。
def lambda_handler(event, context):
return {}
以下のコードは、どのような入力があったとしても、ターゲットに {"foo": "bar"}
を引き渡す Lambda 関数です。
def lambda_handler(event, context):
return {'foo': 'bar'}
Implicit body data parsing の適用有無
Enrichment によるデータ処理をする場合、1点注意があるとすれば、Implicit body data parsing(暗黙のbody変換)です。
Enrichment を使わない場合はImplicit body data parsingが適用され、ターゲット側での入力トランスフォーマーで <$.body.foo>
といった形で参照できるようになりますが、Enrichment を使った場合はImplicit body data parsingは適用されません。
そのため Enrichment を使う場合は、入力トランスフォーマーがシンプルになるように、Enrichment の Lambda 関数内でフォーマットしきってからターゲットに引き渡す設計が良いでしょう。
私が考える Enrichment に指定する Lambda 関数の実装
ターゲットの入力トランスフォーマーを使わない、もしくはシンプルな指定にするためには、Enrichment に指定する Lambda 関数で出力フォーマットを確定させることになります。
アプローチとしては2つ考えられますが、私としては後者の「eventに付加する」実装が、デバックや今後のアップデートに追従できるのではないかと考えています。
※ "enrichment" という単語の趣旨にもあってそうですし。
例えば、Slack の chat.postMessage
への送信フォーマットは channel
と text
の二つを送ることになりますが、以下のような形です。
アプローチ1「完全に書き換える」
def lambda_handler(event, context):
payload = {
'channel': 'XXX',
'text': event[0]['body']
}
return payload
# => {
# 'channel': 'XXX',
# 'text': "STRING...."
#}
ターゲットの入力トランスフォーマーには、何も指定しない。 (出力自体が Slack に合っている)
アプローチ2「eventに付加する」
def lambda_handler(event, context):
event[0]['_enrichment'] = {
'slack': {
'channel': 'XXX',
'text': event[0]['body']
}
}
return event[0]
# => {
# (元々のevent),
# '_enrichment': {
# 'slack': {
# 'channel': 'XXX',
# 'text': 'text'
# }
# }
#}
{
"channel": <$._enrichment.slack.channel>,
"text": <$._enrichment.slack.text>
}
まとめ
Amazon EventBridge Pipes は、ターゲットにも AWS Lambda を指定できるため、わざわざ Enrichment で Lambda 関数で処理する理由が見当たらないという意見もありそうです。
私の好きな本「UNIX という考え方」には "一つのプログラムには一つの事をうまくやらせる" という定理が紹介されています。
ターゲットには AWS サービスだけでなく外部 API をコールする機能が備わっています。その実装を Lambda 関数内から追い出し「payload変換に集中する」という意味では、Enrichment に指定する Lambda 関数はシンプルにできるメリットがあるでしょう。
EoT