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

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

タグ:npm

IBM Domino 10 の新機能の1つで、個人的にすごく楽しみにしていた domino-db パッケージを遅ればせながら使ってみました。

このパッケージについて簡単に説明します。IBM Domino サーバーのアプリケーションデータベースにアクセスする方法は何通りかありますが、一般的にはこれまで以下の3通りありました:

(1) 専用クライアント(IBM Notes)を使う
(2) サーバー側で HTTP タスクを有効にした上で、ブラウザなどの HTTP クライアントからアクセスする
(3) IBM Notes が導入されたシステムから C/C++ や Java、ActiveX でプログラミングを行ってアクセスする

(1) はごくごく一般的な、いわゆる「ノーツ」を使ってアクセスする方法で、(2) はブラウザを使ってアクセスします。この2つはプログラミングというよりは、普通に提供されている機能です。(3) はプログラミングによってアプリケーションやマクロを作成してアクセスする方法なのですが、そのための SDK やインターフェースは同システム内にノーツが導入されている前提で提供されているものでした。つまりプログラミングで IBM Domino サーバーにアクセスする場合、IBM Notes クライアントがインストールされていることが前提条件であり、IBM Notes がインストールされていない(またはインストールできない)環境からはプログラミングしてもネイティブコードが存在しないので実行できない、という制約があったのでした。


このたび IBM Domino 10.0 から提供された App Dev Pack という拡張機能によって、この制約がなくなりました。つまり、

(4) App Dev Pack で提供される domino-db パッケージを使ってプログラミングし、Node.js からアクセスする

という4つ目の選択肢が新たに与えられたことになります。なお App Dev Pack は IBM Domino 10.0.1 と同時に提供が開始されました。またこの段階では Linux 版の App Dev Pack のみが提供されています。詳しくは以下でも紹介しますが、2019/01/14 時点では Linux (CentOS7/RHEL7)版の IBM Domino 10.0.1 サーバーと、Linux 上の Node.js 環境で動作します(2019/01/18 修正 domino-db パッケージは Linux 以外でも動くようです。少なくとも Windows からは動きました)

この App Dev Pack を実際に使って試してみました。今回は以下のような2台の Linux システム間で実際に動かしています:
2019011302


【IBM Domino サーバー】
OS: CentOS 7
Domino: 10.0.1

【クライアント】
OS: Ubuntu 16.04
Notes: インストールせず
Node.js: 8.11

今回は同一ネットワーク上に上記2台の Linux マシンを構築していますが、実際にはクライアントから IBM Domino サーバーに(名前とポート番号指定だけで)アクセスすることが可能であれば、インターネット越しでも可能だと思っています。


【環境準備】
CentOS 7 上に IBM Domino 10.0.1 環境を構築します。こちらの手順はすぐ下に記載した部分以外は省略します(私がこちらを参照したわけではないのですが、詳しくはこちらに記載された方法で導入できるようです)。なお Linux 版の Domino 10.x は CentOS/RHEL 7 以上でないとインストールできません。

ただ上記リンク先に書かれていなくて、ちと注意が必要な点があるのでそこだけ追記しておきます。

後述の環境では(Proton の動作ポートが動的な設定のままでも動くように)インフラ部分のセキュリティレベルをかなり緩めに作りました。具体的には SELinux を無効にし、更にファイアウォール(firewalld)も止めている、という点をコメントしておきます。具体的なコマンドとしては IBM Domino インストール後に root でログインして以下を実行しました:
(firewalld を停止)
# systemctl stop firewalld

(firewalld の無効化)
# systemctl disable firewalld

(SELinux の設定ファイルを編集)
# vi /etc/selinux/config

(SELINUX=enforcing とあった行を以下のように更新して、保存)
SELINUX=disabled

(システム再起動)
# shutdown -r now

これでファイアウォールや実行ポート番号を意識せずに以下の記述内容が実行できる環境にしています。



そして上記の環境を使い、CentOS 7側には Proton というサーバータスクを、Ubuntu 側には domino-db パッケージをそれぞれ追加導入しています。つまりシステム的にはこんな感じで Proton と domino-db パッケージを経由して通信しています:
2019011303


以下、それぞれの追加モジュールについて紹介します。

まずクライアント側に domino-db という npm パッケージを用意します。このパッケージが IBM Domino とのアクセスを提供しており、Node.js を使ったプログラム内で IBM Domino のデータベースを読み書きすることができるようになります。なお 2019/01/14 時点で提供されている domino-db パッケージのバージョンは 1.1.0 であり、Linux 版のみが提供されているようでした。

