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

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

タグ:crud

CouchDBCloudant というデータベースに対してブラウザの JavaScript から(API サーバーなどを使わずに)直接アクセスできるような SDK を作ってみました。


CouchDB CRUD SDK について】
CouchDB は現在は Apache CouchDB としてオープンソースで開発されている JSON ドキュメント型の NoSQL データベースです。そして Cloudant はその CouchDB を IBM からのサポート付きで IBM Cloud から提供されているものです。要するに CouchDB と Cloudant は実体としては同じものです。ということもあって、以下では両方を指す言葉として "CouchDB" という表現をします(Cloudant を使う場合は "Cloudant" と読み替えてください):
2024090900


CouchDB には REST API が提供されています。したがって CORS に注意する必要はありますが、ウェブブラウザの JavaScript とこの REST API だけでデータベースやドキュメントの読み書き更新削除(=CRUD、Create Read Update Delete)といった処理そのものは可能です。ただ REST API を実行するのはパスやパラメータの指定で直感的に分かりにくい点があったり、検索方法や CouchDB の特徴ともいえる添付ファイル対応(バイナリデータ対応)の REST API は更に複雑だったりするので、私自身も使うたびに調べる必要に迫られたりと、少々使いにくい点があると感じていました。そういった使い勝手の点も含めて分かりやすい JavaScript での SDK を提供できないかと思いついて作ってみたものです。

作成した CouchDB CRUD SDK 自体は README.md 含めて GitHub で公開しています:
https://github.com/dotnsf/couchdb-crud-sdk


SDK の説明(英語)も GitHub に含まれる SDK.md で紹介していますが、せっかくなのでこのブログでは日本語で使い方を紹介します。


CouchDB CRUD SDK を使う前提条件】
前提条件というか、当然条件というか、使う対象となる CouchDB(Cloudant) サーバーが必要です。この CouchDB サーバーはインターネットからアクセスできる必要はありませんが、クライアントとなるウェブブラウザを使う PC がインターネットに接続していない場合は後述の CDN が使えないため、SDK の JavaScript ファイルをあらかじめダウンロードするなどしておく必要があります。

またここで用意する CouchDB サーバーはユーザー ID とパスワードで(ベーシック認証で)アクセスできるように構築されている必要があります。API キーを使う方法には未対応です。

自由に使える CouchDB サーバーを持っていない場合に備えて、以下では2つの方法で CouchDB サーバーを用意する方法を紹介します:

(1)docker のコンテナとして用意する
自分の PC に docker をインストールしてある場合であれば以下のコマンドを実行するだけで CouchDB サーバーを localhost:5984 で(ユーザーID=user、パスワード=pass で)動かすことができます:
$ docker run -d --name couchdb -p 5984:5984 -e COUCHDB_USER=user -e COUCHDB_PASSWORD=pass

(2)IBM Cloud のアカウントをお持ちであれば、lite plan という無料枠の範囲内で使える Cloudant サーバーを1アカウントにつき1つだけ使うことができます。ちなみに Cloudant の lite plan で使える容量は 1GB です(その他の条件は下図参照)。バイナリデータなどの膨大な容量のファイルを添付したりしなければ、そこそこ使えるサイズだと思っています:
2024090800


IBM Cloud 内の Cloudant サーバーを作成した場合のデータベースサーバーのホスト名、ユーザーID、パスワードなどの情報は「サービス資格情報」タブ内の下図の変数として確認することができます:
2024090802


なお、この方法で Cloudant サーバーを用意する場合は認証方法として "IAM and legacy credential" を選択してください(この "legacy credential" がベーシック認証です。"IAM" のみだとベーシック認証には非対応です)
2024090801


IBM Cloud のアカウントをこれから作る場合はこちらから登録してください(アカウント作成時にクレジットカードの登録が必要ですが、有料サービスを使わなければ課金対象にはなりません)。

もう1つの前提条件として外部の JavaScript から CouchDB サーバーを利用できるように CORS を正しく設定している必要があります。CouchDB の設定画面内で CORS タブから CORS が enalbed になっていることを確認してください。また CORS 対象のオリジンについては "All domains" を選択するか、"Restrict to specific domains" を選択した上で、この後動かすことになるウェブページのオリジン(URL の "スキーマ://ホスト:ポート" の部分)を指定してください。ここが正しく設定できていないと CORS の制約にかかって JavaScript SDK が正しく実行できなくなります:
2024090802


