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

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

タグ:db

先日、このようなブログを書きました:
"Image to Excel" を REST API 化(xlsx-js-style の使い方)


xlsx-js-style というライブラリを使って、ネットでプチバズった Image to Excel というツール(を REST API 化したもの)を独自実装した、という紹介エントリでした。

この REST API を作る上で xlsx-js-style というエクセルシートやエクセルブックファイルを Node.js で作成・編集するライブラリを使いました。Image to Excel を作るだけであればセルのサイズや背景色を設定する機能を使えばできるので(セルのテキストを読み書きする必要はなかったので)さほど難しくありませんでした。

というわけで、その勉強の続きという意味も含め、より実践的なセルの読み書きを伴うサービスを作ってみようと思い立ち、苦労しながらブログネタになりそうなものを作ることができたので紹介させていただきます。


【作ったサービスの内容】
一言でいうと「データベースとエクセルの相互変換ツール」です。データベースをエクセルファイルに、エクセルファイルはデータベースに、それぞれ変換して記録(保存)するツール。正確にはツールというよりも REST API として実装しているので、別の外部アプリケーションから使うことを想定しています(Swagger API の UI は標準で用意したので、単体でも動かしてみせることはできます)。

作ったサービスをもう少し詳しく説明します。まず「データベースをエクセルにする」部分から、現時点では PostgreSQL か MySQL だけで動くのですが、こんな感じのデータベース接続文字列を指定して API を実行します:
 (PosgreSQL の場合)postgres://username:password@servername:port/dbname
 (MySQL の場合)  mysql://username:password@servername:port/dbname


PostgreSQL の場合は最初のプロトコル名部分を "postgres" に、MySQL の場合は "mysql" と指定します。"username:password" 部分は接続する際のユーザー名(username)とパスワード(password)を指定します。servername はデータベースサーバー名(または IP アドレス)で、port は接続ポート番号です(PostgreSQL のデフォルト値は 5432 、MySQL のデフォルト値は 3306 です)。そして dbname は接続先のデータベース名を指定します。

例えば localhost の 5432 番ポートで動いている PostgreSQL サーバーの mydb データベースに、ユーザー名 user1 、パスワード pass1 で接続する場合は以下のようなデータベース接続文字列が必要となります:
 postgres://user1:pass1@localhost:5432/mydb


話を戻します。このようなデータベース接続文字列を指定して REST API を実行すると、以下のルールでデータベース内容をまるごと1つのエクセルファイルに変換します:
・データベース内の全テーブルと、各テーブル内のレコードデータを取り出す
・各テーブルが1枚のエクセルシート(タブ)になる テーブル名がシート名になる
・シートの1行目にテーブルの全列が書き出される
・シートの2行目以降にテーブル内の全レコードデータが1行ずつ格納される(ただし BLOB などのバイナリデータは無視する)
・処理が完了すると、(データベース名).xlsx という名前のエクセルファイルでダウンロードできる

という誰得(?)な処理を行います。 もう1つの「エクセルをデータベースにする」は基本的にこの逆の処理を行います:

・実行時のパラメータとして(上述の処理と同様に)データベース文字列を指定する
・併せてエクセルファイルをアップロードする形で REST API を実行する
・エクセルファイル内の全シートを1つのテーブル内容としてデータベースに格納する
 ・テーブル名はシート名
 ・各シートの1行目にテーブルの列名が格納されているとみなす
 ・各シートの2行目以降にテーブルのレコードデータが1行ずつ格納されているとみなし、
  テーブルに1行ずつ追加される(ただし BLOB などのバイナリデータは無視する)
・REST API 実行時のパラメータにより各シートの2行目にある情報はレコード情報ではなく、各列の定義情報とみなすこともできる。その場合、3行目以降にテーブルのレコードデータが格納されているとみなす。
・同様にして REST API 実行時のパラメータによりシート名の既存テーブルを削除(drop table)してから実行するか、新規作成(create table)してから実行するかを選択することができる
・処理が完了するとエクセル内の各シートに定義されていた情報がまるごとデータベース化される