またサーバー側にも Proton というアプリケーション(正確にはサーバータスク・アプリケーション)が必要です。この Proton が前述の domino-db を使ったアプリケーションからのリクエストやレスポンスに対応します。なお 2019/01/14 時点で提供されている Proton のバージョンは 0.2.2 であり、こちらも Linux 版のみが提供されているようでした。

これら2つのモジュールが互いに通信しあって Node.js プログラムからの IBM Domino へのリクエスト制御やデータベースの読み書きに対応します。


実際にこれらをインストールする手順を紹介します。まず Domino 10 のインストールモジュールをダウンロードした IBM サイトなどから IBM Domino AppDev Pack をダウンロードします。2019/01/15 時点でのバージョンは 1.0 で Linux English 版のみ、 DOMINO_APPDEV_PACK_1.0_LNX_EN.tgz というファイル名でした:
2019011502


このアーカイブファイルを展開すると、以下のようなファイル群が表れます。なお、今回以下で紹介するものの中で使っているのは青のファイルのみで、これらのファイルが /tmp 以下に展開されているものとします:
- LICENSE
- NOTICE
- domino-appdev-docs-site.tgz : ドキュメント
- domino-domino-db-1.1.0.tgz : domino-db パッケージ
- domino-iam-service-1.0.15.tgz : IAM 認証用モジュール
- domino-node-iam-client-1.0.22.tgz : IAM 認証用の Node.js クライアントモジュール
- node-demo.nsf : デモ用アプリケーション
- oauth-dsapi-0.2.2.tgz : OAuth 用モジュール
- proton-addin-0.2.2.tgz : Proton 一式

なお、以下で紹介する内容は domino-appdev-docs-site.tgz を展開したドキュメント内にかかれている内容をベースに独自にカスタマイズしたものです。以下ではこのファイルを参照することはありませんが、domino-db パッケージを使ってアプリケーション開発をする場合の API リファレンスなども含まれているので、こちらは必ず手元で参照できるようにしておきましょう:
2019011501


まずはサンプルで使うドミノデータベース node-demo.nsf をデータフォルダに移動させます。いったん IBM Domino サーバーをシャットダウンし、notes ユーザー(Linux 内で IBM Domino を実行するユーザー)でログインします。そして node-demo.nsf をデータフォルダ直下にコピーしておきます:
$ cd /local/notesdata

$ cp /tmp/node-demo.nsf .

$ chown notes.notes node-demo.nsf

なお、このデータベースに特別な設計が含まれているわけではありません。ただし、今回の設定では Anonymous ユーザーでこのデータベースにアクセスして文書を作成することになるので、Anonymous ユーザーで文書を読み書きができるような権限設定が必要です。他のデータベースを使う場合も同様です。

次に IBM Domino 10 サーバーに Proton を導入します。notes ユーザーで以下のコマンドを実行してファイルコピー&セットアップを行います:
Domino バイナリのあるフォルダに移動
$ cd /opt/ibm/domino/notes/latest/linux

libnotes.so が存在していることを確認
$ ls -la libnotes.so

Proton ファイルをこのフォルダ内に展開
$ sudo tar -xzvf /tmp/proton-addin-0.2.2.tgz

setup_proton.sh を使ってセットアップ
$ sudo sh -v ./setup_proton.sh

この時点で Proton を起動することは可能ですが、デフォルト状態のままだと同一システムからのリクエストのみ受け付けます。今回は外部の別マシンから Node.js アプリケーションを実行したいので、外部リクエストを受け付けるための設定を追加する必要があります。

そのため、データフォルダ(/local/notesdata/)以下の notes.ini ファイルをテキストエディタで開き、以下の1行を追加します:
PROTON_LISTEN_ADDRESS=0.0.0.0

PROTON_LISTEN_ADDRESS は Proton へのリクエストを受け付けるホストの IP アドレスです。デフォルトは 127.0.0.1 なので同一システムからのリクエストのみ受け付けます。上記の 0.0.0.0 は全ての外部ホストからのリクエストを受け付ける、という設定です。

ここで1つ注意を。実は Proton の実行ポート番号は動的に決まるのですが notes.ini に以下の一行を追加することで実行ポート番号を指定できることになっています(この例の場合は 6000 番ポート):
PROTON_LISTEN_PORT=6000

が、私が試した限りでは、この1行を追加すると Proton 自身が起動に失敗するようになってしまいました。原因はよくわからないのですが、先に進めるため、今回は上記の設定はしていません。つまり Proton は起動時に空きポートを探して動的なポートで起動されるようにしています。

ここまでの設定ができたら IBM Domino サーバーを起動し、更に起動後のサーバーコンソールに以下を実行して Proton サーバータスクを実行します:
> load proton

