ChilliCream / graphql-platform

Welcome to the home of the Hot Chocolate GraphQL server for .NET, the Strawberry Shake GraphQL client for .NET and Banana Cake Pop the awesome Monaco based GraphQL IDE.
https://chillicream.com
MIT License
5.24k stars 744 forks source link

[WIP][FEATURE] Initial Neo4j Integration Planning #2325

Closed arif-hanif closed 2 years ago

arif-hanif commented 4 years ago

This issue will track how to construct the API and packages to add integration of Neo4j with HC. The issue will try to review the current state and what other libraries are doing. Currently there is no library in dotnet. The only examples that exist are Java and Javascript as far as I can see, if someone spots other examples please add.

Existing solutions we can study

Javascript Implementation https://github.com/neo4j-graphql/neo4j-graphql-js Java Implementation https://github.com/neo4j-graphql/neo4j-graphql

Both solutions use schema first approach to execute to cypher

Additional Resources

Dotnet Neo4j Driver https://github.com/neo4j/neo4j-dotnet-driver

Example Cypher Builder & Execution in Dotnet https://github.com/barnardos-au/Neo4jMapper https://github.com/DotNet4Neo4j/Neo4jClient https://github.com/weknow-network/Weknow.Cypher.Builder

Cypher DSL examples in other languages

(Node.js) https://www.npmjs.com/package/cypher-query-builder (Scala) https://github.com/manishkkatoch/scala-cypher-dsl (Java) https://github.com/neo4j-contrib/cypher-dsl/blob/main/neo4j-cypher-dsl-examples/src/test/java/org/neo4j/cypherdsl/examples/core/CypherDSLExamplesTest.java

Cypher Cheat Sheet https://neo4j.com/docs/cypher-refcard/current/

ORM Examples

https://github.com/neo4j/neo4j-ogm

image

(WIP) Features

Core

(WIP) Proposal

Schema

enum RelationshipDirection {
  IN
  OUT
}

type Movie {
    title: String
    year: Int
}

type Person {
    name: String
    age: Int
    actedIn: [Movie] @relation(name: "ACTED_IN", direction: "OUT")
}

type Query {
   actors(
       where: PersonFilterInput
       order: PersonSortInput
    ): [Person]
}

Code First

NEEDS INPUT

Pure Code

[NodeEntity]
public class Person {
     public string Name { get; set; }
     public int Age { get; set; }

     [Relationship(Name="ACTED_IN", Direction=RelationshipDirection.Out)]
     public List<Movie> ActedIn { get; set; }
}

[NodeEntity]
public class Movie {
     public string Name { get; set; }
}

public enum RelationshipDirection {
     In,
     Out
}

Query Type

public class Query
{
        [UsePaging]
        [UseFiltering]
        [UseSorting]
        [UseSelection]
        public ICollection<Person> Actors([ScopedService] driver) => ResolveNeo4j<Person>(driver);
}

Configuration API

Driver Driver = GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("username", "password"));

services.AddScoped<IDriver, Driver>();

services
    .AddGraphQLServer()
        .AddNeo4jIntegration()
        .AddQueryType(Query);

Example Query

query {
  actors(where: {name: {eq: "Tom Hanks"}} sort: {actedIn: { name: DESC }}}) {
    name
        actedIn {
            name
    }
  }
}

Example Cypher

MATCH (a: Person)-[:ACTED_IN]->(b: Movie)
WHERE a.name = 'Tom Hanks'
ORDER BY b.Name DESC
RETURN a, b
String Operations Query Cypher
{name: { eq: "Tom Hanks" }} WHERE a.name = 'Tom Hanks'
{name: { neq: "Tom Hanks" }} WHERE NOT a.name = 'Tom Hanks'
{name: { contains: "Tom" }} WHERE a.name CONTAINS 'Tom'
{name: { ncontains: "Tom" }} WHERE NOT a.name CONTAINS 'Tom'
{name: { in: ["Tom"] }} WHERE a.name in ['Tom']
{name: { nin: ["Tom"] }} WHERE NOT a.name in ['Tom']
{name: { startsWith: "Tom" }} WHERE a.name STARTS WITH 'Tom'
{name: { nstartsWith: "Tom" }} WHERE NOT a.name STARTS WITH 'Tom'
{name: { endsWith: "Tom" }} WHERE a.name ENDS WITH 'Tom'
{name: { nendsWith: "Tom" }} WHERE NOT a.name ENDS WITH 'Tom'
Comparable Operations Query Cypher
{age: { eq: 50 }} WHERE a.age = 50
{age: { neq: 50 }} WHERE NOT a.age = 50
{name: { in: [50] }} WHERE a.age in [50]
{name: { nin: [50] }} WHERE NOT a.age in [50]
{name: { gt: 50 }} WHERE a.age > 50
{name: { ngt: 50 }} WHERE NOT a.age > 50
{name: { gte: 50 }} WHERE a.age >= 50
{name: { ngte: 50 }} WHERE NOT a.age >= 50
{name: { lt: 50 }} WHERE a.age < 50
{name: { nlt: 50 }} WHERE NOT a.age < 50
{name: { lte: 50 }} WHERE a.age <= 50
{name: { nlte: 50 }} WHERE NOT a.age <= 50
Relationship Operations Query Cypher
DateTime Operations Query Cypher
Spatial Operations Query Cypher
PascalSenn commented 4 years ago

Cool thanks for opening the issue! For those who never worked with neo4j it would be helpful to have some more information about these things.

Existing solutions we can study https://github.com/neo4j-graphql/neo4j-graphql-js

Can you go a little more detailed into what this solutions covers, what it does right, what wrong and what is missing? You can look at these issues for inspiration #921 #922 #923 #924 #1870

arif-hanif commented 4 years ago

@PascalSenn just wanted to get a issue open and jot some ideas down, I will spend a lot more time this weekend.

arif-hanif commented 4 years ago

JavaScript Implementation

image image image image

Pros

Cons

arif-hanif commented 4 years ago

Java Implementation

Pros

Cons

-

michaelstaib commented 4 years ago

So,

the way I want to have it in hot chocolate is like the following.

First, let us be SDL compliant to the JavaScript solutions. This way can migrate from js by copying the SDL over:

service
    .AddGraphQL()
    .AddDocumentFromString("SDL GOES HERE")
    .AddNeo4J("[configname]");

AddNeo4J will add the data integration provider that brings the SDL to live.

But I actually want to go further with this. Let us also create a source generator that takes the annotated SDL and generates a code-first integration... in this case configuration would look like this:

service
    .AddGraphQL()
    .AddMovieTypes()
    .AddNeo4J("[configname]");

Why should we do that?

The cool thing now, if you want to extend the schema you will have proper types... lets for instance extend the movie type.

[ExtendObjectType(typeof(Movie))]
public class MyMovieExtension
{
    public string AddSomething(Movie movie) => .... does something here
}
service
    .AddGraphQL()
    .AddMovieTypes()
    .AddTypeExtension<MyMovieExtension>()
    .AddNeo4J("[configname]");
arif-hanif commented 4 years ago

Syntax Tree

Classes

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.