【ソースコード】
作成したサービスのソースコードをこちらで公開しています:
https://github.com/dotnsf/db2xlsx


実際にネット上のどこかでサービスとして公開することも考えたのですが、現実問題としてデータベースがインターネット上に公開されていないと使えないので(普通、公開されていないはずなので)、公開しても使えるものではないと判断しています。 そういうこともあってのソースコードでの公開としました。

サービスとして自分の PC で使ってみたい場合は、Git と Node.js がインストールされた環境で以下の手順を実行してください:

(1)ターミナルやコマンドプロンプトなどの端末を開く

(2)ソースコードをダウンロード
$ git clone https://github.com/dotnsf/db2xlsx


(3)必要なライブラリをインストール
$ cd db2xlsx

$ npm install


(4)実行(終了時は Ctrl+C)
$ npm start


ただ単にサービスとして実行しても対象のデータベースやエクセルファイルが無いと使えないので、より具体的な手順を次のセクションで紹介します。


【Docker と Swagger API で実際に使う手順】
このサービスを実際に使うにはインポートやエクスポートの対象となるデータベースや、エクセルファイルが必要です(そういうサービスです)。エクセルファイルはサンプルを用意できるのですが、データベースはそうはいきません。 実際に使える PostgreSQL や MySQL のデータベースがあればそれをそのまま使っていただいてもいいのですが、そういう人も多くはないと思うので、以下に Docker を使ってデータベースを用意する手順を紹介します。というわけでまずは Docker(個人利用であれば Docker Desktop がお奨めですが、業務利用の場合は有料なので、無料を希望する場合は WSL などを使って Community Edition)をインストールしておいてください。


Docker で PostgreSQL データベースを1つ用意する場合は以下を実行します:
$ docker run -d --name postgres -p 5432:5432 -e POSTGRES_USER=user1 -e POSTGRES_PASSWORD=pass1 -e POSTGRES_DB=db1 postgres

↑このコマンドで作成した場合、ユーザー名 user1 、パスワード pass1 、データベース名 db1、ポート番号 5432 番で postgres という名前の PostgreSQL コンテナが起動します。この例だとデータベース接続文字列は "postgres://user1:pass1@localhost:5432/db1" となります。

データベースを2つ作る必要はないので PostgreSQL で作成した場合はそれを使えばいいのですが、どうしても MySQL を使いたい場合はこちらを実行します:
$ docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=P@ssw0rd -e MYSQL_USER=user1 -e MYSQL_PASSWORD=pass1 -e MYSQL_DATABASE=db1 -p 3306:3306 mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

この場合だとデータベース接続文字列は "mysql://user1:pass1@localhost:3306/db1" となります。ユーザー名部分やパスワードなどは適宜変更して読み替えてください。


この方法を含め、なんらかの方法で接続して読み書き可能なデータベースが用意できているものと仮定して以下の説明を続けます。


データベースが稼働している状態で上述の(4)までを実行して本ツールを起動します(本ツール起動後にデータベース起動でも構いません):
$ npm start

> db2xlsx@0.0.1 start
> node app.js

server starting on 8080 ...

↑の青字のような表示が出力されれば起動に成功しています。ツールは 8080 番ポートで稼働しているので、ウェブブラウザで http://localhost:8080/_doc にアクセスして Swagger API Document を開きます:
2023122201


↑のような画面が表示されます。この画面でも "POST /db2xlsx" と "POST /xlsx2db" の2つの REST API が存在していることが確認できます。名前の通りですが、それぞれ「データベースからエクセル」、「エクセルからデータベース」に変換する API になっています。


これらの API をそれぞれ使ってみましょう。順番としてはデータベースは空の状態であると想定して、
(1)まずエクセルファイルの内容をデータベースにインポート(POST /xlsx2db)して、
(2)そのデータベースの内容をエクセルファイルにエクスポート(POST /db2xlsx)する
という順に実行してみることにします。

