まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

2021/09

QR コード生成サービスを作ってソースコードごと公開してみました:
https://dotnsf.github.io/qrgenerator/

2021091203



同じようなコンセプトのサービスは世に多くあることを知っていますが、自分が作ったものは以下のような特徴を持ってます。便利に感じた方はぜひ使ってみてください。
  • (静的コンテンツだけで構成される)Github ページで公開している。バックエンドサービスが使えない形で公開しているので、実は裏で URL を集めている、といった心配は無用
  • 単に情報を QR コード化するだけのサービスではなく、考えつく限りの以下のようなカスタマイズを可能にしている

カスタマイズできる内容は以下の通りです:
・内容(テキスト、URLなど ここはカスタマイズできて当然)
誤り訂正レベル(デフォルトは M(約15%))
・生成する画像のサイズ(デフォルトは 512x512)
・前景色(デフォルト値は黒)
・背景色(デフォルト値は白)
・画面中央にロゴのように含める画像(デフォルトなし 誤り訂正レベルに応じて自動拡大縮小)


たとえばこんな感じにマンホールマップの URL と画像を設定して「生成」をクリックすると、
2021091201


指定された通りの QR コードがこんな感じで作成されて、
2021091202


「画像ダウンロード」をクリックすると、生成された QR コードを画像としてダウンロードできます:
1631428093716

細かい仕様としては、生成された QR コード部分をクリックすると情報テキストがクリップボードにコピーされます。上の例だと "https://manholemap.juge.me/" がクリップボードにコピーされるので、ブラウザのアドレス欄にペーストするだけでマンホールマップに遷移できるようになる、というわけです。


カスタマイズできる内容のうち、誤り訂正レベルについては自分も知らなかったので解説を加えておきます。

QR コードは紙やビニールといった「クシャクシャになったりして、そもそも印刷内容が平らに見えない可能性がある媒体に印刷して使う」可能性があることを考慮した仕様になっています。それが誤り訂正レベルです。QR コード自体を冗長化する形で作成して、画面の一部が読み取り不可な状態になっていても含まれている情報を認識できるようにするためのものです。

これが誤り訂正レベルで、QR コードでは4段階(H(約30%), Q(約25%), M(約15%), L(約7%))用意されています。H だと全体の30%が読み取り不可になっていても大丈夫、という意味ですが、QR コード自体が他よりも細かなものになります。ちなみに以下はどちらも "https://manholemap.juge.me/" の QR コードですが、左は誤り訂正レベル H 、右は Q です。白黒の細かさの違いがわかると思います:
Q


そのため最終的な印字サイズが小さいと読み取り自体が難しくなったりします。どの誤り訂正レベルを選ぶかは(真ん中にロゴ画像を含めるかどうかにも関わりますが)試行錯誤しながら読み取れそうなものを作りながら選ぶ必要があると思っています。

ちなみにソースコードはこちらです。裏で URL を保存してたりしない証拠としてこちらも公開しておきます:
https://github.com/dotnsf/qrgenerator


よかったら使ってください。


IBM Cloud から提供されている IDaaS サービスである AppID を使っています。AWS の Cognito や Auth0 のようなユーザーログインの(オンラインサインアップやオンラインパスワード変更、多要素認証なども含めた)機能全般をまとめて提供するサービスです。IBM Cloud のライトアカウントを使って無料枠内で利用することも可能です。単なるログイン機能程度であれば自分で実装してもいいのですが、オンラインサインアップや多要素認証まで考慮すると実装は面倒だし、(実在しないアドレスを使うこともできますが)メールアドレス含めて管理するのは個人情報管理の観点からも、特に試験的/実験的な段階では可能であれば避けたいという運用側の事情もあるため、こういったマネージドサービスを使うメリットは大きいと考えています。事実、自分は公私でよく使っています。
2021082902


で、この AppID を使ってサービス開発をしている中で「ユーザーを無効化したい」という意見をいただきました。実験的な意味合いのあるサービスを作って、ある特定の期間だけはそのサービスを使ってもらい、その期間が完了した後は使わせたくない。でもユーザーを削除してしまうと、何かの際に再度ログインしてもらうこともできなくなったり、再作成しても ID のデータ紐付けが切れてしまってたまったデータとの関連が切れてしまうのでそれは都合が悪い。そのため「いったん無効化してログインできないようにしたい」という、運用上ごもっともな意見でした。

「ユーザーの無効化ね。まあ機能的に用意されてるんだろうな」と楽観視していたのですが、AppID のダッシュボードを調べてみると、どうも「ダッシュボード画面からはユーザーの削除はできるが無効化はできなそう」でした。おっと、これはまずいぞ。。。
2021082903


このユーザー無効化を実現するためのワークアラウンドとして考えたのが「パスワードの強制変更」でした。要は管理者の権限で該当ユーザーのパスワードを強制的に変更することで事実上ログインできなくしてしまう、という方法です。アカウントは残るのでデータやその紐付けも消えることはなく、再度初期パスワードに変更すれば、万が一の時にもう一度ログインしてもらうこともできるようになる、と考えました。

で、じゃあダッシュボードからどのようにパスワードを強制変更するのかというと・・・実はパスワードの強制変更もダッシュボードからできるわけではありません。2021年8月現在、AppID ダッシュボードからはユーザーを削除することはできますが、ユーザーを無効化することも、パスワードを強制変更することもできません。

ただ、AppID SDK にはパスワードを強制変更する API がありました(文字通りの「無効化」は SDK からでもできなさそう・・):
https://www.npmjs.com/package/ibmcloud-appid

2021082901
(↑このページ内の "Set User New Password" 欄参照)


