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

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

タグ:rest

開発開始から4週間、ブロックチェーン対応 RESTful データベース HATOYA がある程度動くようになった記念の Docker イメージを公開します。なお現時点で公開しているのは amd64 版の(x86 Linux 版の)Docker イメージです。

以下ではとりあえず開発用途として使うためのシングルノードで動かす場合の使い方を含めて紹介します(マルチノードで動かすこともできるのですが、、、その説明はまた別の機会に。興味ある方は連絡いただければ何か対応しますw)。


【HATOYA とは?】
HATOYA の特徴は大きくは以下の3点です:
(1) REST API でデータベースやその中のアイテムデータを CRUD(Create/Read/Update/Delete) するデータベースです。
(2) 変更を伴う API(読み取り以外の API)を実行すると、その内容は内蔵ブロックチェーンに自動記録されます。
(3) マルチノードで動かす場合、ブロックチェーン部分はマルチノード間で同期します(データベース部分は同期しません)。
 (3-1)データの破損時や環境引越し時などはマルチノード内の他ノードのブロックチェーンを使って、データベースやブロックチェーンのリストアが可能です。
 (3-2)ブロックチェーンネットワークへはプライベートネットワークからでも参加可能です(この場合、push 型の同期になります)


【事前準備】
x86 版の Docker 環境をインストールしておいてください。後述の公開イメージが amd64 アーキテクチャ用のため、Windows コンテナ版やラズパイコンテナ版ではなく、x86 Linux 用のものが必要です。

※ソースコード的にはラズパイ上でも動くことを確認しています。どなたかラズパイコンテナ用のイメージと併せてマルチアーキテクチャイメージにする具体的な方法をご存知の方がいたら教えていただけると助かります。


【Docker イメージ】
DockerHub 上に公開しました:
https://hub.docker.com/repository/docker/dotnsf/hatoya


2020080300


【起動方法】
イメージを DockerHub から pull して起動します。HATOYA はデフォルトで 4126 番ポートでリクエストを待ち受けるので、同じ 4126 番ポートで待ち受ける形で外部からもリクエストを受け付ける形にするのであれば、以下のように起動します:
$ docker pull dotnsf/hatoya

$ docker run -d -p 4126:4126 dotnsf/hatoya


(注 この方法で起動した場合、システム情報やデータ情報、ブロックチェーン情報は永続化されないため、コンテナを再起動するとデータを失ってしまいます。これを避けるためには以下のような(ボリュームを使った)起動パラメータを指定して、システムフォルダを永続化する必要があります)
$ docker run -d -v /tmp/.system:/tmp/.system -e SYSTEM_FOLDER=/tmp/.system -p 4126:4126 dotnsf/hatoya



これで 4126 番ポートで REST API の待受状態になります。同一ホストからのみ http://localhost:4126/doc を開くことで Swagger API ドキュメントを参照/実行することができます(Swagger API ドキュメントは他ホストからでも参照は可能ですが、実際に API を実行することは CORS 制約によりできません):
2020080101



【いくつかの API を実行してみる】
では Swagger API ドキュメントを使って、実際にいくつかの REST API を実行してみます。

※この節で書かれた内容が難しくてわからん、という方は下の「ダッシュボードを使う」を参照してください。同じ操作を GUI で行えます。

例えば、「現在のデータベース一覧」は GET /api/dbs という REST API で取得できます(Swagger API ドキュメント上では GET /dbs と表示されていますが、API は全て /api パス以下にあるため省略表記されています。以下同様):
2020080102


実際に実行するには Swagger API ドキュメントから "Try it out" を押した後に "Execute" を実行します。なおこの画面ではパラメータに token (トークン)を指定できるようになっていますが、今回はトークンを無効にして起動しているので指定する必要はありません(トークンがないと API が実行できないように設定することは可能です):
2020080103


