Neo4j アプリケーションを作るには? その2

Neo4j アプリケーションを作るには? その2


前回は、REST APIを利用してのアプリケーション作成について書きました。

一般的な使い方であれば、標準のREST APIや、Cypherで十分だと思いますが、
パフォーマンスや、トラバーサルを細かく定義したいなどの課題がでる事があります。

Embeddedモードで、JavaAPIを使用すればパフォーマンスは大変良いのですが、
SevertモードとEmbeddedモードの併用は出来ません。

そこで今回は、Neo4jのREST API経由でEmbeddedなJavaAPIが使える
「Server Plugin」と「Unmanaged Extension」の作成について書きます。

「Server Plugin」と「Unmanaged Extension」

構成は、こんな感じです。

スライド1

「Server Plugin」ですと、Neo4jのREST API管理下に入り、「Unmanaged Extension」ですと管理外(リソースも)となります。
「Unmanged Extension」だとレスポンスにJSON以外も使用できます。
ただし、「Unmanged Extension」を使う場合は注意するようにと強くマニュアルなどに書かれてますので、「Unmanged Extension」を使いには事前検討が必要です。

Server Plugin

依存関係

ServerPluginを作るための依存関係は以下となります。

'org.neo4j:server-api:2.3.1'

サンプルコード

import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Result;
import org.neo4j.graphdb.Transaction;
import org.neo4j.server.plugins.Name;
import org.neo4j.server.plugins.PluginTarget;
import org.neo4j.server.plugins.ServerPlugin;
import org.neo4j.server.plugins.Source;
 
public class Sample extends ServerPlugin {
 
    @Name("plugin_sample")
    @PluginTarget(GraphDatabaseService.class)
    public Long count(@Source GraphDatabaseService graphDb) {
 
        Long count = 0L;
 
        try(Transaction tx = graphDb.beginTx()) {
            Result result = graphDb.execute("MATCH (n) return count(n) as cnt");
            count = result.next();
            count = (Long)ret.get("cnt");
        }
        return count;
    }
}

また、「ServerPlugin」で扱える型は以下のようです。


ensure that it can produce an (Iterable of) Node, Relationship or Path, any Java primitive or String or an instance of a org.neo4j.server.rest.repr.Representation

ServiceLoaderなので、jarのMETA-INF/services配下に「org.neo4j.server.plugins.ServerPlugin」ファイルを作成し、
そのファイルにpluginにしたいクラスを記述します。

$ cat src/main/resources/META-INF/services/org.neo4j.server.plugins.ServerPlugin
jp.co.inte.Sample

起動

jarを作成して、${NEO4J_HOME}/pluginに配置します。

Neo4jサーバを起動した際に、以下のようなエラーが出力していたら、プラグインのロードに失敗していいます。
この例は、レスポンスの型がサポートされていないために発生したエラーです。


WARN Failed to load plugin [ServerPlugin[Sample]]: Illegal result type: java.util.Map

ロードに成功したら、以下のようなログが出ます。


Loaded server plugin "Sample"
GraphDatabaseService.plugin_sample:

実行

curlコマンドでREST APIの一覧を取得します。

$ curl http://localhost:7474/db/data/
{
  "extensions" : {
    "Sample" : {
      "plugin_sample" : "http://localhost:7474/db/data/ext/Sample/graphdb/plugin_sample"
    }
  },
  "node" : "http://localhost:7474/db/data/node",
  "node_index" : "http://localhost:7474/db/data/index/node",
  "relationship_index" : "http://localhost:7474/db/data/index/relationship",
  "extensions_info" : "http://localhost:7474/db/data/ext",
  "relationship_types" : "http://localhost:7474/db/data/relationship/types",
  "batch" : "http://localhost:7474/db/data/batch",
  "cypher" : "http://localhost:7474/db/data/cypher",
  "indexes" : "http://localhost:7474/db/data/schema/index",
  "constraints" : "http://localhost:7474/db/data/schema/constraint",
  "transaction" : "http://localhost:7474/db/data/transaction",
  "node_labels" : "http://localhost:7474/db/data/labels",
  "neo4j_version" : "2.3.0"
}

「extensions」配下に追加されていますね。
では、APIを叩いてみます。

$ curl -X post http://localhost:7474/db/data/ext/Sample/graphdb/plugin_sample

ノードの件数が返ってくれば成功です。

Unmanaged Extension

依存関係

'javax.ws.rs:javax.ws.rs-api:2.0.1'

サンプルコード

package jp.co.inte;
 
import org.neo4j.graphdb.GraphDatabaseService;
 
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.nio.charset.Charset;
 
@Path("/helloworld")
public class UnmanagedPluginSample {
 
    private final GraphDatabaseService graphDatabaseService;
 
    public UnmanagedPluginSample(@Context GraphDatabaseService graphDatabaseService) {
        this.graphDatabaseService = graphDatabaseService;
    }
 
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("/{nodeId}")
    public Response hello(@PathParam("nodeId") long nodeId) {
        return Response.status(Response.Status.OK).entity(
                ("Hello World, nodeId=" + nodeId).getBytes( Charset.forName("UTF-8") )
        ).build();
    }
}

すいません。マニュアルのままです。。。。
コンストラクタに、GraphDatabaseServiceが渡されますので、ぐりぐりっと操作しレスポンスで返せばOKです。

起動

「neo4j-server.properties」に呼び出したいExtensionを記述します。

org.neo4j.server.thirdparty_jaxrs_classes=jp.co.inte=/sample/unmanage

この例は、パッケージ「jp.co.inte」にあるクラス群を「/sample/unmanage」で呼ぶ定義です。
サンプルコードのパスは「/sample/unmanage/helloworld」となります。

Neo4jを起動してロードされると以下のようなログが出力されます。


Mounted unmanaged extension [jp.co.inte] at [/sample/unmanage]

実行

$ curl http://localhost:7474/sample/unmanage/helloworld/1

文字列「Hello World, nodeId=1」が返ってくればOKです。

まとめみたいなもの

最初に書いたように、標準のREST APIで大丈夫だと思いますが、パフォーマンスが良くない場合はServerPluginの検討をしてみてください。
開発中ですが、Neo4j Java Driverも今後チェックが必要かなと考えています。