つまり AppID SDK のこの setUserNewPassword 関数を使って自分でプログラムを作れば、ユーザーのパスワードを強制的に変更すること自体は実現できそうでした。それによって実質的な無効化も可能にできるのでは・・と考え、挑戦してみました。以下はその成果の共有です。


【サンプルソースコードの紹介】
AppID のユーザーパスワードを変更するサンプルの Node.js アプリケーションを作って、ソースコードを公開しました:
https://github.com/dotnsf/appid_changepassword

このリポジトリを git clone するかダウンロード&展開してください。

使い方は後述しますが、まずは依存ライブラリをまとめてインストールしておいてください(この手順はソースコードを用意した後に1回だけ行ってください):
$ cd appid_changepassword

$ npm install

このフォルダには4つの JavaScript コードファイルが含まれていますが、それぞれ以下のような役割です(create_users_to_appid.js とか、何気に便利なツールだと自負してます):
ファイル役割
app.jsApp ID の動作確認用ウェブアプリケーション。AppID にログイン&ログアウトするだけ
settings.js動作設定用ファイル
create_users_to_appid.jsCSV ファイルから AppID のユーザーをまとめて作成する CLI アプリ
change_password.jsユーザーIDと新パスワードを指定して、パスワードを強制変更する CLI アプリ


パスワードの変更処理を行うのは change_password.js ですが、app.js や create_users_to_appid.js も含めて、この3つのファイルを動かすためにはあらかじめ settings.js に AppID の接続情報を記載しておく必要があります。以下、一通りの使い方を紹介します。


【サンプルソースコードを動かすための事前設定項目】
このアプリを動かすには、何はともあれ App ID のインスタンスが必要です。IBM Cloud にログインしてダッシュボードから App ID を選択してリソースインスタンスを作成してください。その際に「ライト」プランを選択すると 1000 ユーザー&(一ヶ月)1000 ログインまで無料で利用することが可能です(ログインアカウントがライトアカウントの場合は、ライトプランしか選択できません):
2021082904


作成した AppID インスタンスの「サービス資格情報」から(必要であれば1つ作成した上で)サービス資格情報を選択して、資格情報の内容を確認します:
2021082905


資格情報の中は以下のような JSON フォーマットのテキストになっています:
{
  "apikey": "xxxxx",
  "clientId": "xxxxxxxxxxxxxxxxxxx",
  "secret": "xxxxxxxxxxxxxxxxxxx",
  "tenantId": "xxxxx",
  "profilesUrl": "https://jp-tok.appid.cloud.ibm.com",
    :
    :
}

この中の以下5つの情報が必要です:
・apikey
・secret
・clientId
・tenantId
・region(profilesUrl の "https://" と ".appid.cloud.ibm.com" の間の文字列(上例だと "jp-tok"))

これら5つの値を見つけたら、settings.js 内の該当する変数値として入力し、保存します:
//. IBM App ID
exports.region = '(region の値)';
exports.tenantId = '(tenantId の値)';
exports.apiKey = '(apikey の値)';
exports.secret = '(secret の値)';
exports.clientId = '(clientId の値)';

exports.redirectUri = 'http://localhost:8080/appid/callback';
exports.oauthServerUrl = 'https://' + exports.region + '.appid.cloud.ibm.com/oauth/v4/' + exports.tenantId;

準備の最後に App ID のコールバック URL を登録します。今回のアプリケーションはローカルホストで動かす想定をしていて、その場合はローカルホストで動かす際の URL を事前に AppID に登録しておく必要があります。

IBM Cloud ダッシュボードで作成した App ID を開き、「認証の管理」、「認証設定」を選択して、「Web リダイレクト URL の追加」から "http://localhost:8080/appid/callback" を追加します(ローカルホストで動かすのではなく、公開アドレスで動かす場合はその URL を使って "http(s)://ホスト名/appid/callback" を追加してください):
2021082906


これでアプリケーションを動かす準備ができました。


【サンプルソースコードを使ってユーザーを登録】
このブログエントリの目的は「AppID のユーザーパスワードを変更する方法の紹介」です。そのため実際にログインできるユーザーを使って動作確認する必要がありますが、まずはそのユーザーを登録する必要があり、そのためのツールを紹介します。 既に AppID にユーザーが登録されていて、そのユーザーのパスワードを変更してもいい場合、ここは無視しても構いません。

まずは登録するユーザーを以下のようなフォーマットの CSV ファイルで用意します(newusers.csv 参照):
ユーザー表示名,ユーザーID,ユーザーパスワード
ユーザー表示名,ユーザーID,ユーザーパスワード
    :
    :


1列目のユーザー表示名はログインしたユーザーをアプリケーション内で表示する際に表示される文字列です。AppID の場合、ここはなぜか「8文字以上」という制約があるので、8文字以上の表示名を指定します。

2列目のユーザーIDは「メールアドレス形式のログインID」です。App ID の設定によってはオンラインでパスワードリセットを行ったりすることも可能で、その場合のメール送信先にもなるものです(オンラインパスワードリセットを無効にして運用する場合は実在しないメールアドレスでも構いません)。

3列目のユーザーパスワードは、2列目の ID でログインするユーザーのログイン時に指定するパスワードです。 この3列を一組とした行を必要なユーザーぶんだけ用意します。 なおサンプルで newusers.csv というファイルが含まれていて、その内容は以下のような2行になっています(以下、このファイルを使って紹介しますが、自分でこのファイルに相当する内容のファイルを用意した場合は、そのファイル名に置き換えて読み進めてください):
User0001,user0001@mydomain.com,password1
User0002,user0002@mydomain.com,password2