CouchDB CRUD SDK のロードと初期化】
実際にデータの読み書きをする前に SDK ライブラリをロードして初期化する必要があります。その流れを説明します:

まずブラウザ内に以下の1行を加えて、CDN の SDK ライブラリをロードします:
<script src="https://dotnsf.github.io/couchdb-crud-sdk/couchdb-crud-sdk.js"></script>

インターネットに接続していない環境で使う場合は上記 URL から couchdb-crud-sdk.js をあらかじめダウンロードして、HTML と同じフォルダにコピーするなどして(その場合は src="./couchdb-crud-sdk.js" と指定)対応してください。

次にユーザー ID、パスワード、CouchDB サーバーのベース URL を指定して CouchDB_CRUD_SDK クラスのインスタンスを初期化します:
var cdb = new CouchDB_CRUD_SDK( username, password, base_url );

これでインスタンス変数 cdb の初期化が完了しました。この変数を使うことで CouchDB サーバー内のデータを読み書きできます。


【CouchDB CRUD SDK を使ったデータベースの作成と削除と一覧取得】
上述の初期化まで完了していればインスタンス変数(cdb)のメソッドでリモートの CouchDB に対する各種処理が実行できます。例えば CouchDB のデータベースに関しては以下のようなメソッドが用意されています:
//. データベース一覧取得
var result0 = await cdb.readAllDbs();

//. データベース新規作成
var result1 = await cdb.createDb( 'newdb' );

//. データベース削除
var result2 = await cdb.deleteDb( 'newdb' );

後述のドキュメント向けメソッドについてもいえることですが、メソッドの実行結果は成功した場合は
{
  status: true,
  result: (実行した結果のオブジェクトや配列)
}

といった JSON が返されます。また失敗した場合は
{
  status: false,
  error: (失敗した原因の内容を示すオブジェクトや文字列)
}

という JSON になります。status の true/false で成功か失敗かを判断し、成功の場合は(必要であれば)result の内容を参照、失敗の場合は error の内容に失敗の理由が記載されているのでそれぞれ参照して処理することができます。 なお、cdb のメソッドは全て非同期(async)関数として定義されている点に注意してください。


【CouchDB CRUD SDK を使ったドキュメントの操作】
次にデータベース内のドキュメントに関しては以下のようなメソッドが用意されています:
//. データベース内のドキュメント一覧取得
var result3 = await cdb.readAllDocs( 'db' );

//. ドキュメント新規作成
var result4 = await cdb.createDoc( 'db', doc );

//. ドキュメント一件取得
var result5 = await cdb.readDoc( 'db', 'doc_id' );

//. ドキュメント更新
var result6 = await cdb.updateDoc( 'db', 'doc_id', doc );

//. ドキュメント削除
var result7 = await cdb.deleteDoc( 'db', 'doc_id' );

createDoc や updateDoc のパラメータで指定されている doc オブジェクトは
{
  _id: 'doc_id',
  name: "ジュース",
  price: 120
}

といった JSON オブジェクトです(_id の値はいわゆるドキュメント ID です。新規作成時であれば指定されていてもされていなくても構いません、指定がない場合は自動的に付与されます)。

CouchDB の特徴的な機能を使ったメソッドについても紹介しておきます。例えば以下のようなメソッドも用意されています:
//. 特定ドキュメントの全リビジョン取得
var result8 = await cdb.readAllRevisions( 'db', 'doc_id' );

//. 添付ファイルをドキュメントとして保存する
var result9 = await cdb.saveFile( 'db', 'doc_id', 'selector', 'filename' );

上は特定の ID ('doc_id')を持ったドキュメントの全リビジョンを取得するメソッドです。CouchDB はドキュメントの更新履歴が全て(自動的に)リビジョンとして記録されています。つまりドキュメントを新規作成し(リビジョン1)、一度更新して(リビジョン2)、更に更新(リビジョン3)した場合、最新データはリビジョン3ですが、過去のリビジョンも全て記録されていて、取り出すことができます。上の readAllRevisions() メソッドは特定ドキュメントの全リビジョンを一度の取り出すメソッドです。

下は添付ファイルを JSON データの一部として記録する場合のメソッドです。例えば
<input type="file" id="attachment_file"/>