するとコマンドに続いて以下のような実行ログが表示されます:
> load proton

PROTON> Build 0.2.2
PROTON> Listening on 0.0.0.0:1217, INSECURE
PROTON> Note: Requested port was 0, Actual listen port is 1217
PROTON> Server initialized
PROTON> Server allows Anonymous access only.
  :

この場合の例では Proton は 1217 番ポートが空いていることを確認し、1217 番ポートでリクエストを待ち受けている状態になりました。この番号は後で使うので覚えておきましょう。

これで IBM Domino サーバー側の設定は終わりです。実際には通信に SSL を使ったり、IAM による認証や権限管理を行ったりする設定もできるようですが今回は試していません。興味ある方はドキュメントを参照して挑戦してみてください。


次にクライアント側である Ubuntu システムのセットアップに移ります。まず Ubuntu に Node.js を導入します。ドキュメントによると Node.js V8.x で動作確認を行っている模様なので、今回も Node.js V8 を導入することにします(V10.x でも動くらしい、とは書かれていました、念の為)。多くの環境で Node.js を導入するとデフォルトで V8.x になると思っていますが、6以下だったり10以上だったりする場合は nvm などを使って Node.js V8.x (と npm)が動く環境を用意してください。

そして AppDev Pack 内に含まれている domino-db パッケージを転送するなどして、Ubuntu 側にコピーしておきます(以下の紹介ではカレントフォルダ内に domino-domino-db-1.1.0.tgz が存在しているものとします)。これでアプリケーション開発の準備は完了です。

チュートリアルをベースに、以下のような Node.js プログラムコード(test01.js)を記述します:
//. test01.js

//. domino-db のロード
const { useServer } = require( '@domino/domino-db' );

//. Domino サーバー(192.168.1.100)と Proton の実行ポート(1217)を指定
const serverConfig = {
  hostName: '192.168.1.100',
  connection: {
    port:1217
  }
};

//. 対象とするデータベース
const databaseConfig = {
  filePath: 'node-demo.nsf'
};

//. データベースに新規に作成する文書の内容
const createOptions = {
  documents: [
    {
      Form: 'Contact',
      Firstname: 'Kei',
      LastName: 'Kimura',
      City: 'Funabashi',
      State: 'Chiba'
    }
  ]
};

//. domino-db を使って Proton に接続
useServer( serverConfig ).then( async server => {
  //. 接続に成功したら処理対象データベースを指定
  const database = await server.useDatabase( databaseConfig );

  //. 設定した内容で文書を作成
  const response = await database.bulkCreateDocuments( createOptions );

  //. 作成した文書の unid を取得
  const unids = response.documents.map( doc => doc['@unid'] );

  //. 作成した文書の unid をコンソールに表示して終了
  console.log( `Documents created: ${unids}` );
});

Node.js に慣れている人であればなんとな~く処理内容は理解できると思います。1点だけコメントすると、上記の青字部分で指定した内容でデータベース内に新規文書を作成します。上記例では documents は配列になっていますが、その要素は1つだけです(つまり1文書だけ作成します)。配列要素の中身は作成する文書の各フィールドとその値を指定します。つまり今回の例では以下のようなフィールドとその値を指定してノーツの文書を新規に作成することになります:
フィールド名フィールド値
FormContact
FirstNameKei
LastNameKimura
CityFunabashi
StateChiba


また documents は配列なので、ここに2つ以上のオブジェクトを指定することも可能です。その場合は2つの文書がバルクインサートで作成され、結果の unid も2つ得ることができます。 ただ今回は1文書のみ作成する前提で上記コードが記述されている点にご注意ください。

このコードを実行してみます。まず上記で作成した test01.js ファイルがあるフォルダで domino-db パッケージを npm install します。domino-db はまだ npmjs に登録されているわけではなく、あくまでインストールモジュールから導入する必要があります。したがって以下のようなコマンドでローカルファイルシステムの同一フォルダにある domino-domino-db-1.1.0.tgz を指定してインストールします:
$ npm install ./domino-domino-db-1.1.0.tgz --save

domino-db のインストールができれば test01.js を実行することができます。また実行結果には作成された文書の UNID が表示されます:
$ node test01

Documents created: 5F29E1B7FD62450649258383005083FE

実際に Domino 10 サーバー上の node-demo.nsf ファイルを開いてみると、All Names ビューから Name が Kei Kimura となった文書が追加されていることが確認できます:
2019011301


この文書を開いてみると、入力した通りの内容で文書が作成されていることが確認できます。本当に Notes/Domino の導入されていないシステムから domino-db パッケージを使ってデータベース内に文書を作成することができました:
2019011503


