暖簾に腕押し
私がAzure Functionsに提供してほしいと切に願っている機能の一つに、Search Bindings(仮称)があります。なお、私がこの機能実装のリクエストを出しました。
しかし、現時点ではこのような機能が提供される気配は全くなく、付帯するロジックを記述するしかありません。暖簾に腕押し、というやつです。
実際、このようなロジックを書くことすら億劫なのですが、嘆いていても仕方がないですので、javascriptからAzure Searchへ問い合わせを行うnpmライブラリazure-searchを利用し、レスポンスを返すところまで実装することにしました。
役割
クライアント : webAPIを利用し、HTTP Responseに含まれた検索結果を受け取ります。
Azure Functions : クライアントからHTTP Requestを受け取り、Azure Searchに問い合わせをし、結果をクライアントに返します。
Azure Search : クエリをFunctionsから受け取り、検索結果を返します。また、Indexerという機能を利用し、定期的にTable Storageからデータを同期します。
Table Storage : 検索対象となるデータがストックされています。Indexerによって、定期的にデータ参照されます。
データ構造
検索対象となるデータ構造は以下のようなものです。
1 | [ |
PartitionKeyおよびRowKeyはいずれもTable Storageで必須の項目です。(参照:Azure ストレージ テーブルの設計ガイド: スケーラブルな設計とハイパフォーマンスなテーブル)
Search側のスキーマ構造
インデックスmessageには、Table Storageに格納されているデータ構造を、ほぼそのまま持ってきています。Table Storageで利用していたPartitionKeyおよびRowKeyはここでは使いません。
- id Edm.String (key, retrievable, searchable)
- channel Edm.String (retrievable, filterable)
- body Edm.String (retrievable, searchable)
- author Edm,String (retrievable, filterable)
- visible Edm.Boolean (retrievable, filterable)
- timestamp Edm.Int64 (retrievable, filterable, sortable)
普通に実装
最初、以下のように実装しました。
1 | // function.json |
1 | // index.js |
察しの良い方なら気づいたかもしれませんが、このロジックは期待通りには動かず、502エラーを返してしまいます。
何がダメなのか
期待通りに動かない原因は、client.search(...)の結果を受け取る前にmodule.exports自体が処理を終えてしまうからです。
レスポンスらしいレスポンスを設定しないまま処理が終わってしまうので、502エラーを返す、ということです。
対応方法
結論から書くと、以下の2点を直すと良いです。
module.exportsを、Promiseをreturnするように変更する。function.jsonにて、http output bindingsのnameを$returnにする。(portalの場合、応答パラメータ名のところにある「関数の戻り値を使用する」をチェックする)
なおしてみる
なおした後の実装がこちら。
1 | // function.json |
1 | // index.js |
function.jsonでは、http output bindingsのnameが$returnとなっており、functionの戻り値をレスポンスに使う設定となっています。
そしてindex.jsではPromise.resolve(...).then(result => ...).catch(err => ...)の形式でSearchに問い合わせを行った後の処理をハンドリングするよう定義し、promiseそのものをreturnするロジックへと書き換えられました。
本当はドキュメントに書いておいて欲しかった、もしくは・・・
実はPromiseをreturnすることで解決できるという事について、公式ドキュメントには書かれていないようです(ソース。openになっているし、書こうとはしてる模様)。
今回の解決法は、上記issueを辿ってようやく見つけることができたものでした。
この手の手間をなくすためにも、Search Bindingsが欲しいな、と思うのでした。
まとめ
- Search Bindings欲しいですね。
2018-08-30 追記
node-azure-searchでPromise/thenを使った書き方では、検索結果のヒット件数を取得することができないという問題がありました。
1 | const AzureSearch = require('azure-search'); |
これはnode-azure-searchのindex.jsを修正することで、取得できるようになります。(ただしインターフェイスを破壊する変更です)
1 | @@ -492,7 +492,7 @@ module.exports = function (options) { |
利用する側は以下のようになります。
1 | const AzureSearch = require('azure-search'); |
破壊的な変更であるため、forkして利用しています。