まずはデータベースにインポートするエクセルファイルを用意します。独自のものを準備いただいてもいいのですが、すぐ使えるようなサンプルを用意しておきました:
https://dotnsf.blog.jp/items-brands.xlsx


↑このリンクからエクセルファイル items-brands.xlsx をダウンロードしてください。エクセルや互換ツールを使って開ける場合は内容を確認してみてください(自由に変更していただいても構いません):
2023122301

2023122302


この items-brands.xlsx ワークブックには items と brands という2つのシート(タブ)があります。それぞれ以下のようなデータが表形式で格納されています:

(items)
id name brand_id price body updated
varchar(20) primary key varchar(100) not null varchar(20) integer text timestamp
item001 商品1 brand001 100 商品説明1商品説明1商品説明1商品説明1商品説明1商品説明1商品説明1商品説明1商品説明1商品説明1商品説明1商品説明1 2023-12-20
item002 商品2 brand002 1200 商品説明2商品説明2商品説明2商品説明2商品説明2商品説明2商品説明2 2023-12-21
item003 商品3 brand003 500 商品説明3商品説明3商品説明3商品説明3商品説明3商品説明3商品説明3商品説明3商品説明3商品説明3商品説明3商品説明3商品説明3商品説明3 2023-12-22
item004 商品4 brand002 2980 商品説明4商品説明4商品説明4商品説明4商品説明4商品説明4 2023-12-23


(brands)
id name body url updated
varchar(20) primary key varchar(100) not null text varchar(100) timestamp
brand01 ブランド1 説明文1説明文1説明文1説明文1説明文1説明文1説明文1説明文1説明文1説明文1説明文1説明文1説明文1 https://www.brand1.com 2023-12-20
brand02 ブランド2 説明文2説明文2説明文2説明文2説明文2説明文2説明文2説明文2説明文2 https://www.brand2.net 2023-12-21
brand03 ブランド3 説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3 https://www.brand3.jp/ 2023-12-22


エクセルファイルが用意できたらこれをデータベースにインポートしてみましょう。先ほどのブラウザ画面内の "POST /xlsx2db" と書かれた箇所をクリックします:
2023122303


すると下のような隠れていた画面が展開されます。この API を実行する時に指定するパラメータに関する情報が表示されています。内容を確認し、そのまま実行したいので "Try it out" と書かれたボタンをクリックします:
2023122304


するとパラメータ部分が編集可能状態に切り替わります。ここに必要な情報を入力していきます:
2023122305


まず一番上の database_url パラメータは指定が必須になっています。ここにはデータベース接続文字列を入力します。上の方法で PostgreSQL データベースを起動した場合であれば、
 postgres://user1:pass1@localhost:5432/db1
また MySQL データベースを起動した場合であれば、
 mysql://user1:pass1@localhost:3306/db1
を指定してください(別の方法で起動したデータベースの場合はそのデータベースに接続するための接続文字列を指定してください)。


また一番下の「エクセルワークブックファイル」と書かれた file パラメータも必須入力です。ここは「ファイルを選択」ボタンをクリックしてから、先ほど用意したワークブックファイル(ダウンロードしたものを使う場合は items-brands.xlsx )を指定します。

それ以外の部分は必要に応じて指定するものですが、今回指定したワークブックファイルは各シートの2行目にテーブルの列定義が記載されています。このような場合(シートの2行目がレコード情報ではなく、列定義情報の場合)は col_attr パラメータに "1" という値を指定する必要があります(何も指定しない場合は2行目もデータレコードとみなされて insert into table が実行されます。またテーブルの各列は全て text 型として create table が実行されます)。