本当に domino-db パッケージ(と Node.js)だけでリモートの IBM Domino データベースに文書を作成することができちゃいました。今はサーバー側もクライアントも Linux 環境でないとできない(2019/01/18 修正 サーバー側が Linux 環境でないとできない)、という制約がありますが、いずれ他のシステムでもこの機能が提供される(勝手にそう思ってますが・・)と超便利だと思いました。

1点だけ、Proton の実行ポート番号を notes.ini で指定すると動かなくなる、という現象は自分だけなのでしょうか?この辺りはまだ情報があまりなく、うまい回避方法があるといちいち実行ポートをコンソールで確認する必要がなくなって便利なんだけどな・・・ 情報求む。


IBM Cloud から提供されている IBM CloudantApache CouchDB をベースとしたマネージドな NoSQL データベースのサービスです:
2018090300


ベース製品が同じなので、例えば REST API レベルでは互換性があります。注意が必要な点として自分が気づいた限りでは IBM Cloudant は標準で Apache Lucene ベースの検索機能が有効になっており、インデックスとなる Design Document を用意することでテキスト検索が可能になる、ということが挙げられますが、それ以外に大きな差はありません。 一方で IBM Cloud から提供されているライトアカウント(無料プラン)でも IBM Cloudant を利用することができるので、わざわざ Apache CouchDB を用意しなくても気軽に使うことができる DBaaS としてとても手軽で便利だと思っています:
2018090301


さて、自分は業務のプログラミングでは主に Node.js を使うのですが、Node.js のパッケージライブラリには IBM Cloudant 用のものと、Apache CouchDB 用のもの、両方が存在しています:
(IBM Cloudant)
2018090302

(Apache CouchDB)
2018090303


仮に対象となるデータベースが IBM Cloudant であれば前者の方が簡単に使えるという印象を持っています。ただし IBM Cloudant 用ライブラリは IBM Cloud 上の IBM Cloudant を想定していることもあり、例えばオンプレミス上の Apache CouchDB に対して使えるものではありません。

一方、Apache CouchDB 用ライブラリはローカルや社内サーバー、クラウド上にある Apache CouchDB データベース全般に対して利用することが可能です。この対象はクラウド上の IBM Cloudant であっても構いません。要するにこちらのライブラリを使えば Apache CouchDB だけでなく IBM Cloudant にも接続できる、ということです。


実際にこういった需要がどれだけあるのかわからないのですが、例えばあるシステムを作る際に、そのデータストアとして、
(1) 試しに動かす場合は IBM Cloud 上の IBM Cloudant を使って気軽に開発/テストを行い、
(2) 本番運用ではオンプレミスな Apache CouchDB を利用する(IBM Cloudant の独自機能は使わない想定)

といったことが接続先の切り替えだけでできると便利です。ただこれを実現するためには IBM Cloudant 用の便利なライブラリを使って開発しまうと (2) の本番の時に問題が起こってしまいます。以下では IBM Cloudant に対しても Apache CouchDB 用ライブラリ(以下 node-couchdb)を使ってアクセスするように実装してみたコードを紹介します。ベースが同じ製品なのでできることはできるんですが、そのための手順と注意点を含めて紹介します。


【準備】
まず Node.js のコードを記述する前に上述の node-couchdb を npm install しておきます:
$ npm install node-couchdb


【データベース接続】
node-couchdb を使って IBM Cloudant に接続します。こんな感じのコードを記述します:
var dblib = require( 'node-couchdb' );

var option = {
  auth: {
    user: 'username',
    pass: 'password'
  },
  'host': 'username.cloudant.com',
  'protocol': 'https',
  'port': 443
};
var db = dblib( option );

usernamepassword の部分にはそれぞれ IBM Cloudant の username と password を指定します(localhost の Apache CouchDB に接続する場合は option = {} で接続できます)。これで IBM Cloudant との接続ができました。ここで取得した db を使って、以下の主要な操作を行うことができます。


【主要な操作】

ドキュメント追加
insert() メソッドにデータベース名(以下の例では 'testdb')を指定して、ドキュメントを追加します。取得前に db.uniqid() でユニーク ID を取得し、_id に設定している点に注意してください:
var doc = { name: 'dotnsf', height: 170.0 };  //. 追加するドキュメント
db.uniqid().then( function( id ){
  doc._id = id[0];
  db.insert( 'testdb', doc ).then( function( body, headers, status ){
    console.log( body );
  }).catch( function( err ){
    console.log( err );
  });
});

ドキュメント読み取り
同様に get() メソッドにデータベース名と id を指定してドキュメントを取得します:
db.get( 'testdb', id ).then( function( doc, headers, status ){
  console.log( doc );
}).catch( function( err ){
  console.log( err );
});