"Execute" すると、実際に実行された内容を curl コマンドで実行した場合のコマンド内容(curl -X GET "http://localhost:4126/api/dbs" -H "accept: application/json")や、エンドポイント URL (http://localhost:4126/api/dbs)とともに実行結果が表示されます:
2020080104


ここでは実行結果は以下のようになっています。この実行結果の意味は実行は成功(status: true)し、データベースの一覧は空([])になっている、という内容でした:
{
  status: true,
  dbs: []
}

※注 HATOYA の REST API は(添付ファイル参照など)バイナリデータを返すもの以外は、実行結果は原則的に JSON データとなります。またその status 要素が true の時は成功、false の時は失敗を意味するよう統一されています。


なお、localhost 以外のホストから同じ API を実行する場合は curl コマンドを参照し、localhost 部分を IP アドレス(以下の例では xx.xx.xx.xx)に置き換えて実行することで実現できます:
$ curl -X GET "http://xx.xx.xx.xx:4126/api/dbs" -H "accept: application/json"

{
  status: true,
  dbs: []
}

この節の以下の説明では全て Swagger API ドキュメントを使って操作しますが、外部から実行する場合は同様にして操作する API と同じ curl コマンドを実行して試してみてください。


ではデータベースを1つ作成してみましょう。データベースの作成は POST /api/{_db} を使って行います:
2020080105


ここでも "Try it out" を押し、パラメータ _db に作成するデータベースの名前(以下の例では "mydb")を指定して "Execute" を実行します:
2020080106


すると実行結果(以下例では { status: true })が表示されます。成功したようです:
2020080107


改めてもう一度先程実行したデータベース一覧の API(GET /api/dbs)を実行します。すると先程空([])だった dbs の値が [ "mydb" ] となり、データベース作成が反映されたことが確認できます:
2020080108


では次に作成したデータベース mydb 内に1つアイテムを作成してみます。アイテムの新規作成 API は POST /api/{_db} で行います(下図では POST /api/{_db}/{_id} となっていますが、この {_id} 部分を指定しないと新規作成とみなすので、結局エンドポイントは POST /api/{_db} となります)。なおエンドポイントがデータベース作成時と同様ですが、ポストデータが存在する場合はアイテムの新規作成、存在しない場合はデータベースの新規作成とみなされます:
2020080101


ではこれまでと同様に一度 "Try it out" をクリックし、_db に "mydb" を指定後、送信 Body 内に保存したい内容の JSON オブジェクト(以下の例では { name: "K.Kimura", hobby: "Programming" })を記入して "Execute" を実行します:
2020080102


実行結果が以下のように( { status: true, id: "xxxxxx" } )表示されます。status: true となっているので処理は成功し、アイテムの id が割り振られて保存されました。その id 値も実行結果内に表示されています:
2020080103


実際にデータベース内にアイテムが格納されたことを確認してみます。データベース内のアイテム一覧を見るには GET /api/{_db} を実行します。この _db パラメータに "mydb" を指定して "Execute" します(なおこの API を実行する際に limit や offset を指定することも可能ですが、今回は使用せずに全データを表示します):
2020080104


こちらが実行結果({ status: true, items: [ "xxxxxx" ], limit: 0, offset: 0 })です。status: true が含まれているので処理は成功し、結果は items: [ "xxxxxx" ] という形で含められています。この items は指定したデータベースに含まれるアイテムの id の配列となっています。この時点では1つだけアイテムが含まれていることがわかります:
2020080105


ではこの id のアイテムが本当に先程入力したアイテムかどうかも確認してみます。特定のアイテムデータを取得するには GET /api/{_db}/{_id} を実行します。このパラメータのうち、_db は "mydb" 、_id は先程取得した items 配列の中に1つだけ含まれていた id の値を指定して "Execute" します:
2020080106


その実行結果がこちら({ status: true, item: { name: "K.Kimura", hobby: "Programming", id: "xxxxxx" } })です。先程の新規作成時に入力した内容が正しく記録されていました:
2020080107


今度はこのアイテムの内容を更新してみます。アイテムを更新する API は先程の新規作成時と同じ POST /api/{_db}/{_id} です({_id} を未指定時に新規作成とみなします)。この _db を "mydb" に、_id を先程指定した id 文字列値にして、body 部を適当に(下例では { name: "キムラ ケイ", hobby: "プログラミング" } と)して "Execute" します:
2020080108


再度アイテムの内容を確認する GET /api/{_db}/{_id} を、 _db に "mydb" 、_id に id 文字列値を指定して実行してみます。実行結果は { status: true, item: { name: "キムラ ケイ", hobby: "プログラミング", id: "xxxxxx" } } となり、正しくアイテムを更新することができました:
2020080109


今度はこのアイテムを削除してみます。アイテム削除の API は DELETE /api/{_db}/{_id} です。同様にして _db に "mydb" を、_id に id 文字列値を指定して "Execute" します:
2020080101


実行後に再度 GET /api/{_db}/{_id} を同じパラメータで実行しても、結果は { status: false } となり、実行に失敗することが確認できます。アイテムが消えてしまったので見つからない、という一覧の結果が確認できました:
2020080102


HATOYA にはこれらの基本的なアイテム CRUD 用 API 以外にも添付ファイルをアイテムとして登録する API があったり、複数アイテムをまとめて作成/更新/削除する、いわゆる「バルク処理」を行う API が存在してます。ここでは紹介しませんが、興味ある方は Swagger API ドキュメントを参照して試してみてください。


さて、ここまでが RESTful DB としての HATOYA の紹介でした。ここから先は HATOYA の特徴的な部分でもあるブロックチェーンプラットフォームとしての機能を紹介します。

ここまでの一連の処理の中で何度かシステムに変更を及ぼす API を実行しました。具体的には (1) mydb データベースの作成 (2) mydb データベースにアイテムを1件新規登録 (3) mydb データベースに登録したアイテムの内容を更新 (4) mydb データベースに登録したアイテムを削除 の計4回変更しています。これらの変更記録は全て HATOYA 内のブロックチェーンに自動登録されています。今度はブロックチェーンの中を参照する API を紹介します。

それが GET /api/ledgers です。早速実行してみよう・・と思うのですが、この API は実行時に serverid という HATOYA サーバーノードの id をパラメータ指定する必要があります。まずはこの serverid を取得しましょう:
2020080103


serverid 情報は GET /api/serverid で取得することができます。まずこの API を実行して、その実行結果に含まれる serverid 値を取り出します:
2020080104


GET /api/serverid 実行結果( { status: true, serverid: "zzzzzz" } )の "zzzzzz" 部分が、このサーバーノードの serverid 値です。今回の GET /api/ledgers 以外にも実行時にこの serverid 値をパラメータ指定する必要のある API がいくつかありますが、全てこの方法で確認可能です:
2020080105


では改めて、取得した serverid を指定して GET /api/ledgers を実行します:
2020080106


実行結果は以下のようになりました:
2020080107

{
  "status": true,
  "ledgers": [
    {
      "prev_id": null,
      "body": [
        {
          "serverid": 1594300581524,
          "method": "create_db",
          "db": "mydb"
        }
      ],
      "timestamps": [
        1596292186611
      ],
      "nonce": 34,
      "id": "0041cf982623372e59bafac8ee055f63b061fa702ea0364e6894fc5c453cd8ae0c0bf5e094892e8d34ac8d78229bcfb014f7641595bed4987545cf425b7e2d38"
    },
    {
      "prev_id": "0041cf982623372e59bafac8ee055f63b061fa702ea0364e6894fc5c453cd8ae0c0bf5e094892e8d34ac8d78229bcfb014f7641595bed4987545cf425b7e2d38",
      "body": [
        {
          "serverid": 1594300581524,
          "method": "create_item",
          "db": "mydb",
          "item": {
            "name": "K.Kimura",
            "hobby": "Programming",
            "id": "52ca8330-d405-11ea-a583-074fb3956e72"
          }
        }
      ],
      "timestamps": [
        1596292991723
      ],
      "nonce": 232,
      "id": "00ab2eddbec25b10560d8769c3d21a21efee0edb2f69e634da34fe1f326aae96a2d5346cff4ff794c88bc68fa3c45fcf73ba483b3c1ae1977f3b326efda3aeed"
    },
    {
      "prev_id": "00ab2eddbec25b10560d8769c3d21a21efee0edb2f69e634da34fe1f326aae96a2d5346cff4ff794c88bc68fa3c45fcf73ba483b3c1ae1977f3b326efda3aeed",
      "body": [
        {
          "serverid": 1594300581524,
          "method": "update_item",
          "db": "mydb",
          "item": {
            "name": "キムラ ケイ",
            "hobby": "プログラミング",
            "id": "52ca8330-d405-11ea-a583-074fb3956e72"
          }
        }
      ],
      "timestamps": [
        1596294186025
      ],
      "nonce": 427,
      "id": "0066ee6659b2ea71365ddc8b93a780e2d3684fc7f40d6c5f3ccec6835385f7863c1737f8eef239a0867440f2493d246166387082053f7cbb20aea468a8e74c7a"
    },
    {
      "prev_id": "0066ee6659b2ea71365ddc8b93a780e2d3684fc7f40d6c5f3ccec6835385f7863c1737f8eef239a0867440f2493d246166387082053f7cbb20aea468a8e74c7a",
      "body": [
        {
          "serverid": 1594300581524,
          "method": "delete_item",
          "db": "mydb",
          "id": "52ca8330-d405-11ea-a583-074fb3956e72"
        }
      ],
      "timestamps": [
        1596294559724
      ],
      "nonce": 88,
      "id": "006c91e3c08ded1791775aab6a0fa02b82cb842f5d2e6fba989d58b64444b5f0005ac115eb969b8491cad824ac6e5e924c4b8baa6567c04c7997b505a7891016"
    }
  ],
  "limit": 0,
  "offset": 0
}

実行結果の JSON オブジェクト内の ledgers が配列となっていて、その中にブロックチェーンの内容が含まれています。視認しやすいように上記では色をつけていますが、(1) mydb データベースの作成 (2) mydb データベースにアイテムを1件新規登録 (3) mydb データベースに登録したアイテムの内容を更新 (4) mydb データベースに登録したアイテムを削除 の4つが全て serverid 付きで登録されていることがわかります。しかも各オブジェクトには id と prev_id (と nonce)というキーがあり、これらがハッシュチェーンとなって繋がっていることで改ざんが困難なブロックチェーンとして機能しています。

つまり、この段階では HATOYA には mydb というデータベースが1つだけ存在していて、中身は何のアイテムデータが含まれていない状態になっていますが、これがデータベースが作られた直後ではなく、アイテムデータが作成され、変更され、削除された上での結果として何も含まれていない状態になっている、ということをブロックチェーンという技術が保証してくれていることになります。

これらのデータベースシステムへの変更作業が自動でブロックチェーンに登録される(そしてこのブロックチェーンは改ざんが困難なので変更履歴が保証される)ことが HATOYA の特徴の1つになっています。


【ダッシュボードを使う】
一つ上の節では Swagger API ドキュメントや REST API を使って HATOYA のデータベースを読み書きしたり、その結果生成されたブロックチェーンの様子を確認しました。これが理解しにくい、という人向けに簡易ダッシュボード機能も用意しています(ただしダッシュボードでは全ての REST API を実行できるわけではありません。例えば添付ファイルの読み書きやバルク API 実行は現在のダッシュボードからは行なえません)。

ダッシュボードへアクセスするにはブラウザでドキュメントルート(/)にアクセスします。localhost 上で実行している場合であれば http://localhost:4126/ へアクセスします。初回のみ Basic 認証を聞かれます。デフォルトではユーザー名 = username, パスワード = password です:
2020080202


※なお、この認証値は Docker コンテナ起動時の環境変数パラメータで変更可能です。例えばユーザー名 = user, パスワード = pass にするには以下のように起動します:
$ docker run -e BASIC_USERNAME=user -e BASIC_PASSWORD=pass -d -p 4126:4126 dotnsf/hatoya

正しい認証値を入力するとダッシュボードの画面が表示されます:
2020080201


画面左部分がメニューになっており、ブロックチェーン一覧や新規データベース作成はこちらから行えます。なおこの画面からブロックチェーン一覧を実行する場合、上述時に指定した serverid 値は自動的に指定されるので意識する必要はありません。なお serverid 値を確認したい場合は左上のハトマークにマウスカーソルを移動させると表示できます(シングルノード稼働の場合、(Config) と書かれた部分の環境設定は不要です、また config や ledgers という名前のデータベースを作成することはできません):
2020080203


データベースを作成すると (Config) の下に存在するデータベースが一覧で表示され、どれか1つを選択すると、そのデータベース内のアイテム一覧が表示したり、新規にアイテムを追加したり、編集/削除したりが行なえます。データベースの削除もこの画面から可能です:
2020080204


一通りの作業後に画面左の (Ledgers) を選択すると、その時点でのブロックチェーンの状態を確認することが可能です:
2020080205


↑UI 的にも全然イケてないダッシュボードですが、最小限の CRUD 操作を行ってブロックチェーンの確認までは行えるようになっています。Swagger API ドキュメントや curl の操作がよくわからない場合はこちらも併用してください。



【ブロックチェーンアプリケーション開発に】
HATOYA の全ての機能を紹介しているわけではないのですが、とりあえずこれらの情報を使うことでシングルノードの HATOYA を使ったブロックチェーン対応データストアが実現できると思っています。試験的にアプリケーションと組み合わせて使ってみていただいても構いませんし、HATOYA 対応アプリケーションを開発する用途であれば、その環境はシングルノードでも十分だと考えています。基本的には DB を REST で読み書き更新削除するだけでブロックチェーンに記録されるという特徴があるので、ブロックチェーンをほぼ意識することなくブロックチェーン対応アプリケーションが実装できると思っています(ちなみに拙作のマンホールマップ内でも使っています)。

今回紹介していませんが、本来は config 機能を使ってブロックチェーンネットワークを構築し、マルチノードでブロックチェーン部分を同期しながら運用することも想定しています。そこにもいくつかの特徴的な機能もあって我ながら面白い作品だと思っていますが、そのあたりについてはまた別の機会に。。


Swagger を使うことで、インタラクティブに実行可能な REST API のドキュメントを作ることができます。比較的有名な所では Swagger がライブデモとして用意している「ペットストア・サービスの REST API」があります:
https://petstore.swagger.io/

2020012201



↑架空の「ペットストア・ウェブサービス」が存在している前提で、ペットやその画像を登録/更新したり、検索したり、、加えてペットストア自体の登録/管理をしたり、利用者となるユーザーの管理といったペットストア運営に必要と思われる最低限の機能の REST API が実行可能な状態で公開されています。


アプリケーションを REST API ベースで作る際に、このような Swagger による API ドキュメントを用意しておくと、REST API の仕様が確認できるだけでなく、実際にパラメータを指定した上で実行したり、実行した時のデータのやりとりやその結果までを確認することができて便利です。REST API を作成した時にセットで作っておくと、開発後の運用時や機能追加、引き継ぎといった段階においても非常に役立つインタラクティブドキュメントだと思っています。


この Swagger ドキュメントは REST API 開発後に swagger.yaml (または swagger.json)というファイルを用意することで自動作成が可能になります。特定のルールに基づいて REST API の内容(ホスト、パス、メソッド、パラメータ、返り値、・・)を記述していくと、ドキュメントの UI は自動的に作成され、指定した通りに実行が可能になります(そして実際に実行された結果を確認することもできるようになります)。

なお上述のペットストア・サービス REST API の定義内容はこちらの Swagger Editor で確認できます:
https://editor.swagger.io/

2020012205


swagger.yaml の記述方法そのものについては本ブログエントリの範囲ではないので、興味ある方は別途調べていただきたいのですが、そこまで難しい内容ではないと感じています。例えば GET /users?limit=xx&offset=yy といった具合に2つの URL パラメータ(limit, offset)を指定可能(必須ではない)な GET の REST API が存在していて、これを Swagger ドキュメントに記述する場合は swagger.yaml ファイルに以下のように指定します:
  :
/users
  get
    parameters:
      - name: limit
        type: number
        in: query
        description: 取得するデータ数
      - name: offset
        type: number
        in: query
        description: 取得するデータのオフセット
  :

実際に Swagger ドキュメントとして生成すると、この部分は以下のような UI に変換されて指定可能になる、といった具合です:
2020012202



で、ここからが本エントリの本題です。この Swagger ドキュメント(の yaml ファイル)を記述する際の、特に「ファイルアップロード」の API を記述する場合のパラメータ指定方法が特殊でちとわかりにくいものでした。試行錯誤した上で実際に動く例を見つけたので、自分の備忘録も兼ねて以下に紹介します。

まず API そのものは単純なファイルアップロード(POST /file)と仮定します。受け取るパラメータはアップロードするファイル本体だけ、他のパラメータは(Swagger ドキュメント的には特別な部分ではないので)いったん無視します。Node.js + Express 環境であれば、以下のような内容で作られているようなものです:
//. multer モジュールを利用してアップロードファイルを受け取る
var express = require( 'express' ),
    multer = require( 'multer' ),
    router = express.Router(),

    :

//. multer モジュールの設定
router.use( multer( { dest: './tmp/' } ).single( 'file' ) );

    :

//. POST /file として REST API を用意する
router.post( '/file', function( req, res ){
  //. アップロードされたファイルの情報を取り出す
  var filepath = req.file.path;
  var filetype = req.file.mimetype;

    :
    :


UI 側からこの API を呼ぶ時は、普通に以下の様な enctype="multipart/form-data" を指定した form の HTML を用意すれば実行できるものです:
<form method="POST" action="/file" enctype="multipart/form-data">
<input type="file" name="file"/>
</form>

↓こんな感じの見た目になって実行されるものです:
2020012203

このような挙動を行う API : POST /file を Swagger ドキュメントとして用意する場合の(パラメータ部分の)記述方法をどのようにするか、というのが今回のテーマでした。

ちょっと特殊な指定方法となりますが、結論としては以下のようになります:
  :
/file:
  post:
    consumes:
      - multipart/form-data
    parameters:
      - in: formData
        name: file
        type: file
        description: アップロードするファイル
        required: true
  :

特殊な部分を解説します。まず HTML の form 属性で enctype="multipart/form-data" 指定を行っていた部分ですが、上述のように consumes: で "multipart/form-data" を指定することで実現します。

またパラメータ部分ですが、POST のデータとして送信するので "in: formData" を指定します。またパラメータの種類として "type: file" も付与します。 加えて、このパラメータは API 実行時には必須指定パラメータとなるので "required: true" も付与します。swagger.yaml ではこのように指定することでファイルアップロード系の API を記述できます。

参考程度に、このように指定した swatter.yaml を実際に Swagger ドキュメントに変換して表示すると、以下のような画面になりました:
2020012204


期待通りの結果になりました。

ベータ2が公開されたばかりの IBM Notes(以下「ノーツ」)V10 の、個人的に待望だった新機能「LotusScript で HTTP リクエスト」を早速試してみました。


1989 年(平成元年!)にバージョン1がリリースされたノーツはメールやインターネットの標準機能を取り込みながら進化してきました。特徴の1つでもあるマクロ言語の機能も簡易的な@関数式から VBA 互換の LotusScript 、Java 、そして JavaScript と時代に即して対応範囲を広げてきました。

ただ、ここ数年のカスタマイズの中で不便に感じることも出てきました。それが(この下で紹介する新機能でもある)HTTP リクエスト機能です。各種 REST API などと連携しようとすると HTTP リクエストを実行して、その結果を受け取って・・という一連の処理が必要になります。

ノーツで HTTP リクエストを実行できなかったわけではありません。ノーツは JVM を内蔵しており、Java で(エージェントと呼ばれる)マクロを記述して実行することができます。この方法を使うことで(つまり Java のネットワーク機能を使うことで)HTTP リクエストを実行することはこれまでも可能でした。

一方で制限事項もありました。ノーツの Java エージェントはバックエンド機能として提供されており、そのフロントエンド機能は提供されていませんでした。具体的にいうと「編集中の(未保存の)画面の Subject フィールドの値を取り出して、その値をパラメータとして HTTP リクエストを実行」といった処理をしようとすると、青字部分は Java を使えばいいのですが、赤字の部分の処理は Java からは実行できないのでした。これを実現するために例えば LotusScript のフロントエンド機能を使って赤字部分の処理を実行し、そこで取り出した値を(ファイル渡しなどの)何らかの方法でバックエンドの Java に渡して処理させる、という連携が必要でした。なので、技術的に不可能でなかったのですが、2つの言語記述による処理を連携させる必要があり、ちと面倒だったのでした。

今回リリースされた V10 ベータ2の新機能「LotusScript から HTTP リクエスト」はこの制約を取り除くことができると思っています。具体的にはフロントエンドの LotusScript で編集中の値を取り出して、そのままバックエンドの LotusScript で HTTP リクエストを実行することができるようになる、というものです。全てが LotusScript の1処理で完結することになり、自然に実装することができるようになると期待していました。その機能を使ってみたので以下で紹介します:



まずはノーツ V10 をインストールします、about 画面はこんな感じです:
20180821


で、肝心の HTTP リクエストは・・・どうやって使うんだろ?? (^^; ベータ版だからなのか、全くドキュメントが見つかりません。。。

それっぽいキーワードでぐぐって、唯一なんとか見つけたのがこの PDF ・・・
http://collabsphere.org/ug/collabsphere2018.nsf/0/789F066AA1414139862582A900620D93/$File/CollabSphere2018%20-%20Elementary.pdf

2018082105
 ↑NotesSession から createHttpRequest() して・・・ってこと?

これ、どこまで信用できる情報かわからないし、情報は充分ではないけれど、今はこれを信じて使ってみることにします。まずは超シンプルに僕が IBM Cloud 上に作った「リアルタイム外為情報」 API をそのまま HTTP GET で呼んでみることにします:
2018082105
 ↑HTTP GET されたら、こんな JSON を返すだけの API


上記 PDF の内容に従って、NotesSession のインスタンスから createHTTPRequest() して NotesHTTPRequest のセッションを生成し、URL を指定して HTTP GET 、その結果を MsgBox でそのまま出力、という内容です。なお PDF に記述されていた Use "class.RemoteHTTP" という宣言をするとエラーになってしまうため使っていません:
2018082103

Sub Click(Source As Button)
  Dim session As New NotesSession
  Dim http As NotesHTTPRequest
	
  url = "http://fx.mybluemix.net/"
	
  Set http = session.CreateHTTPRequest()
  response = http.Get( url )
  Msgbox response
End Sub

実行したら・・・  動いた!!
2018082104


ついに LotusScript だけで HTTP GET Request が動いた瞬間です。わーい、結果は HTTP レスポンスヘッダを含むテキストになってるわけね、ふむふむ。。

あ、よく見るとリクエスト先 URL のプロトコルが "https" ではなく、"http" になっていたので、もう一度 "https" にしてやり直し:
2018082101

Sub Click(Source As Button)
  Dim session As New NotesSession
  Dim http As NotesHTTPRequest
	
  url = "https://fx.mybluemix.net/"
	
  Set http = session.CreateHTTPRequest()
  response = http.Get( url )
  Msgbox response
End Sub


・・・したらエラー(苦笑):
2018082102


なんだ、このエラーは (^^; ベータ版だからなのだろうか?それとも回避手順がある?? あと HTTP GET はいいんだけど、HTTP POST がよくわかりません。http.Post( url, content ) らしいんだけど、content をどう書けばいいんだろ??それから認証が必要な場合は・・・


まだ情報不足もあって調べられることに限界がありますが、一方で LotusScript のみで REST API が実行できそうだ、という感触も得ることができました。これでドキュメントが揃ってくればノーツからの Watson API 連携ももっと楽にできるようになるかな。。


以前のブログエントリの中で Hyperledger Composer を使うことでブロックチェーンのビジネスネットワークを比較的簡単な定義で動かすことができることを紹介しました:
Hyperledger Composer フレームワークを使ってみる

↑ここで紹介したように Hyperledger Fabric v1.0 と Hyperledger Composer を使うことで、"asset" と呼ばれる取扱データ、"participant" と呼ばれるユーザー、 "transaction" と呼ばれる実行処理、そして "ACL" と呼ばれるアクセス権管理を定義してブロックチェーン環境に簡単にデプロイすることができるようになります。

この方法でデプロイしたブロックチェーンネットワークを外部のプログラムから使う方法も何通りかあるのですが、今回はその中から「Composer の内容を Web API 化」して、外部からは HTTP(S) を使った REST API として使えるように公開する方法を紹介します。以下の手順を実際に試す場合はローカル環境内に Hyperledger Fabric v1.0 および Hyperledger Composer がインストールされている必要があります。その手順は以下を参照して、ローカル環境で Hyperledger Composer が使える状態を作っておいてください:
Hyperledger Composer フレームワークをインストールする


また、実際に REST API として公開する内容のビジネスネットワークを定義した .bna ファイルが必要です。今回は以下のページで紹介されている方法に従って my-network.bna ファイルを作り、それを使うことにします(以下に日本語で解説します):
Developer Tutorial for creating a Hyperledger Composer solution


ではビジネスネットワークの定義ファイルを用意するまでの手順を紹介します。今回は github.com に公開されているサンプルをベースにクローンして用意します。

まず、上記リンク先の手順に従って Hyperledger Fabric v1.0 および Hyperledger Composer が導入された環境を用意して、両方のサービスを有効にします。

次に同環境にログインし、作業ディレクトリ(以下の例では ~/work)を作って、サンプルプロジェクトを git clone します:
$ mkdir ~/work (作業ディレクトリ)
$ cd ~/work
$ git clone https://github.com/hyperledger/composer-sample-networks.git

このサンプルプロジェクト内の basic-sample-network の内容を my-network という名前でコピーします:
$ cp -r ./composer-sample-networks/packages/basic-sample-network/  ./my-network

my-network プロジェクトの package.json ファイルを変更します。変更内容は name と prepublish 内のプロジェクト名を "my-network" にすることと、description の内容を変えることです:
    :
  "name": "my-network",
  "version": "0.1.6",
  "description": "My Commodity Trading network",
  "networkImage": "https://hyperledger.github.io/composer-sample-networks/packages/basic-sample-network/networkimage.svg",
  "networkImageanimated": "https://hyperledger.github.io/composer-sample-networks/packages/basic-sample-network/networkimageanimated.svg",
  "scripts": {
    "prepublish": "mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/my-network.bna",
    "pretest": "npm run lint",
    :

そして実際のビジネスネットワークの内容を書き換えます。定義する内容は上記ブログエントリの中で紹介したものと同じ定義をこのプロジェクト内にも作成することにします。というわけでまずは models/sample.cto ファイルを開き、以下の内容に書き換えて保存します:
/**
 * My commodity trading network
 */
namespace org.acme.mynetwork
asset Commodity identified by tradingSymbol {
    o String tradingSymbol
    o String description
    o String mainExchange
    o Double quantity
    --> Trader owner
}
participant Trader identified by tradeId {
    o String tradeId
    o String firstName
    o String lastName
}
transaction Trade {
    --> Commodity commodity
    --> Trader newOwner
}

↑ Commodity という asset と、Trader という participant と、Trade という transaction を定義しています。

同様にして lib/sample.js ファイルを開き、以下の内容に書き換えます:
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Track the trade of a commodity from one trader to another
 * @param {org.acme.mynetwork.Trade} trade - the trade to be processed
 * @transaction
 */
function tradeCommodity(trade) {
    trade.commodity.owner = trade.newOwner;
    return getAssetRegistry('org.acme.mynetwork.Commodity')
        .then(function (assetRegistry) {
            return assetRegistry.update(trade.commodity);
        });
}

↑tradeCommodity という処理を定義しています。

そして同様にして permissions.acl ファイルも以下の内容に書き換えます:
/**
 * Access control rules for mynetwork
 */
rule Default {
    description: "Allow all participants access to all resources"
    participant: "ANY"
    operation: ALL
    resource: "org.acme.mynetwork.*"
    action: ALLOW
}

rule SystemACL {
  description:  "System ACL to permit all access"
  participant: "org.hyperledger.composer.system.Participant"
  operation: ALL
  resource: "org.hyperledger.composer.system.**"
  action: ALLOW
}

↑デフォルトで全参加者にリソースへのアクセス権を与えるという内容です。


ではここまでに定義した models/sample.cto, lib/sample.js, permissions.acl の内容でビジネスネットワークをビルドします:
$ cd ~/work/my-network
$ npm install

このコマンドが成功すると、 my-network/dist フォルダ内に my-network.bna ファイルが生成されます。このファイルと Hyperledger Composer を使って Hyperledger Fabric v1.0 に同ビジネスネットワークをデプロイします:
$ cd dist
$ composer network deploy -a my-network.bna -p hlfv1 -i PeerAdmin -s randomString

デプロイが成功しているかどうかは以下の ping コマンドで確認できます:
$ composer network ping -n my-network -p hlfv1 -i admin -s adminpw

ここまでの作業でビジネスネットワークを定義し、Hyperledger Fabric v1.0 上にデプロイすることができました。最後にこのビジネスネットワークを Web API 化して HTTP クライアントから REST でアクセスできるようにします。 そのためには composer-rest-server というツールを使います。composer-rest-server は npm を使ってインストールします:
$ sudo npm install -g composer-rest-server

composer-rest-server の使い方は REST API 化したい Hyperledger Composer プロジェクト上でコマンド実行するだけです:
$ cd ~/work/my-network
$ composer-rest-server

すると Hyperledger-Composer ロゴが現れ、知る人ぞ知る Strongloop loopback のようなインターフェースでのプロパティ指定画面になります:
2017081401


質問内容に以下のように答えます:
 Fabric Connection Profile Name: hlfv1
 Business Network Identifier: my-network
 Fabric username: admin
 secret: adminpw
 namespaces: never use namespaces
 (ここから下はデフォルトのまま)
 REST API to be secured: No
 event publication over WebSockets: Yes
 TLS security: No
2017081402


すると上記画面のように http://(IPアドレス):3000/explorer と表示されます。ウェブブラウザでこの URL にアクセスすると、(これも知る人ぞ知る StrongLoop Loopback のような)定義したビジネスネットワークに REST API でアクセスするための Open API ドキュメントが表示されます:
2017081403


この画面からは定義した Commodity や Trader, Trade といった asset, perticipant, transaction を読み書き実行するための各 API を展開したり、
2017081404


実際にパラメータを指定して実行したりすることもできます:
2017081405


この方法であればブロックチェーンにあまり詳しくないデベロッパーでも REST API で利用できるので非常に便利といえます。

 

このブログでも何度か Node.js のネタを扱ってますが、非同期処理に悩まされることが多いので、自分の理解の意味でもまとめておくことにしました。

まず理解の大前提として、Node.js はシングルスレッドで動作するため、いわゆる並列処理はできない仕様になっています。そして非同期に処理を実行することができます。これによって並列処理ができなくても、何かの時間のかかる処理があった場合にその終了を待たずに次の処理に進むことができることを意味しています。 ただ、この辺りがややこしく難解になっていることも事実です。

例えば REST API などの HTTP リクエストを行って、その結果が得られたら、その得られた結果の JSON オブジェクトの値を使って処理をする、などというよくあるケースでもこの問題に直面します。

まずは何が難解なのかを紹介します。REST API だとローカル環境で気軽に試せないので、わざと実行に時間のかかる関数を用意して説明します。

例えばこのような処理を考えてみます:
// test.js

// わざと1秒かけてから、パラメータの2倍の数値を出力する関数
function func1( x ){
  setTimeout( function(){
    console.log( 2 * x );
  }, 1000 );
}

// 初期値を設定して出力
var n0 = 10;
console.log( 'n0 = ' + n0 );

// 初期値を上記関数のパラメータに入れて実行
func1( n0 );

この中では func1 という関数を定義しています。setTimeout を使ってわざと1秒(1000ミリ秒)待ってから、パラメータの値の2倍を画面に出力する、という関数です。 この関数を n0 (=10 に設定)という変数をパラメータにして実行する、というものです。なので "20" という結果が出力されることを期待しています。

この内容を test.js というファイルに保存して、実行してみます(青字部分が出力結果):
$ node test
n0 = 10
20

期待通りに "20" と出力されました。とりあえずここまでは成功です。


さて問題はここからです。上記例では関数 func1 の中で console.log が実行されて出力までを行いました。これを func1 からは与えた数値を2倍した結果を受け取るようにして、func1 の外側で出力するように変更してみます。深く考えずにやるとこんな感じでしょか?
// test.js(注 正しく動きません)

// わざと1秒かけてから、パラメータの2倍の数値を出力する関数
function func1( x ){
  setTimeout( function(){
    console.log( 2 * x );
  }, 1000 );
}

// わざと1秒かけてから、パラメータの2倍の数値を戻す関数
function func2( x ){
  setTimeout( function(){
    return ( 2 * x );
  }, 1000 );
}

// 初期値を設定して出力
var n0 = 10;
console.log( 'n0 = ' + n0 );

// 初期値を上記関数のパラメータに入れて実行し、戻り値を出力
var n1 = func2( n0 );
console.log( 'n1 = ' + n1 );

func1 をほぼコピペして func2 という関数を作りました。console.log の代わりに return にして、値を返すようにしています。呼び出し元からはこの func2 を実行して、得られた結果を出力するようにしています。

これを先程と同様に実行するとこうなります:
$ node test
n0 = 10
n1 = undefined

n0(10)の値の2倍の "20" という結果を期待していたのですが、"undefined" と表示されてしまいます(正確に書くと、上記の2行はすぐに表示されますが、更に1秒くらい経過してから終了します)。この理由は最初に書いたように func2 は非同期に実行されているので、return の行が実行されるまでには1秒かかります。しかしその前に(return の行が実行される前に)関数そのものの処理は終了してしまいます。つまり値が戻る前に(戻っていない値を受け取ることになっている)n1 という変数を出力しているので "undefined" になっているのでした。

このように、期待通りに動かなかった理由は明白なのですが、ではどうすればこの関数が期待通りに動く(1秒後に与えられた結果を戻り値として戻し、受け取った側がその値を出力する)ようにできるでしょうか?これが今日紹介する大きなテーマです。


結論を先に紹介すると、ここで Promise オブジェクトを使って関数を修正し、受け取った側もその変更に合わせて一部書き直す必要があります。具体的には以下のように修正します:
// test.js

// わざと1秒かけてから、パラメータの2倍の数値を出力する関数
function func1( x ){
  setTimeout( function(){
    console.log( 2 * x );
  }, 1000 );
}

// わざと1秒かけてから、パラメータの2倍の数値を戻す関数
function func2( x ){
  return new Promise( function( resolve ){
    setTimeout( function(){
      resolve( 2 * x );
    }, 1000 );
  });
}

// 初期値を設定して出力
var n0 = 10;
console.log( 'n0 = ' + n0 );

// 初期値を上記関数のパラメータに入れて実行し、戻り値を出力
func2( n0 ).then( function( n1 ){
  console.log( 'n1 = ' + n1 );
});

まず関数 func2 側は、Promise オブジェクトを新規に作成します。Promise オブジェクトは処理が成功した場合の関数をパラメータに指定します。上図だと
function( resolve ){
  setTimeout( function(){
    resolve( 2 * x );
  }, 1000 );
}

という関数がパラメータに指定されているので、成功するとこの関数が実行されます(今回は使っていませんが、第二パラメータを指定した場合は失敗時に実行する処理を指定したことになります)。この処理の中で1秒待って、指定したパラメータを2倍して resolve とする、ということになります。

そしてこの関数 func2 を呼び出す側も少し変更が必要になります。 func2() 関数の実行結果をそのまま変数として受け取るのではなく、成功した場合(今回の例だと1秒待って2倍になった値が返された場合)の処理を .then() 内に渡して処理することになります。この then 内の処理で計算結果(resolve で処理された内容)を n1 という変数で受け取って console.log で表示する、という内容にしています。

こうして修正した test.js を実行すると、以下のような結果になります(実際には n0 = 10 がすぐに表示され、1秒くらい待ってから n1 = 20 の行が表示されて終了します):
$ node test
n0 = 10
n1 = 20

Node.js の関数内で非同期処理を実行して、その非同期処理の終了を待って値を受け取るような関数を作る場合は、Promise オブジェクトを使って上記のように記述します、という紹介でした。非同期実行に慣れていないと、この辺りで戸惑うことが多いと感じたので、まとめておきました。


このページのトップヘ