また今回は初回実行なのでまだデータベース内にテーブルは何も作成されていません。したがって create table を実行してから insert into table を実行する必要があります。その場合は create_table パラメータに "1" を指定してください。今回は指定しませんが drop_table パラメータに "1" をセットして実行すると create table の前に drop table が実行されます。定義の異なる同名のテーブルが作成されている可能性があって、そちらを削除してから実行する場合や、現在登録済みのデータを削除してから再実行したい場合は drop_table の方にも "1" を指定してください。

パラメータの指定が完了したら最後に青い "Execute" ボタンを実行すると xlsx2db 処理が実行されます:
2023122601


実行後、ボタンの下のほうに実行結果が表示されます。またこの REST API を実行した時のコマンド内容も表示されるので、この REST API を外部から利用する機会があれば参考にしてください:
2023122307


とりあえず実行は完了し、成功しているようです。 せっかくなので実際にデータベースにアクセスして成功した結果を確認してみましょう。

ターミナルやコマンドプロンプトなどの端末をもう1つ開いて、CLI からデータベースに接続します。上述のような Docker でデータベースを起動している場合はそのコンテナに直接ログインして調べることができます。

データベースが PostgreSQL の場合は、以下のようなコマンドで確認できます(赤字が入力内容、青字は説明用のコメントです。実際に入力しないでください):
$ docker exec -it postgres bash (データベースが動いているコンテナにログイン)

/# psql "postgres://user1:pass1@localhost:5432/db1" (接続文字列を指定して psql コマンドでログイン)

db1=# \dt (テーブル一覧を表示、brands テーブルと items テーブルが作成されていることを確認)

        List of relations
 Schema |  Name  | Type  | Owner
--------+--------+-------+-------
 public | brands | table | user1
 public | items  | table | user1

db1=# select * from brands; (brands テーブルの内容を確認し、エクセルに入力されていた内容が格納されていることを確認)

   id    |    name    |
           body
   |          url           |       updated
---------+------------+--------------------------------------------------------
-------------------------------------------------------------------------------
---+------------------------+---------------------
 brand01 | ブランド1 | 説明文1説明文1説明文1説明文1説明文1説明文1説明文
説明文1説明文1説明文1説明文1説明文1説明文1
  | https://www.brand1.com | 2023-12-20 00:00:00
 brand02 | ブランド2 | 説明文2説明文2説明文2説明文2説明文2説明文2説明文
説明文2説明文2
  | https://www.brand2.net | 2023-12-21 00:00:00
 brand03 | ブランド3 | 説明文3説明文3説明文3説明文3説明文3説明文3説明文
説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文3説明文
 | https://www.brand3.jp/ | 2023-12-22 00:00:00
(3 rows)

db1=# select * from items; (items テーブルも同様に確認する)

(略)

db1=# \q (psql からログアウトする)

/# exit (コンテナからもログアウトする)

データベースが MySQL の場合も、コマンドが少し異なりますが同様に確認できます:
$ docker exec -it mysql bash (データベースが動いているコンテナにログイン)

# mysql -u user1 -ppass1 db1 --default-character-set=utf8mb4" (接続情報を指定して mysql コマンドでログイン)

mysql> show tables; (テーブル一覧を表示、brands テーブルと items テーブルが作成されていることを確認)

+--------------+
| Tables_in_db |
+--------------+
| brands       |
| items        |
+--------------+
2 rows in set (0.01 sec)

mysql> select * from brands; (brands テーブルの内容を確認し、エクセルに入力されていた内容が格納されていることを確認)

(略)

mysql> select * from items; (items テーブルも同様に確認する)

(略)

mysql> quit (mysql からログアウトする)

# exit (コンテナからもログアウトする)

エクセルファイルをアップロードして、データベース化できることが確認できました。


では次に「データベースからエクセル」も試してみましょう。上の作業でエクセルファイルからデータベースを作成した直後だと、アップロードしたものと同じエクセルファイルが作成されるだけ(苦笑)ではあるのですが、データベースの内容がエクセルになる、という部分については体験できると思います。

Swagger API ドキュメントに戻り、今度は "POST /db2xlsx" のボタンをクリックします:
2023122301