ドキュメント削除
del() メソッドにデータベース名と id と rev を指定して、データベースからドキュメントを削除します。以下の例では一度 get() メソッドを実行して id 値から rev 値を取り出してから del() を実行しています :
db.get( 'testdb', id ).then( function( doc, headers, status ){
  db.del( 'testdb', doc.data._rev ).then( function( data, headers, status ){
    console.log( data );
  }).catch( function( err ){
    console.log( err );
  });
}).catch( function( err ){
  console.log( err );
});

ビューを指定してドキュメント一覧取得
あらかじめ作成したビューを指定して、そのビューに含まれるドキュメントの一覧を取得します。以下の例ではデザイン名 : library, ビュー名 : byname というビューを指定して文書一覧を取得しています :
db.get( 'testdb', '_design/library/_view/byname', {} ).then( function( data, headers, status ){
  if( data && data.data ){
    var docs = data.data.rows;
    console.log( docs );
  }
}).catch( function( err ){
  console.log( err );
});


IBM Cloudant の npm だと最初にデータベース名を指定してそのデータベースのオブジェクトを取得した上で各種操作を行う、、という流れなんですが、Apache CouchDB 版だと毎回データベース名と一緒に各種操作を行う、、という点が大きな違いだと思いました。ただその辺りさえ理解していればまあ大丈夫かな。。


 

これまであまり積極的に使うことのなかった webpack を使ってみました:
2018082300


webpack は複数のモジュール(ファイル)を1つに(場合によっては複数に)まとめるバンドラ(bundler)ツールです。Node.js でいうと、エントリーポイントとなる main.js ファイルと、ここから呼ばれるサブ機能が sub1.js や sub2.js などの別ファイルに分かれている場合に、これら3つのファイルを1つのファイルにまとめるというものです。

【サンプルの紹介】
今回は jsonwebtoken パッケージを使ったシンプルなサンプルコード(main.js)を用意しました:
// main.js
const jwt = require( 'jsonwebtoken' );

function main( params ){
  const id = params.id;
  const password = params.password;
  const token = jwt.sign( { id: id, password: password }, 'secret' );

  console.log( token );
}

main({
  id: 'userid',
  password: 'password'
});

global.main = main;

↑ main() 関数の中で2つのパラメータ(id と password)を受け取り、JSON をトークン化して出力する、というものです。そして id = 'userid', password = 'password' を指定して main を実行しています。

この main.js を実行する前に、コード内で使っている jsonwebtoken パッケージをインストールする必要があります。以下の内容の package.json を用意します:
{
  "name": "jwt_test",
  "version": "0.0.1",
  "main": "main.js",
  "dependencies": {
    "jsonwebtoken": "^8.3.0"
  }
}

そして以下のコマンドで jsonwebtoken パッケージをインストールします:
$ npm install


その後に main.js を普通に実行すると、{ id: 'userid', password: 'password' } をトークン化した結果が表示されます:
$ node main.js
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImRvdG5zZiIsInBhc3N3b3JkIjoicGFzc3dvcmQiLCJpYXQiOjE1MzQ5OTU5NTh9.LUx0q5ycwIFeR1HNnNFgUrlX0w0cny0qDVsPmiVtVnI

とりあえずこの状態で動くことは確認できました。コードは main.js 1つだけに記述されていますが、jsonwebtoken パッケージを利用しているので、実行前に $npm install (jsonwebtoken) を実施しておく必要があり、実際にはここでインストールされるモジュールファイルも使われながら動作します。これらを1ファイルにまとめた上で実行できるようにするというのが今回の目的です。


【webpack のインストール】
webpack は npm を使ってインストールします。例えば以下のコマンドでグローバルインストールすることができます:
$ npm install webpack -g

今回は webpack を(このプロジェクト用に)ローカルインストールして使うことにします。なお webpack 4.0 以降では webpack-cli も合わせて導入する必要があるのでまとめてインストールし、webpack コマンドが実行できるようローカルモジュールへのパスを通します:
$ npm install webpack webpack-cli

$ export PATH=$PATH:./node_modules/.bin


【config ファイルの用意】
webpack を使ってバンドルする作業の条件を config ファイルと呼ばれるファイルで指定します。以下の内容で webpack.js ファイルを作成します:
var path = require( 'path' );
module.exports = {
  entry: './main.js',
  output: {
    path: path.resolve( __dirname, 'dist' ),
    filename: 'bundle.js'
  },
  target: 'node'
};

↑ main.js をエントリーポイント(最初に実行するファイル)とする一連の処理の中で必要とされるモジュール(今回の例では jsonwebtoken パッケージ)がバンドルの対象となり、バンドル結果は dist/bundle.js というファイルに出力される、という指定をしています。


