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

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

2020/08

ウェブページ内の特定要素の見た目を JavaScript で動的に変更する方法を調べました。

一般的にウェブページ内の要素の見た目はスタイルシートで管理されます。例えばこのような感じ:
<head>
<style>
#container1{
  background-color: #fcc;
  height: 100px;
}
</style>
</head>
<body>

<div class="container" id="container1">
</div>

</body>

この時点では #container1 要素はピンク(#ffcccc)で塗りつぶされて表示されます:
2020082601


この部分の「背景色を動的に(JavaScript で)別の色に変えたい」場合、いくつかの方法が考えられます。一般的には背景色が指定された class を複数用意し、id ではなく class 指定で背景色を与えた上で、JavaScript で指定 class を切り替える(jQuery であれば removeClass + addClass)方法です。 ただこの方法は「事前に class を定義しておく」必要があります。切り替える瞬間の情報や属性値を参照して背景色を決めるとか、ランダム要素を使って背景色を決めるなど、事前に定義する内容が定まらないような場合では使えません。このような場合も含めて #container1 要素に定義された内容そのものを変更する(つまりスタイル定義内容そのものを動的に変更する)にはどのようにすればよいかを調べました。以下、jQuery を併用した場合で説明します。

例えば #container1 で定義されるエリアをクリックしたら背景色をランダムに変える、という場合であれば以下のような JavaScript で実現できます:
<script>
$(function(){
  $('#container1').on( 'click', function( e ){
    //. #container1 のスタイルシート定義を動的に変える。具体的には背景色を変更する
    var bgs = [ '#fcc', '#cfc', '#ccf', '#cff', '#fcf', '#ffc', '#ccc' ];
    var bg = bgs[Math.floor( Math.random() * bgs.length )];
    var t = $('#container1')[0];
    t.style['background-color'] = bg;
  });
});
</script>

スタイルシートの内容を変更したい要素をセレクターを指定して取り出し、その取得結果(配列)の最初の要素を取り出します。そして取り出した要素の style 属性がスタイルシートの内容なのでここを JavaScript で上書きするよう記述します(上例では t.style 内の 'background-color' 属性をランダムに上書きしています)。この方法であれば class を使うことなく、切り替えることもなく、スタイルシート定義を書き換えることで見た目の変更を実現できます。


実際に動くサンプルを用意しました:
https://dotnsf.github.io/dynamic-css/


上記 URL にアクセスすると以下のような画面が表示されます。画面上部に高さ 100px でピンク色の矩形が表示されています:
2020082602


この矩形部分をクリック(タップ)すると、7種類の中からランダムに選ばれた背景色に切り替わります:
2020082603


JavaScript でスタイルシートの内容を動的に書き換える方法のサンプルでした。class を事前に用意して指定し直す、というロジックが使えない場合でも実現可能な方法です。


こちらの続きです:
HATOYA の Docker イメージを公開

また、そもそもこのようなデータベースプラットフォームを開発しようと考えた背景的なものはこちらのブログエントリで綴っています。こちらも合わせて参照ください:
開発者視点で「理想的なブロックチェーン」とは?



上述のブログエントリでブロックチェーン対応 RESTful データベースプラットフォームである HATOYA とその REST API 、また公開している docker イメージをシングルノードで使う方法を紹介しました:
2020081201


アプリケーション開発者が対応アプリケーションを開発するだけの目的であればシングルノードでも充分だと考えていますが、実際に運用(特にブロックチェーンネットワークを運用)する際においてはマルチノードで運用するケースが多いと考えています。ということで、本ブログエントリでは同じ docker イメージを使ってマルチノードで HATOYA ネットワークを構築する方法を紹介します:
2020081202


まず、以下で紹介する HATOYA ネットワークのトポロジーは以下のように定義するものと仮定します:

  • サーバーノードは3つ(それぞれノード1、ノード2、ノード3と呼ぶ)。それぞれが個別のデータベース・サーバーとなっており、ブロックチェーンのみネットワークで同期する
  • (docker でネットワークを構築することを想定し)3つのサーバーノードの IP アドレスは全て同一とする。またポート番号はそれぞれ 14126、24126、34126 とする
  • ノード1上でブロックチェーンに更新があると、その情報はノード3のブロックチェーンと同期する
  • ノード2上でブロックチェーンに更新があってもなくても、1分おきにノード3のブロックチェーンと同期する
  • ノード3上でブロックチェーンに更新があっても、同期しない(他ノードからの同期リクエストを待つだけ)
  • ノード1とノード2間は直接の同期パスは存在しないが、ノード3を介して情報が同期される


