見出し画像

#69 Neo4j

 MySQLなどのリレーショナルデータベースは、堅牢でとても使いやすいですが、システムによっては適切でない場合もあります。例えば、扱うデータ量が莫大だと、MySQLのパフォーマンスは落ちてしまいます。データ構造が定まっていなかったり、複雑なリレーションを扱う必要がある場合も対応できません。
 そんなときに使われるのがNoSQLです。ドキュメント型や、キーバリュー型など、要件に合わせて様々な選択ができます。Neo4jは、グラフデータベースのオープンソースプロジェクトで、ネットワーク状の複雑なデータ関係を表現するのに適しています。
 それでは、早速試してみましょう!

準備

Dockerで環境構築し、Pythonから操作したいと思います。詳しいインストール方法などは、下記を参考にしてください。

環境構築

Docker ComposeでDBストレージとサーバーを立てます。

docker-compose.yml

networks:
  lan:

services:
  storage:
    hostname: storage
    image: neo4j:4.4.8-enterprise
    networks:
      - lan
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      NEO4J_ACCEPT_LICENSE_AGREEMENT: "yes"
      NEO4J_AUTH: neo4j/passw0rd
      NEO4J_dbms_default__advertised__address: storage
      NEO4J_dbms_connector_http_listen__address: storage:9000
      NEO4J_dbms_connector_bolt_listen__address: storage:9001
    healthcheck:
      test: [ "CMD-SHELL", "echo RETURN 1 | cypher-shell -a bolt://storage:9001 -u neo4j -p passw0rd || exit 1" ]

  server:
    hostname: server
    image: neo4j/neo4j-ops-manager-server:latest
    depends_on:
      storage:
        condition: service_healthy
    networks:
      - lan
    ports:
      - "8080:8080"
      - "9090:9090"
    environment:
      SPRING_NEO4J_URI: bolt://storage:9001
      SPRING_NEO4J_AUTHENTICATION_USERNAME: neo4j
      SPRING_NEO4J_AUTHENTICATION_PASSWORD: passw0rd
      SERVER_SSL_KEY_STORE_TYPE: PKCS12
      SERVER_SSL_KEY_STORE: file:/certificates/localhost.pfx
      SERVER_SSL_KEY_STORE_PASSWORD: changeit
      GRPC_SERVER_SECURITY_KEY_STORE_TYPE: PKCS12
      GRPC_SERVER_SECURITY_KEY_STORE: file:/certificates/localhost.pfx
      GRPC_SERVER_SECURITY_KEY_STORE_PASSWORD: changeit
      CORS_ALLOWEDHEADERS: "*"
      CORS_ALLOWEDORIGINS: "http://localhost:[*],https://localhost:[*]"
      JWT_SECRET: please-set-a-random-secret-string-here-for-jwt-signing
    volumes:
      - type: bind
        source: .nom/ssc
        target: /certificates
    entrypoint:
      - "sh"
      - "-c"
      - "java -jar app.jar ssc -n localhost -o /certificates -p changeit -d localhost.localdomain -i 127.0.0.1 && java -jar app.jar"

起動する前に、コンテナで使用されるディレクトリを先に用意する必要があるので注意です。
以下コマンドで起動できます。

$ mkdir -p .nom/ssc
$ docker compose up -d --build


Pythonもvenvで仮想環境を用意します。

$ python3 -m venv .venv
$ source .venv/bin/activate

仮想環境内で、Neo4jのライブラリをインストールします。

$ pip3 install neo4j-driver


接続

DBとの接続を管理するクラスを用意しました。

connection.py

from neo4j import GraphDatabase

class Connection:
    def __init__(self, uri, user, password):
        self.uri = uri
        self.user = user
        self.password = password
        self.driver = None

    def connect(self):
        try:
            self.driver = GraphDatabase.driver(self.uri, auth=(self.user, self.password))
        except Exception as e:
            print("Failed to connect: ", e)

    def close(self):
        if self.driver is not None:
            self.driver.close()

    def execute(self, query):
        try: 
            session = self.driver.session(database="neo4j")
            response = list(session.run(query))
        except Exception as e:
            print("Failed:", e)
        finally: 
            if session is not None:
                session.close()
        return response

とりあえず、データの投入と取得を試してみます。

main.py

from connection import Connection

def main():
    uri = "bolt://localhost:9001"
    user = "neo4j"
    password = "passw0rd"

    conn = Connection(uri, user, password)
    conn.connect()
    query1 = "CREATE (test: Test {name: 'test1'})"
    result = conn.execute(query1)
    print(result)
    query2 = "MATCH (t:Test) RETURN t"
    result = conn.execute(query2)
    print(result)


if __name__ == "__main__":
    main()
$ python3 main.py
[]
[<Record t=<Node element_id='0' labels=frozenset({'Test'}) properties={'name': 'test1'}>>]

しっかり取れているみたいです。


Cypher

 Neo4jでは、慣れ親しんだSQLではなく、Cypherという言語でクエリを組み立てます。文法が少し違うだけで、それほど難しいものでもありません。

CREATE
ノードやリレーションをDBに挿入します。

// ノード
CREATE (t: Test {name: 'test1'})
// リレーション
CREATE (t1: Test {name: 'test1')-[:IS_PARENT]->(t2: Test {name: 'test2'})

MATCH
条件に合ったものを取得します。

MATCH (t: Test {name: 'test1'}) RETURN t

SET
値の更新を行います。

MATCH (t: Test {name: 'test1'})
SET t.name = 'test2'

DELETE
レコードを削除します。リレーションがある場合は、先にリレーションを削除しなければいけません。

MATCH (t:Test {name: 'test1'}) 
DELETE t
// リレーションもまとめて削除
MATCH (t:Test {name: 'test1'}) 
DETACH DELETE t


まとめ

 グラフデータベースで扱っている概念は、リレーショナルデータベースのものとは違うため、慣れるまでは少し手間取りそうですが、グラフでしか扱えない問題もたくさんありそうです。
 気になるのはやはりセキュリティ面ですが、アプリの設計によっては、SQLインジェクションならぬCypherインジェクションのような脆弱性も当然生まれてしまうでしょう。深く理解して扱っていきたいです。

EOF

この記事が気に入ったらサポートをしてみませんか?