【バンドルと実行】
ここまでに準備した環境とファイルを使って webpack を使ってみます。

まずはバンドル、上記で作成した webpack.js を config ファイルとして指定して webpack コマンドを実行します:
$ webpack --config webpack.js

バンドルが成功すると dist/bundle.js というファイルが生成され、この1ファイルに main.js と jsonwebtoken パッケージがバンドルされているはずです。

確認のため生成されたファイルを実行します。念の為、これらのパッケージモジュールが存在しないテンポラリディレクトリ(/tmp)に bundle.js ファイル1つだけをコピーした上で実行します:
$ cp dist/bundle.js /tmp
$ cd /tmp
$ node bundle.js
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImRvdG5zZiIsInBhc3N3b3JkIjoicGFzc3dvcmQiLCJpYXQiOjE1MzUwMDM1MjF9.llZuuWxqX607AWrJBvMEDpFksFX8IvigUrdIWbhItRg

期待通りにトークンが表示される結果になりました。この bundle.js 1ファイルの中に jsonwebtoken パッケージやその依存パッケージまでが含まれる形でバンドルされて実行されたことが確認できました。


webpack を使うことで今回の例のように依存関係ごとファイルをまとめることができるだけでなく、ファイルのモジュール化が促進されたり、一度のロードで全ファイルを読み込めることから SPA(Single Page Application) が作りやすくなったりします。その一方で、バンドル先のファイルが大きくなってしまうと最初のロードに時間がかかるようになる、という問題もあります。個人的にはパッケージ化されたあとに開発コンソールを使ったデバッグが難しくなることに懸念も持っています。

まあケース・バイ・ケース、なんだろうなあ。。

 

Node.js を使ったアプリケーション開発中に npm install を実行して、こんなエラーに遭遇することがあります(個人的な印象では下の例のように canvas モジュールをインストールしようとした際によく遭遇します):
$ npm install canvas

> canvas@1.6.11 install /Users/dotnsf/src/tmp/node_modules/canvas
> node-gyp rebuild

gyp WARN download NVM_NODEJS_ORG_MIRROR is deprecated and will be removed in node-gyp v4, please use NODEJS_ORG_MIRROR
gyp ERR! configure error 
gyp ERR! stack Error: Python executable "/Users/dotnsf/.pyenv/shims/python" is v3.6.5, which is not supported by gyp.
gyp ERR! stack You can pass the --python switch to point to Python >= v2.5.0 & < 3.0.0.
gyp ERR! stack     at PythonFinder.failPythonVersion (/Users/dotnsf/.nvm/versions/node/v8.9.4/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:492:19)
gyp ERR! stack     at PythonFinder. (/Users/dotnsf/.nvm/versions/node/v8.9.4/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:474:14)
gyp ERR! stack     at ChildProcess.exithandler (child_process.js:267:7)
gyp ERR! stack     at emitTwo (events.js:126:13)
gyp ERR! stack     at ChildProcess.emit (events.js:214:7)
      :
      :

スクリーンショット 2018-08-21 9.44.07


Node.js でネイティブモジュールのビルドが必要になった場合に node-gyp を使ってビルドが行われるのですが、その際に使われる Python のバージョンがあっていない、というエラーです。ちと厄介なのは Python のバージョンが古くて問題になっているのではなく、新しすぎてエラーが発生している、ということです(上の例では v2.5.0 以上 v3.0.0 未満でないといけないのに v3.6.5 がインストールされていてエラーが発生している、というメッセージが表示されています)。

この解決のためにわざわざ古いバージョンをインストールしないといけないのか、ということはなく、以下のようにバージョンを明示して npm install することで解決できます:
$ npm install canvas --python=python2.7

> canvas@1.6.11 install /Users/dotnsf/src/tmp/node_modules/canvas
> node-gyp rebuild

gyp WARN download NVM_NODEJS_ORG_MIRROR is deprecated and will be removed in node-gyp v4, please use NODEJS_ORG_MIRROR
      :
      :

+ canvas@1.6.11
added 2 packages in 10.027s


IBM BluemixAPI Connect サービスを利用して、データベースのテーブルスキーマから CRUD の API を生成する、という手順を試してみました:
2017071001


以下にその作業内容を紹介します。なお、以下の内容は基本的にはこのドキュメントに書かれている内容を補足しながら実行したものです:
https://new-console.ng.bluemix.net/apis/apiconnect/start


何はともあれ API Connect サービスインスタンスを1つ作成します。なお 2016/07/10 時点では(クラシックエクスペリエンスではなく)新しいUI 画面でないと正しく動かなかったので、以下は新 UI でのスクリーションショットで紹介します。