2020081601


このようなブロックチェーンネットワークを定義するための各ノードの設定内容を紹介していきます。

まず、今回3つのサーバーノードを docker コンテナとして起動しますが、docker 上のコンテナが同期を取る際にコンテナ間でネットワーク通信する必要があります。したがって docker 上に構成する複数のサーバーノードは同一の docker ネットワーク上で作成する必要があります。というわけで、まずは hatoya-network という docker ネットワークを作成しておきます:
$ docker network create hatoya-network


次に3つのサーバーノードコンテナを同一の hatoya-network ネットワーク上で、それぞれポート番号 14126, 24126, 34126 で起動します。また docker 上での名前をそれぞれ hatoya1, hatoya2, hatoya3 とします。この時点では3つの HATOYA サーバーがそれぞれ単独で起動される状態となります:
$ docker run -e PORT=14126 --name hatoya1 --network hatoya-network -d -p 14126:14126 dotnsf/hatoya

$ docker run -e PORT=24126 --name hatoya2 --network hatoya-network -d -p 24126:24126 dotnsf/hatoya

$ docker run -e PORT=34126 --name hatoya3 --network hatoya-network -d -p 34126:34126 dotnsf/hatoya

これで docker 上にコンテナ通信可能な3つの HATOYA ノードが作成・起動できました。

次にブロックチェーン同期のための設定を各ノードに加えます。まずノード1は自身のブロックチェーンに変更があった場合、即時にノード3のブロックチェーンに同期リクエストを発行する必要があります。そのための設定項目を追加します。

