< back

Neo4j Bolt Driver for JavaScript

本記事では、Neo4j Driver として公式サポートされている、neo4j-javascript-driver について紹介します。

Neo4j Driver

Neo4j 3.0 より、より効率的なネットワーク通信のため、独自のバイナリプロトコルである Bolt protocol を利用し始めました。執筆時点では仕様として v4.3 が最新であり、ソケット通信と WebSocket を利用した TCP 上のプロトコルとなります。

セッション や複数のメッセージを応答を待たずにクライアント側から送る Pipelining を含んでおり、特に Neo4j やグラフデータベースに制限されたプロトコルではありません。

そして、Neo4j (Bolt) Driver とは、裏側で Bolt protocol を利用して Neo4j データベースとやり取りをするために Neo4j またはオープンソースで開発されているクライアントのことです。

現在、Java/Python/.NET/JavaScript/Go らについては公式にサポートされており、オープンソースで開発されているものでは Ruby/PHP/Elixir/Perl/Haskell/Clojure/C/C++ などがあります。

neo4j-javascript-driver

neo4j-javascript-driver は、先述したとおり Neo4j が公式にサポートしている Driver の内の一つです。

最新のバージョンである v4.3 の API Document は こちら で公開されています。

基本の API

基本の API としては非常にシンプルです。例えば、任意の Cypher クエリをセッション内で実行する場合、Session#run() を用いて以下の様に実装します。

const neo4j = require('neo4j-driver')

const user = 'neo4j';
const password = 'foobar';

// Driver クライアントをまずはインスタンス化。セッション管理を行ってくれます。
// アプリで一つ作るイメージで、複数の接続を行う場合は使い回すと良いです。
const driver = neo4j.driver(uri, neo4j.auth.basic(user, password))

// セッションを獲得します。コネクションプールから利用可能なものを利用するので、
// 任意の論理的なクエリ群ごとにセッションを獲得・解放すると良いです。
const session = driver.session()

// Cypher クエリに渡す変数
const personName = 'Alice'

try {
    // このタイミングで実際にクエリを実行します
    const result = await session.run(
        'CREATE (a:Person {name: $name}) RETURN a',
        { name: personName }
    )

    // クエリ結果を出力します
    const singleRecord = result.records[0]
    const node = singleRecord.get(0)
    console.log(node.properties.name)
} finally {
    // セッションを解放します
    await session.close()
}

// Driver クライアントを終了します
await driver.close()

トランザクション

トランザクションを獲得して読み込みまたは書き込みのクエリを実行したい場合、Session#readTransaction() または Session#writeTransaction() を用います。

Session#readTransaction() を用いた例は以下です。

const readQuery = `MATCH (p:Person) WHERE p.name = $personName RETURN p.name AS name`
const readResult = await session.readTransaction(tx => tx.run(readQuery, { personName: 'Alice' }))
readResult.records.forEach(record => {
    console.log(`Found person: ${record.get('name')}`)
})

特に書き込み時にトランザクションを獲得したいケースは多いでしょう。一連の処理群をトランザクションに含むことによって、途中の処理が実行中にネットワークエラーなどによってクエリが完走しなかった場合、適切なロールバックやタイムアウト内のリトライ処理をライブラリ側で行ってくれます。

Session#writeTransaction() を用いた例は以下です。

const writeQuery = `MERGE (p1:Person { name: $person1Name })
                    MERGE (p2:Person { name: $person2Name })
                    MERGE (p1)-[:KNOWS]->(p2)
                    RETURN p1, p2`
const writeResult = await session.writeTransaction(tx => tx.run(writeQuery, { person1Name: 'Alice', person2Name: 'Bob' }))
writeResult.records.forEach(record => {
    const person1Node = record.get('p1')
    const person2Node = record.get('p2')
    console.log(`Created friendship between: ${person1Node.properties.name}, ${person2Node.properties.name}`)
})

また、任意のトランザクションを実行したい場合は、Session#beginTransaction() も用意されているので、そちらを使うようにしましょう。

数値型

neo4j-javascript-driver を利用時のよくある注意点としては、数値型の取扱です。

詳細は ドキュメント に記載されているとおりですが、Neo4j データベースがサポートしている Int のビット範囲と、JavaScript がサポートしている Number のビット範囲が異なるため、変換時には注意が必要です。

The Neo4j type system uses 64-bit signed integer values. The range of values is between -(264- 1) and (263- 1).

However, JavaScript can only safely represent integers between Number.MIN_SAFE_INTEGER -(253- 1) and Number.MAX_SAFE_INTEGER (253- 1).

具体的には、数値型が返却された場合、抽象化された Integer クラスで返却されるので、直接 JavaScript の Number 型として扱うのではなく、この Integer クラスを利用するようにします。

例えば、返却された値に数値型が入っていた場合、Integer#toNumber() を用いて変換してあげる必要が有ります。

var smallInteger = neo4j.int(123)
if (neo4j.integer.inSafeRange(smallInteger)) {
  var aNumber = smallInteger.toNumber()
}

最後に

以上、neo4j-javascript-driver の紹介を行いました。

October 05, 2021