ではこのファイルを使って AppID にユーザーを登録します。コマンドの最後に CSV ファイル名を指定して、以下のように実行します:
$ node create_users_to_appid newusers.csv

画面に色々出力された後にプロンプトに戻ります。成功していると CSV ファイル内で指定されたユーザーが AppID に登録されているはずです。ダッシュボードから「クラウド・ディレクトリー」、「ユーザー」を選択し、CSV ファイルで指定したユーザーが追加されていることを確認します:
2021082907


検証用のユーザーが AppID に登録できました。まずはこのユーザー ID(と CSV ファイルで指定したパスワード)でログインできるか一度確認しておきます。


【サンプルソースコードを使って登録したユーザーでログイン確認】
では今登録したユーザーが登録したパスワードでログインできるかどうかを確認するため、サンプルのウェブアプリケーションを起動します:
$ node app

App ID のメッセージが数行表示された後に "server starting on 8080 ..." と表示されれば起動完了です。ウェブブラウザを開いて http://localhost:8080/ にアクセスします。すると(ログイン前なので) AppID のログイン画面に転送され、以下のような画面が表示されます:
2021082901


この Email 欄と Password 欄にそれぞれユーザーのメールアドレスとパスワードを入力して "Sign in" ボタンをクリックします。先程 CSV ファイルで作成したユーザーのメールアドレスとパスワード(例えば user0001@mydomain.com と password1)を入力してみます:
2021082902


正しく入力されている状態で "Sign in" ボタンをクリックするとログイン処理が成功し、画面が遷移して以下のような画面に切り替わります。ユーザーの ID、表示名、メールアドレスが表示されています。アドレスはログイン前に指定したものと同じ http://localhost:8080/ ですが、ログインが完了しているのでログイン画面には転送されず、この画面が表示されています。"Logout" ボタンでログアウトできます:
2021082903


ログアウトするとログイン画面に戻ります。試しにもう1人のユーザー情報(ID: user0002@mydomain.com, パスワード: password2)でもログインしてみます:
2021082904


User0002 でもログインできました。CSV ファイルから作成したユーザーで正しくログインできる所まで確認できました:
2021082905


ここまで確認できたらいったんサンプルウェブアプリケーションを終了しておきます。"node app" を実行した画面で Ctrl+C を押してアプリケーションを強制終了させておきます。


【サンプルソースコードを使ってユーザーのパスワードを変更する】
さていよいよ本ブログエントリの本題となる「ユーザーパスワードの強制変更」の箇所です。change_password.js ファイルを使って以下のように実行します:
$ node change_password user0001@mydomain.com Password1

実行時に指定するパラメータは2つあります。1つ目がパスワードを変更したいユーザーのログインID(メールアドレス)、2つ目が変更後のパスワードです(つまり上の例だと user0001@mydomain.com ユーザーのパスワードを Password1 に変更します)。旧パスワードを指定せずに強制的に変更する、という点に注意してください。実行すると色々画面に表示されますが、これも処理が完了するとプロンプトに戻ります。

パスワード変更が正しく行われたかどうかを再度サンプルウェブアプリケーションで確認します:
$ node app

ウェブブラウザで https://localhost:8080/ にアクセスしてログイン画面に移動したら、最初は変更前のパスワード(password1)を指定して user0001@mydomain.com でログインを試みてください。先程はログインできましたが、今はパスワードが変更されているのでログインできないはずです(メールアドレスとパスワードが一致しない、という旨のエラーメッセージが表示されます):
2021082906


改めて変更後のパスワードを指定してログインすると、今度はログインが成功してログイン後の画面が表示されます:
2021082907


これで AppID SDK を使ってログインパスワードを強制的に変更することができることが確認できました。

ちなみにソースコード上でのこのパスワード変更処理箇所はこのようになっています:
var SelfServiceManager = require( 'ibmcloud-appid' ).SelfServiceManager;

  :
  :

selfServiceManager.setUserNewPassword( uuid, password, "en", null, null ).then( function( user ){
  resolve( user );
}).catch( function( err ){
  reject( err );
});

  :
  :


ibmcloud-appid ライブラリを呼び出して SelfServiceManager をインスタンス化し、(省略した部分で)アクセストークンを取得してメールアドレスで指定したユーザーの ID(上記コード上では uuid)を特定します。特定できたら selfServiceManager.setUserNewPassword( uuid, password, "en", null, null ); を実行してパスワードを変更しています(password が新しいパスワード)。3番目のパラメータは言語情報らしいですが現在使われていないらしく、既定値の "en" を指定しています。このあたり、詳しくは SDK の説明もご覧ください。


ともあれ、これで当初の目的である「App ID ユーザーの無効化」に近いオペレーションが実現できそうでした。めでたしめでたし。



Bonsai Elasticsearch はその名前の通り、高速多機能な検索エンジンである Elasticsearch による検索機能をダッシュボード機能も含めてマネージドサービスとして提供するプロバイダーです。日本語の情報が少ないせいか、技術記事含めて情報自体を見ることがあまりありませんでした。偉そうに言ってますが、自分も以下の件で興味を持つまでは存じ上げていませんでした:
2021083002


Bonsai Elasticsearch は Salesforce から提供されている PaaS 環境である herokuサードパーティサービスの1つでもあります。heroku 自体も無料で使える利用枠が提供されていて、また Bonsai Elasticsearch にも無料枠があるため、heroku を経由してサービスを申し込むと「検索エンジンの使えるアプリケーションを無料で開発・運用できる環境」を手に入れることができます。世に多くのクラウド環境はありますが、Elasticsearch が無料で使えるアプリケーション・サーバー環境、という時点でかなり珍しいといえます:
2021083003