API 実行時のパラメータが展開されます。今回は "POST /xlsx2db" の時よりもシンプルなパラメータになっていることがわかります。"Try it out" ボタンをクリックします:
2023122302


こちらの API ではエクセルにエクスポートしたいデータベースを database_url パラメータにデータベース接続文字列の形で指定するだけです。先ほどと同様のデータベース接続文字列を入力して "Execute" ボタンをクリックします:
2023122303


処理が成功すると "Download file" と書かれたリンクが現れます。ここをクリック:
2023122304


すると、(データベース名).xlsx というエクセルファイルがダウンロードされます。ダウンロード後に開いてみてください:
2023122305


ダウンロードしたエクセルファイルを開いてみると、items と brands という2つのワークシート(タブ)が含まれていて、それぞれのワークシート内に items テーブルと brands テーブルの内容がエクスポートされていることが確認できます。日付列は日付情報として取り出しているので少しフォーマットが変わっていますが中身(日付は)正しいはず:
2023122306

2023122307


データベースからエクセル、の REST API も正しく動くことが確認できるはずです。


【あとがき的なもの】
一部でエクセルがデータベースの代わりに使われていることを知っている身として、その状況はなんとかならんかという思いで作ってみました。REST API 化したので簡単なプログラムを書けば一括処理もできるはずです(笑)。で、どうしてもまたエクセル化する必要が生じた場合にも対応できるような逆方向の REST API も用意しているので、これでなんとかしてください。


 

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


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


オラクルが、正規ライセンス購入前の勉強目的で試しにソフトウェアを利用することのできるライセンス『OTN(Oracle Technology Network)開発者ライセンス』を提供していることを今更ながら知りました:
自分用Oracle DBを「無償で」作ろう!「OTN開発者ライセンス」

※同じく無料版である IBM Db2 Express-C みたいな位置付け?と思っていたのですが、ちと違いそう。Express-C は機能としては Express Edition に近いのですが、開発者エディションというわけではありません。「サポートなしで本番利用可」なのが IBM Db2 Express-C であり、この辺りが違いと言えそうです。

※むしろ IBM Db2 Developer Community Edition の位置付けが近い感じ。


利用するには Oracle アカウントの登録が必要ですが、登録自体は無料でした。各種オラクル製品がダウンロード可能で、オラクルDBはもちろん、Java や MySQL などの製品版も対象に含まれていました。利用目的は制限されていますが、仕様、検証、開発、テストに使うことはできるとのこと。早速使ってみました。なんと 20年以上ぶりにオラクル DB をインストールしてみた顛末です(笑)。


まずは OTN ページにアクセスして "Sign In" します:
2017072101


自分の OTN ID とパスワードを指定して「サインイン」します:
2017072102


サインイン後、"Software Downloads" をクリックしてダウンロードページに移動します:
20170721025


ダウンロードページをスクロールすると、ダウンロード対象の製品一覧が見つかります。オラクルDBも新しいエディションである 12c の Enterprise/Standard Edition 含めてダウンロードできますが、自分は比較的軽量な方がいいので 11g の Express Edition を選択しました:
2017072103


オラクルDB Express 11g2 のダウンロードページです。License Agreement にチェックを入れます:
2017072104


そして対象プラットフォーム(下図は Linux x64 を選択。他に Windows 32/64 bit 版を選択可)を選ぶとダウンロードが開始されます:
2017072105


ダウンロードできれば後はインストールするだけですが、Linux 版のオラクルDB Express 11g2 の場合、インストールするにはメモリが 2GB 必要です(足りないとインストーラーが止まります)。充分なスワップメモリが確保されていない環境の場合は動的にスワップファイルを増やす必要があります。その方法や手順は以下を参照ください:
Linux の SWAP 領域を増やす