まず API Connect サービスインスタンスを作成します。新 UI 画面のダッシュボードから「コンソール」を選択し、「API」を選択します:
2016071001


API に関連するサービスの画面が表示されたら「API Connect」を選択:
2016071002


API Connect の画面に移動しますが、まだインスタンスが作成されていないので、「作成」ボタンをクリックします:
2016071003


API Connect サービスの紹介画面が表示されたら、下にスクロールします:
2016071004


利用プランを選択して「作成」ボタンをクリックします。下図では無料の "Essential" プランを選択しました。共有サーバーで利用できるリソースもあまり多くありませんが、まずはこれで作ってみます:
2016071005


作成された API Connect サービスインスタンスの画面に移動します。"Sandbox" と表示されていれば成功です。また後でこの画面を使うことになりますが、まずはここまでの作業で API Connect サーバーが1つ用意できました。Bluemix のダッシュボードに戻るには左上の "IBM Bluemix" 部分をクリックします:
2016071006


さて、API が用意できたら再度この API Connect サービスを使って公開のための設定を行うことになります。既に公開してもよい API があればしばらく読み飛ばしてください。以下はこれから1つのデータベーステーブルを作り、その中身を読み書きするための API を API Connect Developer Toolkit を使って新たに生成するための作業手順を紹介します。

API Connect Developer Toolkit を導入するにはまず Node.js と npm を作業マシンに導入する必要があります。Windows 環境であればこちらからインストーラーをダウンロードして導入します。Linux などの UNIX 環境であればこちらの記事(の nodejs と npm のインストールまで)を参考に Node.js と npm を導入しておいてください:
http://dotnsf.blog.jp/archives/1036420834.html

Node.js および npm が導入できたら、npm を使って API Connect Developer Toolkit を導入します:
# npm install -g apiconnect

なお、この作業マシン上でも API Connect サーバーを起動することになり、一部の作業をウェブブラウザから実行することになります。つまりこの作業マシンには GUI のデスクトップ環境やウェブブラウザが導入されている必要があります。環境に合わせて導入しておいてください。CentOS であれば以下のコマンドで X Window のデスクトップおよび FireFox ウェブブラウザを導入できます:
# yum groupinstall "Desktop"
# yum install firefox

そして、API Connect Developer Toolkit を使って StrongLoop LoopBack アプリケーションを作成します。以下の例ではホームディレクトリ以下に src サブディレクトリを作り、更にその下に apic コマンドで loopback アプリケーションを作成しています。この通りに実行していただいても構いませんし、別途適宜ディレクトリを変更して実行することもできます:
# mkdir ~/src
# cd ~/src
# apic loopback

最初の1回目だけはライセンスに同意していただく手続きが必要です。内容を確認の上、"Yes" を選択してください:

2017071001


ライセンス同意後、ここからは StrongLoop LoopBack とほぼ同じ手順を実行することになります。まずは API アプリケーションとしての名前を指定します。ここでは "myapp" という名前にしました:
2017071002


次にアプリケーションを作成するディレクトリ名を指定します。デフォルトではアプリケーション名と同じ名前のディレクトリが指定されます。このままでも構いませんし、変更していただいても構いません:
2017071003


アプリケーションの種類を聞かれます。今回はサーバー上で動く API をテンプレートなしで作るので、"empty-server" を選択します:
2017071004


すると入力された内容にそってアプリケーションが作られます・・・・が、私の場合は何故かここでエラーになってしまいました:
2017071005


エラーになった場合はアプリケーションディレクトリ(この例では myapp)に移動し、"npm install" を実行して手動でインストールします:
2017071006


ではこの API アプリケーションを具体的に作成していきます。ここからはブラウザでの作業になるので、まずはデスクトップ画面を開き、その中でターミナルウィンドウを開きます。

アプリケーションディレクトリ(この例では myapp)に移動し、"apic edit" と入力します。すると "Express server listening on http://127.0.0.1:9000" と表示され、デスクトップ内にデフォルトブラウザが開いて、この URL が開きます。まず最初に Bluemix にログインする必要があるため、「Bluemix にサインイン」を選択します:
2017071007


IBM Bluemix のアカウントとパスワードでログインします:
2017071008


ログインが成功すると「ドラフト API」の画面に移動します。「OK」をクリックします:
2017071001


IBM API Connect のトップ画面に移動します。以下の様な画面が表示されることを確認してください:
2017071002