一方で、現実的に日本人を対象とするアプリケーションを考えると、検索機能は日本語検索ができなければあまり実用的とはいえません。英語のように単語と単語の間に明確なスペースが入って区切られるわけではない日本語は、テキスト内の単語と単語の区切りを見つける時点でかなりハードルが高く、そこから更に検索インデックスを作る必要があるためです。ここまでの機能がサポートされていないと検索エンジンとしては使いにくいのでした。 例えば自分で自分の(手元の)PC やサーバーに Elasticsearch をインストールした場合であれば、自分で更に日本語形態素解析機能を追加でインストールすればいいのですが、クラウドのマネージドサービスとして提供されている場合、そのような権限をもらえないことも多いため、サービスとして提供されている機能の中に多言語対応(日本語対応)が含まれていないといけない、という高めのハードルが設定されているのでした。

そんな中で見つけたこの Bonsai Elasticsearch 。ネットの日本語情報が少ないということは、日本語が使えないということかな・・・ と勝手に想像していました。 が、Bonsai のドキュメントを調べてみると Bonsai Elasticsearch に始めから導入されているプラグイン一覧の中に日本語形態素解析である "Kuromoji Analyzer" の文字を見つけました。あれ?これはもしかして期待できるやつ?:
https://docs.bonsai.io/article/135-plugins-on-bonsai

2021083001


というわけで、実際に Bonsai Elasticsearch で日本語検索ができるかどうかを調べてみました。結論としてはどうやら使えそう(!!!)だと思ってます。以下はその際の記録です。


【調査に使ったシナリオ】
最初に迷ったのは「何を調べれば日本語検索ができるといえそうか」でした。日本語データを入れて、日本語で検索して、日本語のそれっぽいデータが返ってきたらいいのか?? というと、そうともいえないし・・・

で、今回は Elastic 社の日本語エンジニアリングブログから提供されていた『Elasticsearchで日本語のサジェストの機能を実装する』というエントリで紹介されていた方法が Bonsai Elasticsearch でできるかどうかを調べました。詳細はリンク先を確認していただきたいのですが、大まかな内容としては kuromoji を使う前提で日本語でのサジェスト機能を実装するためのインデックスおよびマッピングを作成し、日本語のデータを入れた上で検索して結果を見る、というものです。ここで紹介されているのは入力ミスまでを考慮した曖昧な検索を行うという内容で、この結果が期待通りになればそれはもう大丈夫でしょ、という判断です。

ちなみに同ページで紹介されている内容自体が日本語サジェストを実現するための考え方なども紹介されていて非常に有用でした。その上で、ここで紹介されていることと同じ内容が Bonsai Elasticsearch に対して行っても同じ結果になるか(Bonsai Elasticsearch で日本語形態素解析が使えれば同じ結果になるはず)、を試してみました:
https://www.elastic.co/jp/blog/implementing-japanese-autocomplete-suggestions-in-elasticsearch


【Bonsai Elasticsearch の準備】
まずは Bonsai Elasticsearch のインスタンスを準備します。自分の場合は heroku 経由でインスタンスを作成したので、その場合の手順を紹介します。

まず heroku で無料アカウントを作成し、クレジットカードを登録しておきます(無料版の Bonsai Elasticsearch を使いますが、そのためには heroku アカウントにクレジットカードの登録が必要です)。改めてブラウザで heroku にログインし、(必要であれば)アプリケーションを1つ作成した上でそのアプリケーションにアドオンとして紐付ける形で Bonsai Elasticsearch を追加します。アプリケーションを選択して、"Overview" タブから "Configure Add-ons" を選択します:
2021083004


アドオンを選択する画面で検索ボックスに "Bonsai" と入力すると "Bonsai Elasticsearch" が見つかるのでこれを選択します:
2021083005


こんな感じの確認画面が表示されたら、無料プランの "Sandbox -  Free" が選択されていることを確認して(これ以外は有料です) "Submit Order Form" ボタンをクリックします:
2021083006


正しく処理されると、アプリケーションのアドオン一覧に "Bonsai Elasticsearch" が追加されます:
2021083007


追加された Bonsai Elasticsearch にアクセスするための接続 URL を確認します。同アプリケーションの "Settings" タブから "Reveal Config Vars" ボタンをクリックします:
2021083008


すると、このアプリケーションの動作時に設定される環境変数の一覧が表示されます。さきほど、Bonsai Elasticsearch を追加した際のオペレーションで "BONSAI_URL" という環境変数が設定されているはずです:
2021083009


ここでコピーした BONSAI_URL の値は、このようなフォーマットのテキストになっているはずです:
https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443

この値をこの後のインデックス作成やクエリー実行時に使うことになります。何度も使うことになるのでコピペできるよう、どこかに退避しておきましょう。

ここまで完了すれば Bonsai Elasticsearch の準備は環境です。


【Bonsai Elasticsearch に日本語インデックスと日本語データを作成して検索】
ここまでの準備ができれば Elastic 社の日本語エンジニアリングブログで紹介されていた、この内容を実際に試すことができるようになります:
https://www.elastic.co/jp/blog/implementing-japanese-autocomplete-suggestions-in-elasticsearch

ただ具体的なコマンド入力を考慮すると、このままだと少しわかりにくいため、この内容がもう少し試しやすくなるようなファイルやコマンド集(なんなら実行結果も含まれてますw)を作って公開しました。単に挙動や結果だけを確認したい人はこちらをダウンロードして使ってください:
https://github.com/dotnsf/bonsai_elasticsearch