改めて Linux 版の場合は zip された rpm ファイルがダウンロードされます。なので 64bit Linux 上でダウンロードしたファイルを unzip し、Disk1 フォルダ以下にある rpm ファイルを指定してインストールします:
# unzip oracle-xe-11.2.0-1.0.x86_64.rpm.zip
# cd Disk1
# rpm -ivh oracle-xe-11.2.0-1.0.x86_64.rpm
準備中...                ########################################### [100%]
   1:oracle-xe              ########################################### [100%]
Executing post-install steps...
You must run '/etc/init.d/oracle-xe configure' as the root user to configure the database.


「root で /etc/init.d/oracle-xe configure を実行しろ」というメッセージが表示されているので、このコマンドを実行して初期セットアップを実行します。セットアップ時の選択肢は全てデフォルトのままで、明示的に指定したのはパスワード(と確認パスワード)だけです:
# /etc/init.d/oracle-xe configure

Oracle Database 11g Express Edition Configuration
-------------------------------------------------
This will configure on-boot properties of Oracle Database 11g Express
Edition.  The following questions will determine whether the database should
be starting upon system boot, the ports it will use, and the passwords that
will be used for database accounts.  Press <enter> to accept the defaults.
Ctrl-C will abort.

Specify the HTTP port that will be used for Oracle Application Express [8080]:

Specify a port that will be used for the database listener [1521]:

Specify a password to be used for database accounts.  Note that the same
password will be used for SYS and SYSTEM.  Oracle recommends the use of
different passwords for each database account.  This can be done after
initial configuration: (パスワード指定)
Confirm the password: (確認パスワード指定)

Do you want Oracle Database 11g Express Edition to be started on boot (y/n) [y]:

Starting Oracle Net Listener...Done
Configuring database...Done
Starting Oracle Database 11g Express Edition instance...Done
Installation completed successfully.


これでセットアップ完了、、のはず、ですが、この後の作業を便利に行うための追加作業をしておきます。オラクル DB をコマンドラインから操作する場合に便利な環境変数が /u01/app/oracle/product/11.2.0/xe/bin/oracle_env.sh(および /u01/app/oracle/product/11.2.0/xe/bin/oracle_env.csh)コマンドによって設定されます。なので、/etc/bashrc などに以下の1行を追加して、対象ユーザーがログインする際にこの環境変数設定が行われるようにしておきます:
. /u01/app/oracle/product/11.2.0/xe/bin/oracle_env.sh

改めてログインし直して(この環境変数設定を有効にして)、SQL*Plus を実行し、system DB に対して SQL を実行してみます:
# sqlplus system

SQL*Plus: Release 11.2.0.2.0 Production on 土 7月 22 01:54:50 2017

Copyright (c) 1982, 2011, Oracle.  All rights reserved.

パスワードを入力してください:


Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production
に接続されました。
SQL> select * from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production
PL/SQL Release 11.2.0.2.0 - Production
CORE    11.2.0.2.0      Production
TNS for Linux: Version 11.2.0.2.0 - Production
NLSRTL Version 11.2.0.2.0 - Production

SQL>

動きましたー! 懐かしい(Solaris の前の)SunOS 以来のオラクル DB インストール体験でした。いやあ、再びオラクルをインストールする日が来るとは・・・

ワードプレスのインポートプラグインを使わず、データベース内のテーブル(wp_posts とか、wp_terms とか、・・)に直接 SQL を実行して、記事をインポートする方法を紹介します。単なる記事だと(簡単すぎて)つまらないので、カテゴリ付きの記事の、そのタイトルと本文をインポートすることに挑戦しました。 なお、ワードプレスのデータベーステーブルプリフィックスは設定によって変更可能ですが、以下はデフォルトである 'wp_' に設定されているものとして紹介します。


【記事のインポート】
単純な記事のインポートだけであれば wp_posts テーブルに post_status = 'publish', post_type = 'post' でデータを挿入するだけです(必要に応じて post_date や post_author なども):
insert into wp_posts( post_content, post_title, post_status, post_type ) values( '(本文)', '(タイトル)', 'publish', 'post' );