のような HTLM 要素を使って添付ファイルを読み込もうとした場合であれば、'selector' は "#attachment_file" となります(jQuery などのセレクタだと思ってください)。'filename' は指定されていればそのファイル名で保存されます('filename' 指定がなかった場合は実際のファイル名で保存されます)。なお 'doc_id' が指定されている場合はその ID のドキュメントを更新し、'doc_id' が指定されていない場合は新規にドキュメントを作成します。 この関数を使って(バイナリデータなどの)添付ファイルもブラウザの JavaScript でリモートデータベースに格納/更新できるようになります。またこの添付ファイルの更新記録もリビジョンとして記録されます。

一応ここに挙げたメソッドで一通りの読み書き更新削除まではカバーしていると思いますが、その他全てのメソッドを(最新情報として)参照したい場合はこちらを参照ください(英語です):
https://github.com/dotnsf/couchdb-crud-sdk/blob/main/SDK.md


【CouchDB CRUD SDK のサンプル】
この CouchDB CRUD SDK を比較的汎用的に使ったサンプルアプリケーションを GitHub Pages に用意しておきました。docker コンテナであっても CouchDB/Cloudant 環境があれば試しにアクセスすることができます:
https://dotnsf.github.io/couchdb-crud-sdk/


上記 URL にアクセスすると以下のような画面が表示されます:
2024090901


上部に3つのテキストフィールドがあり、左からユーザーID、パスワード、CouchDB の URL (docker であれば http://localhost:5984 など)を入力し、最後に Login ボタンをクリックします:
2024090902


入力した内容が正しい場合は指定された CouchDB にアクセスし、画面左にデータベースの一覧が表示されます。またログインフィールドが画面から消えます(ログインフィールドが残っている場合はログインに失敗しています)。この画面から新しくデータベースを作成(Create DB)することもできます:
2024090903


データベースを1つ選択すると、そのデータベース内のドキュメント一覧が画面右側に表示されます。この部分からは選択したデータベースの削除(Delete DB)、ドキュメントの新規作成(Create Doc)や、既存ドキュメントの参照(Show Doc)、変更(Edit Doc)、削除(Delete Doc)といったアクションを実行できます:
2024090904


以下は "Create Doc" ボタンをクリックしてドキュメントを新規作成している画面です:
2024090905


ドキュメントの内容は JSON 形式であればどのようなフォーマットでも指定可能です。最後に "Save" で保存します:
2024090906


(わかりにくいかもしれませんが)新たに1つドキュメントが追加されました(赤枠):
2024090907


このドキュメントの横にある "Show Doc" ボタンをクリックするとドキュメントの内容が表示されます。実際に入力した JSON の内容に加えて、"_id" や "_rev" といったキーとその値が追加されているのがわかります。これらが(自動的に付与された)ドキュメント ID や、そのリビジョンです。同様にして編集や削除も可能です:
2024090908


このサンプルアプリでできることはこの程度ですが、ブラウザの JavaScript だけでリモートのデータベース内を読み書きできていることがわかると思います。もちろんデータベースを読み書きする REST API があれば(CORS の設定もできていれば)普通にできることではあるんですが、REST API を意識しなくても関数を呼び出すだけでできるようにしたのはまあまあ便利かなと自分でも思っています。API サーバーなしでも動くフロントエンドアプリケーションの SDK という位置付けであり、それが実現できているので現に GitHub Pages でこのサンプルが作れているわけです。

サンプルアプリの内容を見たい場合はこちらを参照ください(index.html, viewer.js, viewer.css の3つのファイルで作られているウェブアプリケーションです):
https://github.com/dotnsf/couchdb-crud-sdk/tree/main/docs


【最後に】
・・・というものを作って公開してみました。ローカルストレージではなくリモートデータベースをブラウザの JavaScript だけで(比較的簡単に)操作できることと、CouchDB の持つリビジョン管理機能や添付ファイル管理まで含めて操作できる点が特徴的なライブラリかな、と思っています。

CouchDB や Cloudant といったデータベースに馴染みのない人もいるかもしれませんが、この記事をきっかけに知ったり興味持ったりしてもらうことがあれば嬉しいです。


この記事の続きです:
開発者視点で「理想的なブロックチェーン」とは?


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

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


(追記)
マルチノード運用の記事を追加しました:
HATOYA をマルチノード運用して、ブロックチェーン情報を分散管理する


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


このページのトップヘ