以下に curl を使った具体的な手順とともに紹介します。日本語エンジニアリングブログによると、最初に kuromoji を使った日本語インデックスを作成する必要があります。上述でダウンロードしたファイルの中に含まれている my_suggest.json が日本語インデックスとマッピングを構成するファイルなので、以下の curl コマンドを実行してこれを Bonsai Elasticsearch に PUT します:
$ curl -XPUT https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest -d @my_suggest.json -H 'Content-Type: application/json'

https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443 部分は先述の BONSAI_URL の値に置き換えて指定してください。以下同様)

次に検索対象となる日本語データをまとめてバルクアップロードします。データファイルは japanese.json です(厳密には JSON フォーマットではなく、複数の JSON が繋がっているフォーマットです。そのため後述のコマンドで特殊な Content-Type を指定する必要があります)。このファイルを以下の curl コマンドで Bonsai Elasticsearch に POST します:
$ curl -XPOST https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_bulk --data-binary @japanese.json -H 'Content-Type: application/x-ndjson'

(Content-Type の指定値が 'application/json' ではなく、'application/x-ndjson' となっている点に注意してください)

これで Bonsai Elasticsearch に日本語インデックスと日本語データが格納されました。これらが正しく日本語で検索できるかどうか(このブログの本来の目的でいえば、5種類の曖昧な日本語検索をしても同じ結果になるかどうか)を確認してみます。5種類の検索クエリーを5つのファイル(query1.json, query2.json, ..., query5.json)で用意したので、5回に分けてそれぞれを実行し、その結果を result1.json, result2.json, ..., result5.json に格納します:
$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query1.json -H 'Content-Type: application/json' > result1.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query2.json -H 'Content-Type: application/json' > result2.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query3.json -H 'Content-Type: application/json' > result3.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query4.json -H 'Content-Type: application/json' > result4.json

$ curl -XGET https://username:password@xxxxxxxx.us-east-1.bonsaisearch.net:443/my_suggest/_search -d @query5.json -H 'Content-Type: application/json' > result5.json

ちなみにこれらの検索の内容ですが、「日本」という文字を多く含む日本語データがある程度格納されている状況下で、query1.json では「日本」を、query2.json では「にほn」を、query3.json では「にhん」を、query4.json では「にっほん」を、そして query5.json では「日本ん」を検索します。このように入力ミスまで考慮した曖昧な検索をしても同じ検索結果になるようなインデックスとマッピングを作る、という内容でした。実際に result1.json から result5.json まで見ると、検索結果はどれも一致しているはずです。


・・・というわけで、無料の Bonsai Elasticsearch (と導入済みの kuromoji)を使って日本語インデックスで日本語データを期待通りに検索できることがわかりました。こりゃ、いいモン見つけたかも!



IBM Cloud から提供されているマネージド NoSQL データベースである IBM Cloudant をよく使っています。最大の魅力は無料アカウントでも容量 1GB まで使えるストレージで、かつデータが分散管理されているという安全性です。仕様が曖昧な状態からアジャイルに作り始める際は(テーブル定義などを意識する必要がないこともあって)むしろ NoSQL データベースの方が便利だったりもします。

この IBM Cloudant を Node.js プログラムから使う場合のライブラリとして長く @cloudant/cloudant を使ってきましたが、2021/12/31 を以って End of Support を迎えることになり、現在は deprecated なライブラリ、という扱いになっています。2021年8月の今は「まだ使えるけど、もうすぐ使えなくなるよ」って所でしょうか。可能であれば、これから新たに作り始める時には利用を避けたほうが安全だと思います。

で、その後継ライブラリとして公開されているのが @ibm-cloud/cloudant (IBM Cloudant Node.js SDK)です。IBM Cloudant だけでなく、そのベースとなっている Apache CouchDB に対してももちろん使うことができるものです。現在 @cloudant/cloudant ライブラリを使っているアプリケーションはなるべく今年中にこの新しい @ibm-cloud/cloudant に移植することをおすすめします。

・・・と言うのは簡単ですが、実際この @ibm-cloud/cloudant は @cloudant/cloudant と比べてどのくらい似ていて/同じで、移植はどの程度簡単/難しいのでしょう? というわけで、まずは @ibm-cloud/cloudant を使ってみることにしました。


【サンプルソースコード】
以下で紹介するサンプルアプリケーションのソースコードを github で公開しています:
https://github.com/dotnsf/cloudant-node-sdk_sample


【接続方法の決定】
@ibm-cloud/cloudant を使って Cloudant(CouchDB) にアクセスする場合の接続方法には3通りあります:
(1)IAM
(2)COUCHDB_SESSION
(3)BASIC


(1)の IAM は IBM Cloudant を IAM 接続サポート形式で作成した場合に利用できる方法です。公式ドキュメントでも「この方法が使える場合はこの方法で」と紹介されています。

(2)の COUCHDB_SESSION を使った方法でも接続が可能です。

(3)の BASIC はいわゆる「ベーシック認証」です。この3つの中では「この方法しか使えない場合の選択肢」と紹介されています。CouchDB を自分でインストールして使う場合など、この方法で接続する前提のセットアップをしているとこの方法でしか接続できないことになります。もちろん Cloudant でもベーシック認証をサポートした形式で作成している場合は利用可能です。

(1)、(2)、(3)のいずれの方法でアプリケーションとデータベースを接続するかを決めておく必要があります。正確には @ibm-cloud/cloudant は「環境変数を設定して接続する」のですが、どの方法で接続するかによって、設定が必要になる環境変数が変わる点に注意が必要です。