単純に記事を公開状態でインポートするだけなら、必要な記事のぶん上記コマンドを繰り返して実行するだけです。以下はカテゴリ情報を有効にする場合の追加作業です。


この SQL を実行した結果、auto_increment なプライマリキーである ID 値が確定します。この ID 値は記事のカテゴリーを指定する際に必要になるので取得しておきます。
select last_insert_id() as ID from wp_posts;


【カテゴリーのインポート】
記事にカテゴリを付与する手順は少し複雑です。

まずカテゴリの名称(と slug)を wp_terms テーブルに挿入します:
insert into wp_terms( name, slug ) values( '(カテゴリー名)', '(カテゴリー名を urlencode したもの)' );

この SQL を実行した結果、auto_increment なプライマリキーである term_id 値が確定するので、この値を取得します:
select last_insert_id() as term_id from wp_terms;

この term_id が所得できたら、次は wp_term_taxonomy テーブルにこの term_id と、term_id が示すカテゴリの記事が何件あるのかという情報を taxonomy 情報(='category')と一緒に wp_term_taxonomy テーブルに挿入します:
insert into wp_term_taxonomy( term_id, taxonomy, count ) values( (term_id の値), 'category', (このカテゴリに属する記事数) );

この SQL を実行した結果、auto_increment なプライマリキーである term_taxonomy_id 値が確定するので、この値を取得します:
select last_insert_id() as term_taxonomy_id from wp_term_taxonomy;

term_taxonomy_id が取得できたら、最初に取得した記事の ID 値と、カテゴリを示す term_taxonomy_id 値との組み合わせを wp_term_relationships テーブルに挿入します:
insert into wp_term_relationships( object_id, term_taxonomy_id ) values( (ID の値), (term_taxonomy_id の値) );

ちとややこしいですが、ここまでの作業でカテゴリに紐付いた記事のインポートが実現できます。ワードプレスDBのテーブルスキーマとの関連だとこんな感じになります:
2017072000


まとまったデータコンテンツをワードプレスに移行する、という場合は上記の作業を必要なだけ繰り返し自動的に行うようなツールを(プログラミングで)作ることになります。本文&タイトル&カテゴリ程度が決まっている単純構成であれば、(それらのデータを取り出す部分さえ作れれば)自動化もそんなに難しくないと思ってます。


Apache Derby(或いは "Cloudscape")という RDB をご存知でしょうか?
derby-logo-web


最近は SQLite や HTML5 のローカルデータストアの台頭であまり名前を聞かなくなりましたが、Pure Java で記述された軽量の RDB です。元々は Cloudscape Inc. によって開発されましたが、Informix Software を経て IBM 製品として扱われていた時代もあります。その影響もあってか "DB2 互換 SQL" に対応し、DB2 の SQL が動く軽量の Java RDB という立ち位置でした。軽量であるが故に組み込み系のアプリケーション内で使われることが多いようです。歴史的には 2004 年に IBM から Apache 財団へソースコードが寄贈され、現在の Apache Derby という名称のプロジェクトになりました。また Oracle JDK 1.6 以降に(オプションとして)組み込まれている JavaDB の実装はこの Apache Derby です。

私自身は "Cloudscape" と呼ばれていた頃に使ったことがありました。今回、久しぶりに Apache Derby を使ってみました。

JDK のオプションに組み込まれているとはいえ、せっかくなので最新版を使ってみることにしました。まずは Apache Derby のダウンロードページから最新バージョン(2016/Oct/07 時点では 10.12.1.1)のリンクをクリックします:
2016100601


最新版のアーカイブファイル:db-derby-(バージョン番号)-bin.zip をクリックしてダウンロードします:
2016100602


ダウンロードした zip ファイルを展開し、lib フォルダ内の必要な JAR ファイルを取り出します。今回は本体である derby.jar と、日本語ロケールが含まれた derbyLocale_ja_JP.jar の2ファイルを取り出します:
2016100603