ノード1のダッシュボードを開き(http://xx.xx.xx.xx:14126/)、左ペインから (Config) と書かれた項目を選択します。右ペインにはノード1のネットワーク設定一覧が表示されますが、この時点では何も設定されていないはずです(新規追加用のフィールドがあるだけです):
2020081602


ここにノード3のブロックチェーンへ同期リクエストを行うための設定を追加します。右ペイン内の新規追加フィールド内に以下の JSON テキストを記述して "Save" ボタンをクリックします(赤字は説明用のコメントなので不要です)。リクエスト送信元では URL キーで同期リクエスト先(今回はノード3)の URL を指定ような設定を行います:
{
  "name": "config1-1",        設定の名前(任意文字列)
  "url": "http://xx.xx.xx.xx:34126" 同期リクエスト先URL、今回はノード3のURL 
}

2020081604


正しく設定が保存されると、設定一覧が更新され、以下のように1件追加されます(表示されている文字列は自動追加された ID です):
2020081605


ID 部分をクリックすると設定内容を表示することができます(もう一度クリックすると省略表示に戻ります):
2020081606


これでノード1のブロックチェーン更新時にノード3のブロックチェーンに同期リクエストが発行されるようになりました。ただこれだけでは同期されません。正しく同期するにはノード3側にもこの同期リクエストを受け付けるための設定が必要です。

その設定をする際にリクエスト元であるノード1の serverid 情報が必要になります。このタイミングで確認しておきましょう。サーバーノードの serverid はダッシュボード画面左上の鳩マーク部分をマウスホバーすると表示されます。この値をこの後使うのでメモしておきます:
2020081603


改めて、今度はノード3のダッシュボード(http://xx.xx.xx.xx:34126)を開き、(Config) を選択して設定一覧の画面を開きます。ノード3も起動直後であれば、この時点では設定項目は何もないはずです:
2020081607


ここに新しい設定を以下の JSON テキストの内容で追加(Save)します。なお serverid 値は先程確認した同期リクエスト元の serverid を指定する必要があります。同期リクエスト先ではリクエスト元の serverid を明示的に指定することであらかじめ想定したノードからのリクエストのみを受け付けることができるよう調整できます:
{
  "name": "config3-1",        設定の名前(任意文字列)
  "serverid": "1597540321639"    同期リクエスト元serverid、今回はノード1の serverid 
}

2020081608


正しく保存できるとノード3の設定一覧が更新され、指定した serverid からの同期リクエストを処理するための設定が追加されます:
2020081609


このペアとなる設定でノード1のブロックチェーンが更新された時にノード3のブロックチェーンと同期を取るための設定ができあがりました。このように同期リクエストを発行する側ではリクエスト先の URL を、受ける側ではリクエスト元の serverid を指定して、両方設定済みの場合のみ同期が行われる点に注意してください。

(今回は設定しませんが)同様にしてノード3のブロックチェーンが更新された時にノード1と同期する場合は今の逆の設定を行います。ノード3の Config にノード1の URL を指定した設定を追加し、ノード1の Config にノード3の serverid を指定した設定を追加することで双方向の同期が実現できます。


続いてノード2とノード3の同期設定を行います。ここでの設定内容は「1分おきにノード2からノード3へ同期リクエストを送る」です。同期リクエストを受けるノード3側はノード1の時とほぼ同様の設定をするだけですが、同期リクエストを送信するノード2側の設定を「ブロックチェーンが更新されたら」ではなく「1分おき」に変更する必要があります。

ではその具体的な方法を紹介します。ノード2のダッシュボードを開き(http://xx.xx.xx.xx:24126)、(Config) を選択して設定一覧の画面を開きます。ノード2も起動直後であれば、この時点では設定項目は何もないはずです:
2020081601


ここに新しい設定を以下の JSON テキストの内容で追加(Save)します。先程と異なり同期リクエスト先 URL に加えて、"cron" というキーで同期タイミングを cron フォーマットで指定します。"* * * * *" は「1分おき」を意味しています:
{
  "name": "config2-1",        設定の名前(任意文字列)
  "url": "http://xx.xx.xx.xx:34126", 同期リクエスト先URL 
  "cron": "* * * * *"                同期タイミングの cron 指定
}

2020081602


正しく保存できると一覧が更新されます。"url" と "cron" 指定がある設定は「指定先 URL に cron 指定のタイミングで定期的に同期リクエストを送信する」というものです。またこの cron 指定はいわゆるスケジュール実行の crontab と同じフォーマットを利用します。「1分」おき以外にも「毎時○分」とか「毎週○○曜日の××時」のような指定も可能です:
2020081603


後はこのノード2からの同期リクエストを受けるための設定をノード3側に追加します。こちらはノード1からの同期リクエストを受ける設定と同様です。ノード2の serverid を調べ、その値を serverid キーに指定して保存します(つまりノード3の設定項目は2つになります):
2020081604


これで目的のブロックチェーンネットワークを構築するための設定が完了しました。なお最後の設定を保存することでノード2のブロックチェーンとノード3のブロックチェーンは1分おきに同期が取られるようになるため、これまでの (Config) 項目を変更した際のブロックチェーンも同期されることになります。1分程度待ってからノード2とノード3のブロックチェーン一覧を確認すると、ノード2とノード3の Config を追加した内容が記録され、同期されている(同じ3つのブロックになっている)はずです:
2020081605


一方ノード1のブロックチェーンのみ、まだ(同期が行われていないため)単独の内容になっているはずです。ではノード1にブロックを追加して、ノード3と同期してみます。ノード1のダッシュボードを開いて左ペインのデータベース作成フィールド(new db と表示されているフィールド)に "db1" を指定して "New" をクリックします("db1" というデータベースを作ります)。この処理がノード1のブロックチェーンに記録されると共にノード3へブロックチェーンの同期リクエストが送信されて同期されます。そのためノード1のブロックチェーンはノード1、ノード2、ノード3の変更記録が全て記録された、5つのブロックで構成されます(ノード3のブロックチェーンも同じ内容になります):
2020081606


また、この直後(ノード1に db1 データベースを追加した直後)ではノード2のブロックチェーンに変化はありませんが、1分程度経過するとノード1との同期が行われたノード3のブロックチェーンと再度同期されるため、ノード2のブロックチェーンも他の2つと同じ5つのブロックからなるブロックチェーンに変わるはずです。これで全てのノードの内容が同期されることになります。


以上が HATOYA をマルチノード運用する場合の設定例となります。もちろん4ノード以上で運用することもあるでしょうし、全て同一ホストの docker ネットワーク上で構成する必要もありません。あくまで同期設定の URL で正しい(ポート番号までを含めた)オリジンが指定されていれば同期できます。HATOYA ネットワークにノードを参加させたい場合は同期リクエストを送る側と受ける側両方に設定が必要(一方的な設定でネットワークに参加することはできない)な点に注意してください。


最後に、この設定方法の特徴として、「プライベートネットワークから HATOYA ネットワークに参加する」ことも可能になっている点を紹介します。上の例では3ノードを全て同一の docker ネットワーク上に作成して説明しましたが、必ずしも同一ネットワーク上に作る必要はなく、また全ノードがパブリックネットワークに存在している必要はありません。同期リクエストを送信する際の設定項目の中で同期リクエスト先の正しい URL を指定できていること(と、同期リクエスト元の正しい serverid を指定すること)だけが条件になっているので、同期リクエスト送信ノードはプライベートネットワーク上に存在していてもよい(同期リクエスト受信ノードから送信ノードにアクセスできなくてもよい)という副産物的な特徴があります。事実、上の例でいえばノード3はノード1やノード2から URL でアクセスできる場所に存在している必要がありますが、ノード1やノード2は他のノードから直接参照できる場所に存在している必要はなく、それぞれ別のネットワーク上に存在していても構いませんし、それがプライベートネットワークであっても構いません。この特徴によって HATOYA ではプライベートネットワークからブロックチェーンに参加することも可能になり、既存のネットワーク構成(の変更)を意識することなくブロックチェーンネットワークが実現しやすいという特徴があります:
2020081607



以上が HATOYA のブロックチェーンネットワーク構築の例となります。ノードの数が増えたり、その同期トポロジーや同期タイミングを複雑にしたり、一度作成したネットワークに新たにノードを追加するようなこともあると思っていますが、基本的には上述の応用でカバーできると思っています。

また HATOYA の場合、マルチノードネットワークにしておくと、サーバーノードの故障時や増強時などにブロックチェーンからデータベースをリストアするという特徴的な機能も実装済みだったりします。そのあたりはいずれ別の機会に紹介したいと思っています。

開発開始から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 機能を使ってブロックチェーンネットワークを構築し、マルチノードでブロックチェーン部分を同期しながら運用することも想定しています。そこにもいくつかの特徴的な機能もあって我ながら面白い作品だと思っていますが、そのあたりについてはまた別の機会に。。


facebook に URL を含めて投稿すると、そこは自動的にリンクになり、クリックすると指定された URL ページを開くことができます。ここまでは当たり前の話:
2020080206


2020080207


このリンク先の URL は正確には元の URL とは少し異なります。というのは(おそらく追跡目的で)元の URL に fbclid=XXXXXXXX というパラメータが付与されたURL がリンク先になります(XXXXXXXX 部分はその時によって異なるランダムな文字列です)。このパラメータはリンク先ではほとんどのケースで無視されるので実質的には何の違いも生じずに期待通りのページが開きます:
2020080208


さて、問題はこの facebook の仕様が期待通りの結果にならないケースがあるということです。その典型がウェブ上に公開されたノーツの各種ヘルプデータベースのページです。例えば bcom.com 様が公開している Lotus Domino Designer 8.5 のデザイナーヘルプは以下の URL で開きます:
https://www.bcom.co.jp/help/help85_designer.nsf/Main?OpenFrameSet

2020080209



※勘のいい人はこの時点で気づいているかもしれませんが、↑実はこの URL は少し特殊なフォーマットになっています。"?" のあとに URL パラメータとして "OpenFrameSet" が付与されています。が、一般的には URL パラメータは "key=value" という形になっているもので、この URL は key だけが指定された形になっています。とはいえ、これで正しく動くんですけど。。


さて、問題は上記のようなおかしな URL を facebook に貼ったときのリンクの挙動です。仕様的には fbclid=XXXXXXXX というパラメータが追加され・・・るはずなんですが、元の URL が少しおかしなフォーマットになっているせいか期待通りの形(https://www.bcom.co.jp/help/help85_designer.nsf/Main?OpenFrameSet&fbclid=XXXXXXXX)になってくれません。OpenFrameSet の直前の "?" がなぜか "!" に変換されて、https://www.bcom.co.jp/help/help85_designer.nsf/Main!OpenFrameSet?fbclid=XXXXXXXX という URL が facebook からリンクされます。そしてこのリンク先を開くとエラーとなってしまうのでした:
2020080201


さて普通にヘルプのページを facebook に貼っただけではリンク先がエラーになってしまう事実がわかりました。ではそこまで理解した上で該当の URL を facebook に正しく貼るにはどのようにすればいいでしょうか?

実は単純な解決方法があります。もともとのおかしな URL は key=value の key 部分だけが指定されているものだったので、無理やり value 部分を足してフォーマットしては矛盾のない形する方法で解決できそうでした。具体的には https://www.bcom.co.jp/help/help85_designer.nsf/Main?OpenFrameset=1 という URL を指定して facebook に貼る、という方法です。

こうすると facebook からのリンク先は https://www.bcom.co.jp/help/help85_designer.nsf/Main?OpenFrameset=1&fbclid=XXXXXXXX という矛盾のないフォーマットになり、この URL であれば問題なく開くことができるようでした:
2020080202



ノーツの各種ヘルプファイル以外にこのようなフォーマットの URL を使うケースがあるのか、また facebook 以外でこのような(パラメータ自動付与による)不都合が生じるサービスがあるのかどうかもよくわかっていないのですが、おそらくここで紹介したのと同様の方法で回避できると思っています。

このページのトップヘ