【Node.js から接続する際に必要な環境変数】
@ibm-cloud/cloudant を使って Cloudant/CouchDB に接続する際に設定が必要な環境変数は以下です(横のカッコ付き数字は、上述の(1)、(2)、(3)のどの方法を使った時に必要な環境変数か、を表しています)。また変数名の頭の "CLOUDANT" 部分は別の値でも構いませんが、同じ文字列に統一して設定する必要があります:

変数意味
CLOUDANT_AUTH_TYPE上述の接続方法。"IAM"(1), "COUCHDB_SESSION"(2), "BASIC"(3) のいずれか。デフォルト値は "IAM"
CLOUDANT_URLデータベースの URL((1)、(2)、(3))
CLOUDANT_APIKEYAPI キーの値((1))
CLOUDANT_USERNAMEユーザー名((2)、(3))
CLOUDANT_PASSWORDパスワード((2)、(3))


環境に合わせてこれらの値を用意して実際に接続してみます。


【Node.js から Cloudant に接続】
(1)IAM 接続を行う場合は、上述の表より以下の環境変数を設定します:
process.env['CLOUDANT_AUTH_TYPE'] = 'IAM'; //(デフォルト値なので設定しなくてもよい)
process.env['CLOUDANT_APIKEY'] = 'xxxxx'; //(API Key の値)
process.env['CLOUDANT_URL'] = 'https://xxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud'; //(Cloudant のURL)

そして @ibm-cloud/cloudant ライブラリを読み込んで、以下のコードを実行します:
var { CloudantV1 } = require( '@ibm-cloud/cloudant' );

var client = CloudantV1.newInstance( { serviceName: 'CLOUDANT' } ); 

このコード実行が成功すると、Cloudant に接続したインスタンスが client という変数に格納され、実際のデータの CRUD 処理が可能になります。

なお上記コードの serviceName 値として 'CLOUDANT' を指定していますが、この部分は変更可能です。ただ変更する場合は環境変数として指定した変数名の頭の CLOUDANT 部分をここで指定する値と同じものに変更してください。

(2)COUCHDB_SESSION 接続を行う場合は、上述の表より以下の環境変数を設定します:
process.env['CLOUDANT_AUTH_TYPE'] = 'COUCHDB_SESSION';
process.env['CLOUDANT_USERNAME'] = 'username'; //(ユーザー名の値)
process.env['CLOUDANT_PASSWORD'] = 'password'; //(パスワードの値)
process.env['CLOUDANT_URL'] = 'https://xxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud'; //(Cloudant/CouchDB のURL)

そして @ibm-cloud/cloudant ライブラリを読み込んで、以下のコードを実行します:
var { CloudantV1 } = require( '@ibm-cloud/cloudant' );

var client = CloudantV1.newInstance( { serviceName: 'CLOUDANT' } ); 

(3)BASIC 接続を行う場合は、上述の表より以下の環境変数を設定します:
process.env['CLOUDANT_AUTH_TYPE'] = 'BASIC';
process.env['CLOUDANT_USERNAME'] = 'username'; //(ユーザー名の値)
process.env['CLOUDANT_PASSWORD'] = 'password'; //(パスワードの値)
process.env['CLOUDANT_URL'] = 'http://xxx.xxx.xxx.xxx:5984'; //(Cloudant/CouchDB のURL)

そして @ibm-cloud/cloudant ライブラリを読み込んで、以下のコードを実行します:
var { CloudantV1 } = require( '@ibm-cloud/cloudant' );

var client = CloudantV1.newInstance( { serviceName: 'CLOUDANT', disableSslVerification: true } ); 

なお(CouchDB の場合に多いと想像していますが) SSL 接続が不要の場合は上述のように接続時のパラメータに disableSslVerification: true を追加してください。

参考までに、@cloudant/cloudant の場合は、
var Cloudantlib = require( '@cloudant/cloudant' );
var cloudant = Cloudantlib( { account: "username", password: "password", url: 'https://xxxxxxx-bluemix.cloudantnosqldb.appdomain.cloud' } );
var db = cloudant.db.use( "dbname" );

といった感じでデータベースまでを取得した上で、
db.get( "id", function( err, result ){
  if( err ){
    console.log( err );
  }else{
    console.log( result );
  }
});

のようにして特定データを取得したりしていましたが、この @ibm-cloud/cloudant ではまだデータベースが特定されていない点にご注意ください。


【接続後の CRUD 処理の例】
では接続後の Cloudant クライアントを使って、IBM Cloudant のデータを読み書きしてみましょう。まず対象データベースと id がわかっている特定データを取得してみます。@ibm-cloud/cloudant では以下のような getDocument() メソッドを使うコードとなります:
client.getDocument( { db: "dbname", docId: "id" } ).then( function( result ){
  console.log( result );
}).catch( function( err ){
  console.log( err );
});

client.getDocument() を実行し、そのパラメータ内でデータベースと id を指定する、という方式になります。この点からして @cloudant/cloudant とは異なってますね。

また特定データベースの全データをまとめて取得(@cloudant/cloudant だと db.list() )する場合は以下のような postAllDocs() メソッドを使うコードです:
client.postAllDocs( { db: "dbname", includeDocs: true } ).then( function( result ){
  console.log( result );
}).catch( function( err ){
  console.log( err );
});

データを一件追加する場合は以下のような postDocument() メソッドを使うコードになります:
client.postDocument( { db: "dbname", document: { name: "Kimura" } } ).then( function( result ){
  console.log( result );
}).catch( function( err ){
  console.log( err );
});

以上、あくまでいくつかのメソッドを紹介しただけですが、全般的に行うオペレーションとメソッド名の関係がわかりやすく整頓されているように感じて、比較的慣れやすいのではないかと感じています。



IBM Cloud から提供されている Knative ベースのフルマネージドランタイム環境 "Code Engine" を使って cron ライクなスケジュールジョブを実現してみました。