ここからテーブルモデルのスキーマを定義して、そのモデルの CRUD 操作 API を生成するのですが、その前にその情報を格納するデータソース(データベース)を作成する必要があります。「データソース」タブに移動して、まだデータソースが1つも定義されていないことを確認してから「追加」をクリックします:
2016071001


最初にデータソースの名称を入力します。ここでは "db" とだけ指定して「新規作成」します:
2016071002


次にこのデータソースのコネクターと localStorage を指定します。前者はデフォルトでもあるインメモリDB(In-memory db)を指定し、localStorage には window.localStrage と入力して、画面右上の保存ボタンで保存します。これでデータソースの定義は決まりました:
2016071003


続けてモデルを定義します。「モデル」タブに移動して「追加」ボタンをクリックします:
2016071001


新しく作成するモデルの名称を入力します。この例では "item" という名前のモデルにしました:
2016071002


続けて新規作成した item モデルに追加設定を加えるため、一覧から選択します:
2016071003


(モデルの)複数形には "items" 、基本モデルには "PersistedModel" 、そしてデータソースには先程作成した "db" を指定します:
2016071202


続けてプロパティを追加するために右下の+印をクリックして、以下のようにプロパティを設定します。追加するプロパティは2つで、1つ目は必須の name(string)、2つ目は必須ではない price(number)とします。item(商品)のname(名前)とprice(値段)を設定するためのプロパティだと思ってください。なお、特に指定しなくても id というインデックスプロパティがこれらに加えて追加されます:
2016071000


プロパティの追加が完了したら、右上の保存ボタンでモデル定義を保存します。またこの状態で CRUD API を有効にするため、画面下の "Stopped" と書かれた左にある三角をクリックして、API アプリケーションを実行します:
2016071201


しばらくすると HTTP サーバーが起動し、ステータスが "Running" に変わります。同時に API アプリケーションの URL(http://127.0.0.1:4001/)が表示されます:
2016071203


この状態になると、作成した myapp の API アプリケーションが起動している状態になり、item モデルの読み書きが可能になります。試しにいくつかの API を curl で実行してみましょう(以下青字が入力コマンド、黒字が出力結果)。

まずは item の一覧を取得(GET /api/items)します。まだ何も追加していないので、結果は空配列になっています:
# curl -XGET http://localhost:4001/api/items
[]

1つの item を追加(POST /api/items)してみます。まずは必須の "name" プロパティをわざと指定せずにデータを POST してエラーになることを確認します:
# curl -H 'Content-Type: application/json' -XPOST http://localhost:4001/api/items -d '{"price":100}'
{
 "error":{
  "name":"ValidationError",
  "status":422,
  "message":"The `item` instance is not valid. Details: `name` can't be blank (value: undefined).",
  "statusCode":422,
  "details":{
   "context":"item",
   "codes":{
    "name":["presence"]
   },
   "messages":{
    "name":["can't be blank"]
   }
  },
  "stack":"ValidationError: The `item` instance is not valid. Details: `name` can't be blank (value: undefined).\n ....
    :

↑"name" がブランクではダメ、と怒られています。というわけで、改めて正しいデータを指定して追加してみましょう:
# curl -H 'Content-Type: application/json' -XPOST http://localhost:4001/api/items -d '{"name":"あいうえお","price":100}'
{"name":"あいうえお","price":100,"id":1}

↑追加処理が成功したようです。入力データに指定しなかった id 要素が追加され、その値が1になっているようです。


この状態で再度 item の一覧取得を実行すると、追加したデータが含まれた配列で返ってくることを確認します:
# curl -XGET http://localhost:4001/api/items
[{"name":"あいうえお","price":100,"id":1}]

また id を指定して要素を取り出す(GET /api/items/{id})こともできます:
# curl -XGET http://localhost:4001/api/items/1
{"name":"あいうえお","price":100,"id":1}

↑こちらは結果が1つしか存在しない API なので、結果は配列ではなく、該当する JSON オブジェクトそのものになります。

データを更新(PUT /api/items)することもできます:
# curl -H 'Content-Type: application/json' -XPUT http://localhost:4001/api/items -d '{"id":1,"name":"かきくけこ","price":200}'
{"name":"かきくけこ","price":200,"id":1}

# curl -XGET http://localhost:4001/api/items
[{"name":"かきくけこ","price":200,"id":1}]

そして削除(DELETE /api/items/{id})する API も用意されています:
# curl -XDELETE http://localhost:4001/api/items/1
{"count":1}

# curl -XGET http://localhost:4001/api/items
[]

というわけで、item モデルを定義しただけで、その item モデルを読み書き更新削除する API が自動生成できていることが確認できました。


API Connect 本体の機能をほとんど紹介できてないのですが、長くなったのでとりあえずここまで。


このページのトップヘ