Mario Stefanutti
Mario Stefanutti

Reputation: 262

Transitive relationships in neo4j

I have just started using neo4j.

I was trying to use it to describe a company's Enterprise Architecture.

I'd like to show that, by removing the node "Middleware", Service_A goes from Client_A and Server_X, and the same for the other services.

Is there a way to do that? Is this the right approach?

Next is the code used to generate the graph.

enter image description here

from neo4j import GraphDatabase
import logging
from neo4j.exceptions import ServiceUnavailable


class App:

    def __init__(self, uri, user, password):
        self.driver = GraphDatabase.driver(uri, auth=(user, password))

    def close(self):
        # Don't forget to close the driver connection when you are finished with it
        self.driver.close()

    def drop_everything(self):
        with self.driver.session(database="neo4j") as session:
            # Write transactions allow the driver to handle retries and transient errors
            session.execute_write(self._drop_everything)
            print("Graph DB dropped")

    @staticmethod
    def _drop_everything(tx):
        query = (
            "MATCH (n) DETACH DELETE (n)"
        )
        try:
            tx.run(query)

        # Capture any errors along with the query and data for traceability
        except ServiceUnavailable as exception:
            logging.error("{query} raised an error: \n {exception}".format(query=query, exception=exception))
            raise

    def create_system(self, system_name):
        with self.driver.session(database="neo4j") as session:
            # Write transactions allow the driver to handle retries and transient errors
            result = session.execute_write(self._create_system, system_name)
            print("Created system: {p1}".format(p1=result))

    @staticmethod
    def _create_system(tx, system_name):
        # To learn more about the Cypher syntax, see https://neo4j.com/docs/cypher-manual/current/
        # The Reference Card is also a good resource for keywords https://neo4j.com/docs/cypher-refcard/current/
        query = (
            "CREATE (n:system{name:$system_name}) "
            "RETURN (n)"
        )
        result = tx.run(query, system_name=system_name)
        try:
            return result.single()[0]["name"]
        # Capture any errors along with the query and data for traceability
        except ServiceUnavailable as exception:
            logging.error("{query} raised an error: \n {exception}".format(query=query, exception=exception))
            raise

    def create_flow(self, service_name_a, system1_name, system2_name):
        with self.driver.session(database="neo4j") as session:
            # Write transactions allow the driver to handle retries and transient errors
            result = session.execute_write(self._create_flow, service_name_a, system1_name, system2_name)
            print("Created flow: {p1}".format(p1=result))

    @staticmethod
    def _create_flow(tx, service_name, system1_name, system2_name):
        query = (
            "MATCH (p1:system), (p2:system) "
            "WHERE p1.name = '" + system1_name + "' AND p2.name = '" + system2_name +"' "
            "CREATE (p1)-[r:" + service_name + " {type:'call'}]->(p2) "
            "RETURN ('" + service_name + "')"
        )

        # CREATE (p1)-[r:service {name:'" + service_name + "'}]->(p2)
        # CALL apoc.create.relationship(p1, '$service_name', NULL, p2) YIELD rel
        # CREATE (p1)-[:service {name:'$service_name'}]->(p2) "

        result = tx.run(query, service_name=service_name, system1_name=system1_name, system2_name=system2_name)
        try:
            return service_name
        # Capture any errors along with the query and data for traceability
        except ServiceUnavailable as exception:
            logging.error("{query} raised an error: \n {exception}".format(query=query, exception=exception))
            raise

    def find_system(self, system_name):
        with self.driver.session(database="neo4j") as session:
            result = session.execute_read(self._find_system, system_name)
            for row in result:
                print("Found system: {row}".format(row=row))

    @staticmethod
    def find_system(tx, system_name):
        query = (
            "MATCH (p:system) "
            "WHERE p.name = $system_name "
            "RETURN p.name AS name"
        )
        result = tx.run(query, system_name=system_name)
        return [{ row["name"] } for row in result]


if __name__ == "__main__":
    # Aura queries use an encrypted connection using the "neo4j+s" URI scheme
    # https://console.neo4j.io/ - Login with google [email protected]
    uri = "neo4j+s://8a9f54ab.databases.neo4j.io"
    user = "neo4j"
    password = "Xxx"
    app = App(uri, user, password)

    app.drop_everything()

    app.create_system("Client_A")
    app.create_system("Middleware")
    app.create_system("Server_X")
    app.create_system("Server_Y")
    app.create_system("Server_Z")

    app.create_flow("Service_A", "Client_A", "Middleware")
    app.create_flow("Service_B", "Client_A", "Middleware")
    app.create_flow("Service_C", "Client_A", "Middleware")

    app.create_flow("Service_A", "Middleware", "Server_X")
    app.create_flow("Service_B", "Middleware", "Server_Y")
    app.create_flow("Service_C", "Middleware", "Server_Z")

    app.close()

Upvotes: 0

Views: 192

Answers (2)

trippy
trippy

Reputation: 150

Do you have distinct labels for the different types of nodes? i.e., client nodes vs. server nodes

If you would like to retain the graph's current structure, you can use the Virtual Nodes/Relationships concept to collapse the intermediary node (i.e., Middleware) and create a (virtual) relationship between the Client and Servers.

Note that the virtual relationship will not exist within the graph or require you to remove the Middleware intermediary, but it will be available at the UI layer for visualization/presentation.

MATCH (from:system)-[rel1:service]->(inter:system)-[rel2:service]->(to:system)
RETURN from, to, apoc.create.vRelationship(from,'SERVES', {}, to) as myVirtualRel;

Check out https://neo4j.com/labs/apoc/4.1/virtual/virtual-nodes-rels/.

Upvotes: 1

Charchit Kapoor
Charchit Kapoor

Reputation: 9294

For the graph, created by this query as depicted in your image:

MERGE (c:Client)
MERGE (m:Middleware)
MERGE (s:Server_X)
MERGE (s1:Server_Y)
MERGE (s2:Server_Z)
MERGE (c)-[:SERVICE_A]->(m)-[:SERVICE_A]->(s)
MERGE (c)-[:SERVICE_B]->(m)-[:SERVICE_B]->(s1)
MERGE (c)-[:SERVICE_C]->(m)-[:SERVICE_C]->(s2)

This query should work:

MATCH (c:Client)-[rel1]->(m:Middleware)-[rel2]->(server) WHERE type(rel1) = type(rel2)
MERGE (c)-[:CONNECTION]->(server)

Upvotes: 1

Related Questions