Code Engine については以前にこの環境を使って、コンテナ化されたウェブアプリケーションをデプロイして使う、という内容を紹介したことがあります。準備手順もこの中で紹介していたので、後述の内容に興味を持っていただき、実際に試してみようと感じていただけた場合はこちらのページで紹介する内容も参照し、環境の準備を行っておいてください:
IBM Cloud の新サーバーレス環境 Code Engine を試してみる


今回は上述のリンク先で紹介しているようなウェブアプリケーションではなく、単発実行のスタンドアロンアプリケーション(コマンドラインから指定して実行し、処理が終了したら終わり。GUI なし)を Code Engine で実行できるように設定する方法を紹介します。あわせてこのスタンドアロンアプリを手動で実行するのではなく、cron ライクなフォーマットで実行タイミングをあらかじめ指定しておき、そのタイミングで自動実行させる方法も紹介します。


【ibmcloud CLI のセットアップ】
まず準備段階として ibmcloud CLI をセットアップする必要があります。上述の Code Engine 利用時はコンテナ化されたウェブアプリケーションを Code Engine 上にデプロイする手順を紹介しましたが、この内容であればリンク先のように IBM Cloud のダッシュボード画面を利用した GUI 操作だけで行うことができました。が、今回の内容は現時点のダッシュボード画面からだけでは設定できない内容が含まれており、一部の操作を CLI で行う必要があります(2021年8月8日時点)。そのための ibmcloud CLI のインストールおよびセットアップが必須となります。

ibmcloud CLI のインストールは以下のリンク先を参照し、お使いのシステム(Windows, Linux, MacOS)に合わせたインストールを行ってください:
IBM Cloud CLI の概説


ibmcloud CLI のインストールに続いてセットアップを行います。以下 Windows のコマンドプロンプト環境を使った手順を紹介しますが、基本的にはすべてのシステムでほぼ同様の手順を実行可能です。

まずはコマンドプロンプト画面やターミナルを起動し、ibmcloud CLI を使って IBM Cloud にログインします:
> ibmcloud login -u (ログインID) -g (リソース名:通常は default)

ibmcloud CLI で Code Engine のリソースを操作するには Code Engine 用のプラグインをインストールする必要があります。すでにインストール済みの場合は飛ばしてかまいませんが、まだプラグインをインストールしていなかったり、自信がない場合はログイン後に以下のコマンドを実行して Code Engine プラグインをインストールしてください:
> ibmcloud plugin install code-engine

これで ibmcloud CLI のセットアップは完了しました。


【IBM Code Engine に登録するジョブアプリケーションコンテナを開発】
次に Code Engine にスケジュールジョブとして登録するアプリケーションを開発します。

これはもちろんどのようなアプリケーションでもいいですが、今回は最終的に実行スケジュールを指定して「1時間おきに実行」といった実行を指示することになります。そのためウェブアプリケーションのような常駐型のアプリケーションではなく、実行してそのまま終了するスタンドアロン型のアプリケーションを用意します。アプリケーション自体はどのような言語で作られていてもかまいませんが、x86_64 の Linux で実行できるものを用意してください。

このアプリケーションはご自身で用意していただいてもかまいませんが、一応サンプルを用意・公開しました。以下ではこのアプリケーションを Code Engine 上でスケジュール実行する手順を紹介します:
https://github.com/dotnsf/cejob_sample


Node.js を使ったシンプルなアプリケーションです。メインファイルの内容は以下の実質1行だけで、「実行した瞬間のタイムスタンプを ISO フォーマットで標準出力に表示」するというだけのシンプルなものです:
//. app.js
console.log( ( new Date() ).toISOString() );

Code Engine は Knative ベースということもあり、アプリケーションはコンテナイメージ化されている必要があります。 このアプリケーションをコンテナ化するために、以下のような Dockerfile を用意しています(特にポートを EXPOSE することもなく、単に "$ node app.js" を実行しているだけの Dockerfile です):
# https://nodejs.org/ja/docs/guides/nodejs-docker-webapp/

# base image
FROM node:12-alpine

# working directory
WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

CMD ["node", "app.js"]

作業として必須ではありませんが、自分でもコンテナイメージを作ってみたい場合は docker をインストールして、docker hub にアカウントを作成後に以下のコマンドを実行してイメージをビルドし、docker hub にイメージをプッシュすることで外部から利用可能なコンテナイメージが用意できます(yourname の部分は自分の docker hub アカウント名に置き換えてください):
> docker login

> docker build -t yourname/cejob .

> docker push yourname/cejob


このイメージは私がすでにビルドして公開済みなので、自分でビルドしなくても使える状態になっています:
https://hub.docker.com/r/dotnsf/cejob


以下はこのイメージを使って Code Engine 上で実行できるようにする方法を紹介しますが、自分でビルドしたイメージを使う場合は "dotnsf/cejob" 部分を自分のコンテナイメージ名(上述の "yourname/cejob" 部分)に置き換えて捜査してください。


【IBM Code Engine にジョブを登録】
ここからは実際に Code Engine を使った操作を行います。まずはスケジュール実行させるジョブアプリケーションを Code Engine に登録します。

ウェブブラウザで IBM Cloud にログインします。Code Engine を使うにはライトアカウントでなく、スタンダート以上のアカウントでログインする必要がある点に注意してください。ログイン後の画面で「リソースの作成」ボタンをクリックします:
2021081501


検索バーに "Code Engine" と入力して、Code Engine を選択します:
2021081502