この2ファイルを Java の開発環境から使えるようにします。J2SE/EE プロジェクトであれば、WebContent/WEB-INF/lib 以下にコピーするなどして、コンパイル/実行時に参照できるようにしておきます:
2016100604


試しに以下のような index.jsp ファイルを用意してみました:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page import="java.sql.*" %>
<%@ page import="me.juge.derby.*" %>
<%
  request.setCharacterEncoding("utf-8");
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<title>Derby JDBC Sample</title>
</head>
<body>

<table border="1">
<tr><th>ID</th><th>NAME</th><th>PRICE</th></tr>
<%
final String driverName = org.apache.derby.jdbc.EmbeddedDriver.class.getCanonicalName();
final String dbName = "derbydb";
final String connURL = "jdbc:derby:" + dbName + ";create=true";  //. DBが存在していない場合は作成するオプション

try{
  Class.forName( driverName );
  Connection conn = null;

  try{
    conn = DriverManager.getConnection( connURL );
  }catch( Exception e ){
  }

  if( conn != null ){
    ResultSet rs = null;
    //. 初期化
    try{
      //. 試しに items テーブルにアクセス
      Statement s0 = conn.createStatement();
      rs = s0.executeQuery( "select count(*) from items" );
    }catch( SQLException e ){
      //e.printStackTrace();

      String state = e.getSQLState();
      if( state.equals( "42X05" ) ){
        //. テーブルが存在しない
        try{
          Statement s1 = conn.createStatement();
          s1.execute( "create table items("
            + " id int generated always as identity primary key"  //. この辺りが DB2 互換 SQL
            + ", name varchar(100)"
            + ", price int"
            + " )");

          PreparedStatement s2 = conn.prepareStatement( "insert into items( name, price ) values( ?, ? )" );
          s2.setString( 1, "シャンプー" );
          s2.setInt( 2, 1000 );
          s2.execute();

          PreparedStatement s3 = conn.prepareStatement( "insert into items( name, price ) values( ?, ? )" );
          s3.setString( 1, "石鹸" );
          s3.setInt( 2, 500 );
          s3.execute();
        }catch( Exception e1 ){
        }
      }else if( state.equals( "42X14" ) || state.equals( "42821" ) ){
        //. テーブル定義が不正

      }else{
        //. その他の想定外の例外

      }
    }finally{
      if( rs != null ) rs.close();
    }

    //. レコード表示
    try{
      rs = conn.createStatement().executeQuery( "select * from items" );
      while( rs.next() ){
        int id = rs.getInt( 1 );
        String name = rs.getString( 2 );
        int price = rs.getInt( 3 );
%>
<tr><td><%= id %></td><td><%= name %></td><td><%= price %></td></tr>
<%
      }
    }catch( SQLException e ){
    }finally{
      if( rs != null ) rs.close();
    }
  }
}catch( Exception e ){
  e.printStackTrace();
}
 %>
</table>
</body>
</html>

コードそのものは一般的な JDBC プログラミングです。DB を開く際に目的の DB が存在していない場合はその場で作成するようなオプション("create=true")を付与しています。また items というテーブルのレコード数を取得して、エラーが出るようであればテーブルが存在していないと判断し、新規に items テーブルを定義して作成し、2つほどデータを insert するようにしています。 最終的には items テーブル内の全レコードを画面に出力する、という内容にしています。


このプロジェクトを実行して、index.jsp にブラウザでアクセスすると以下のような表形式で2つのレコード内容が確認できます:
2016100701

まあ普通に RDB として使うこともできますが、組み込み系以外であればフットプリントの小ささからローカルレプリカ DB として利用する、というケースも考えられます。特にサーバー側が DB2 だったりすると SQL 互換が便利に使えたりしますね。

久しぶりに Cloudscape を使ってみました。相変わらずですが( Java が動いている前提はありますが)JAR ファイル置くだけで使える RDB は便利ですね。



このページのトップヘ