Code Engine に登録するアプリケーションの準備方法を選択します。(docker 環境や docker hub アカウントの準備がなく)ソースコードのみ手元にある場合は右側の「ソース・コードから始める」を選択することで自動的にイメージをビルドして利用することもできますが、今回はすでにイメージが公開されているので、左側の「コンテナー・イメージの実行」の「作成の開始」ボタンを選択します。なお右側の「ソース・コードから始める」の「作成の開始」を選択した場合、コンテナイメージを作ってプライベートリポジトリを利用することになりますが、その際の CPU やメモリ利用量は課金の対象となりますのでご注意ください(既にイメージがある場合は左側から行うことを推奨します):
2021081503


ここからは実際に稼働させるアプリケーションを指定していきます。まずはアプリケーションの種類。以前に紹介したウェブアプリケーションでは「アプリケーション」を選択しましたが、今回のように1回実行して(常駐するでも、待ち受けるでもなく)そのまま終了するアプリケーションの場合は「ジョブ」を選択します。続けてアプリケーションの名前(下図では "mycejob" )を入力します:
2021081504


そのまま下にスクロールして続きを入力します。Code Engine のアプリケーションはプロジェクトという単位でまとめて管理します。すでにあるプロジェクトを選んで使っても(そのプロジェクト内で管理しても)かまいませんが、初めて使う場合はプロジェクトを新規に作成する必要があります。作成する場合は「プロジェクトの作成」ボタンをクリックします:
2021081505


画面右に表示されるダイアログ内で、プロジェクトのサーバーロケーション(下図では「東京」)、プロジェクト名(下図では "my_ce_project")、そしてリソースグループ(下図では「default」、これは選択肢がない場合が多いと思います)を選択して、最後に「作成」ボタンをクリックします:
2021081506


作成したプロジェクトがセレクトボックスに含まれているので選択します。 続けて実行コードを指定します。繰り返しになりますが、今回はソースコードからではなく既存コンテナを使って実行するので「コンテナー・イメージ」を選択し、そのイメージに docker hub 上の dotnsf/cejob を使う場合は "docker.io/dotnsf/cejob" と入力します(自分のイメージを使う場合は該当部分を変更してください):
2021081507


そのまま下にスクロールして更に続きを入力します。ランタイム設定内のインスタンス・リソースでアプリケーションを実行する際の1インスタンスに割り当てられるマシンスペックを指定します(ここで指定するスペックが課金に影響します)。今回は超シンプルなアプリなので最小スペック(vCPU = 0.125 個、メモリ = 0.25 GB)を選択します。他に実行時に環境変数を指定する必要がある場合はここで指定します。最後に右下の「作成」をクリックします:
2021081508


これで Code Engine 上にジョブを作成することができました:
2021081501


この画面を下にスクロールすると、手動でこのジョブを実行することができます。「ジョブの実行依頼」ボタンをクリックして、一度実行させてみます:
2021081502


実行時のインスタンス数を1にして「ジョブの実行依頼」ボタンをクリックします:
2021081503


するとジョブが実行されます(正確にはジョブの実行依頼が実行されます)。この画面が表示された直後はステータスが「待機中」と表示されていて、まだ実行そのものがされていないことを示しています:
2021081504


しばらく待つと(Knative によって)ジョブが実行され、エラーなく正しく実行されていると「成功」ステータスに変わります:
2021081505


先ほどのジョブ一覧画面(ジョブ実行依頼をした画面)に戻ると、実行されたジョブが一覧に含まれる形で表示されていることを確認できます:
2021081506


ここまでの作業で Code Engine 上にスタンドアロンアプリケーションを登録して、手動実行できるようになりました。これだけでもクラウドに実行環境を用意することなく、アプリケーションジョブを(Pay per Use で)実行できる環境が用意できたことになります。この時点でかなり便利な環境ができました。


【IBM CodeEngine に登録したジョブをスケジュール実行する】
本ブログの目的がここです。クラウド上に用意されたアプリケーションジョブを手動実行ではなく、スケジュール実行できるようにします。なお、ここから先の手順は現状 IBM Cloud の GUI ダッシュボードからは行うことができず、CLI で操作する必要があります。

まずは上述の ibmcloud CLI セットアップの手順が済んで、"ibmcloud login" までが完了していることを確認してください。

ジョブにスケジュールを割り当てるには、まず操作する Code Engine プロジェクト(上の例だと "my_ce_project" )を指定する必要があります。ibmcloude CLI で以下のように実行します:
> ibmcloud ce project select --name my_ce_project

確認のため、選択したプロジェクト内のジョブ一覧を表示します(mycejob が存在していることを確認します):
> ibmcloud ce job list

ジョブをリスト中...
OK

名前     経過時間
mycejob  24m

ではこの mycejob を「5分おきに実行」するよう指定します。この「5分おき」というのは cron ジョブスケジュールでは --schedhle "*/5 * * * *" と表現します。またウェブアプリケーションではなくジョブアプリケーソンにこのスケジュールを指定する場合は --destination-type job オプションを指定する必要があります。結論としては以下のように入力します(mycesub という名前の ping サブスクリプションを作成し、mycejob ジョブに対して5分おきの実行を指示しています):
> ibmcloud ce sub ping create --name mycesub  --destination mycejob --destination-type job --schedule "*/5 * * * *"

成功していると5分おきに(毎時 5 分、10 分、15 分、20 分・・・のタイミングで)ジョブが実行され、実行結果が増えていく様子が確認できます:
2021081502


これでインターネットに常時接続している実行環境を用意しなくても、あらかじめ想定したタイミングでスタンドアロンアプリケーションを実行できる環境が用意できました。 COBOL などのレガシー資産でもこういう使い方ができると便利ですよね。


このページのトップヘ