订阅事件

此页面介绍了 Neo4j GraphQL 库提供的各种订阅选项。

仅通过@neo4j/graphql进行的更改才会触发此处描述的事件。直接对数据库进行更改或使用@cypher指令进行的更改将**不会**触发任何事件。

CREATE

CREATE事件的订阅**仅**监听新创建的节点,而不是新的关系。在这种情况下,每个新节点都会触发一个新事件,其中包含其属性。

此操作通过顶级订阅[type]Created执行,其中包含以下字段

  • event:触发此订阅的事件(在本例中为CREATE)。

  • created<typename>:新创建节点的顶级属性,不包含关系。

  • timestamp:进行变异的时间戳。如果同一个查询触发多个事件,则它们应具有相同的时间戳。

例如,考虑以下类型定义

type Movie {
    title: String
    genre: String
}

Movie类型任何新创建的节点的订阅应如下所示

subscription {
    movieCreated {
        createdMovie {
            title
            genre
        }
        event
        timestamp
    }
}

UPDATE

UPDATE事件的订阅**仅**监听节点属性更改,而不是其他字段的更新。在这种情况下,每个修改节点顶级属性的变异都会触发一个新事件。

此操作通过顶级订阅[type]Updated执行,其中包含以下字段

  • event:触发此订阅的事件(在本例中为UPDATE)。

  • updated<typename>:更新节点的顶级属性,不包含关系。

  • previousState:节点在UPDATE事件之前的先前顶级属性。

  • timestamp:进行变异的时间戳。如果同一个查询触发多个事件,则它们应具有相同的时间戳。

例如,考虑以下类型定义

type Movie {
    title: String
    genre: String
}

对属性最近更新的Movie类型任何节点的订阅应如下所示

subscription MovieUpdated {
    movieUpdated {
        event
        previousState {
            title
            genre
        }
        updatedMovie {
            title
        }
        timestamp
    }
}

DELETE

DELETE事件的订阅**仅**监听被删除的节点,而不是被删除的关系。此操作通过顶级订阅[type]Deleted执行,其中包含以下字段

  • event:触发此订阅的事件(在本例中为DELETE)。

  • deleted<typename>:已删除节点的顶级属性,不包含关系。

  • timestamp:进行变异的时间戳。如果同一个查询触发多个事件,则它们应具有相同的时间戳。

例如,考虑以下类型定义

type Movie {
    title: String
    genre: String
}

对任何已删除的Movie类型节点的订阅应如下所示

subscription {
    movieDeleted {
        deletedMovie {
            title
        }
        event
        timestamp
    }
}

CREATE_RELATIONSHIP

CREATE_RELATIONSHIP事件的订阅监听新创建的关系,并包含有关连接节点的信息。这些事件

  • 仅适用于定义关系字段的类型。

  • 包含特定于关系的信息,例如关系字段名称和包含指定类型所有关系字段名称的对象。

  • 根据创建的关系数量触发相同数量的事件,如果在变异后创建新的关系,并且目标类型负责在模式中定义两个或多个关系。

  • 包含关系对象,该对象仅填充一个关系名称的新创建的关系属性(所有其他关系名称应具有空值)。

  • 包含通过关系连接的节点的属性,以及新关系的属性(如果有)。

可能已存在或可能不存在的连接节点不包含在此订阅中。要订阅这些节点的更新,请使用CREATEUPDATE订阅。

CREATE_RELATIONSHIP事件的订阅可以使用顶级订阅[type]RelationshipCreated进行,其中包含以下字段

  • event:触发此订阅的事件(在本例中为CREATE_RELATIONSHIP)。

  • timestamp:进行变异的时间戳。如果同一个查询触发多个事件,则它们应具有相同的时间戳。

  • <typename>:目标节点的顶级属性,不包含关系,在触发CREATE_RELATIONSHIP操作之前。

  • relationshipFieldName:新创建关系的字段名称。

  • createdRelationship:一个对象,其中包含受新创建的关系影响的节点的所有字段名称。虽然与relationshipFieldName无关的任何事件都应该是null,但相关的事件应包含关系属性(如果已定义)和一个node键,其中包含关系另一侧节点的属性。仅顶级属性可用,不包含关系,并且它们是在CREATE_RELATIONSHIP操作发生之前已经存在的属性。

无论数据库中关系的方向如何,CREATE_RELATIONSHIP事件都绑定到订阅的目标类型。因此,如果类型 A 和 B 具有非互惠关系,并且 GraphQL 操作在它们之间创建关系(尽管在数据库中之前已连接),则CREATE_RELATIONSHIP事件应该只返回类型 A 的订阅。

例如,考虑以下类型定义

type Movie {
    title: String
    genre: String
    actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
    reviewers: [Reviewer] @relationship(type: "REVIEWED", direction: IN, properties: "Reviewed")
}

type Actor {
    name: String
}

type ActedIn @relationshipProperties {
    screenTime: Int!
}

type Reviewer {
    name: String
    reputation: Int
}

type Reviewed @relationshipProperties {
    score: Int!
}

现在考虑一个创建名为Tom HardyActor和一个名为JaneReviewer通过关系连接到标题为InceptionMovie的变异。在这种情况下,CREATE_RELATIONSHIP订阅应接收以下事件

{
    # 1  - relationship type `ACTED_IN`
    event: "CREATE_RELATIONSHIP",
    timestamp,
    movie: {
        title: "Inception",
        genre: "Adventure"
    },
    relationshipFieldName: "actors", # notice the field name specified here is populated below in the `createdRelationship` object
    createdRelationship: {
        actors: {
            screenTime: 1000, # relationship properties for the relationship type `ACTED_IN`
            node: { # top-level properties of the node at the other end of the relationship, in this case `Actor` type
                name: "Tom Hardy"
            }
        },
        reviewers: null # relationship declared by this field name is not covered by this event, check out the following...
    }
}
{
    # 2 - relationship type `REVIEWED`
    event: "CREATE_RELATIONSHIP",
    timestamp,
    movie: {
        title: "Inception",
        genre: "Adventure"
    },
    relationshipFieldName: "reviewers", # this event covers the relationship declared by this field name
    createdRelationship: {
        actors: null, # relationship declared by this field name is not covered by this event
        reviewers: { # field name equal to `relationshipFieldName`
            score: 8,
            node: {
                name: "Jane",
                reputation: 9
            }
        }
    }
}

标准类型

对于另一个示例,这次使用标准类型创建关系,请考虑以下类型定义

type Movie {
    title: String
    genre: String
    actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
}

type Actor {
    name: String
}

type ActedIn @relationshipProperties {
    screenTime: Int!
}

对任何具有新创建关系的Movie的订阅应如下所示

subscription {
    movieRelationshipCreated {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        createdRelationship {
            actors {
                screenTime
                node {
                    name
                }
            }
        }
    }
}

抽象类型

在使用带有关系的抽象类型时,您需要在执行订阅操作时指定一个或多个相应的具体类型。

这些类型由库生成,并符合格式[type]EventPayload,其中[type]是具体类型。

例如,考虑以下类型定义

type Movie {
    title: String
    genre: String
    directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN)
}

union Director = Person | Actor

type Actor {
    name: String
}

type Person {
    name: String
    reputation: Int
}

type Directed @relationshipProperties {
    year: Int!
}

对任何Movie新创建关系的订阅应如下所示

subscription {
    movieRelationshipCreated {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        createdRelationship {
           directors {
                year
                node {
                    ... on PersonEventPayload { # generated type
                        name
                        reputation
                    }
                    ... on ActorEventPayload { # generated type
                        name
                    }
                }
            }
        }
    }
}

接口

对于关系与接口一起创建的示例,请考虑以下类型定义

type Movie {
    title: String
    genre: String
    reviewers: [Reviewer!]! @relationship(type: "REVIEWED", properties: "Review", direction: IN)
}

interface Reviewer {
    reputation: Int!
}

type Magazine implements Reviewer {
    title: String
    reputation: Int!
}

type Influencer implements Reviewer {
    name: String
    reputation: Int!
}

type Review @relationshipProperties {
    score: Int!
}

对任何Movie新创建关系的订阅应如下所示

subscription {
    movieRelationshipCreated {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        createdRelationship {
            reviewers {
                score
                node {
                    reputation
                    ... on MagazineEventPayload { # generated type
                        title
                        reputation
                    }
                    ... on InfluencerEventPayload { # generated type
                        name
                        reputation
                    }
                }
            }
        }
    }
}

非互惠关系

例如,非互惠关系可以描述为类型 A 和类型 B 保持关系,但在 GraphQL 模式中,类型 A 是定义与 B 的关系的一个,而 B 没有定义与 A 的关系。

为了说明这一点,请考虑以下类型定义

type Movie {
    title: String
    genre: String
    actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
    directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN)
}

type Actor {
    name: String
    movies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT)
}

type Person {
    name: String
    reputation: Int
}

union Director = Person | Actor

type ActedIn @relationshipProperties {
    screenTime: Int!
}

type Directed @relationshipProperties {
    year: Int!
}

请注意,类型定义包含两个关系

  • ACTED_IN,它在MovieActor类型中都定义了相应的字段,因此可以被认为是互惠关系。

  • DIRECTED,它仅在Movie类型中定义。Director类型没有定义匹配的字段,因此可以被认为是非互惠关系。

考虑到前面描述的三种类型(MovieActorPerson),仅在Person类型的情况下,订阅CREATE_RELATIONSHIP是不可能的,因为它没有定义任何关系。对于其他两种类型,以下是订阅方法

Movie类型
subscription {
    movieRelationshipCreated {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        createdRelationship {
           actors { # corresponds to the `ACTED_IN` relationship type
                screenTime
                node {
                    name
                }
           }
           directors { # corresponds to the `DIRECTED` relationship type
                year
                node {
                    ... on PersonEventPayload {
                        name
                        reputation
                    }
                    ... on ActorEventPayload {
                        name
                    }
                }
            }
        }
    }
}
Actor类型
subscription {
    actorRelationshipCreated {
        event
        timestamp
        actor {
            name
        }
        relationshipFieldName
        createdRelationship {
           movies { # corresponds to the `ACTED_IN` relationship type
                screenTime
                node {
                    title
                    genre
                }
           }
           # no other field corresponding to the `DIRECTED` relationship type
        }
    }
}

actorRelationshipCreated订阅中createdRelationshipMovie字段的存在反映了ACTED_IN类型关系是互惠的这一事实。

因此,当创建这种类型的新关系时,例如通过运行此变异

mutation {
    createMovies(
        input: [
            {
                actors: {
                    create: [
                        {
                            node: {
                                name: "Keanu Reeves"
                            },
                            edge: {
                                screenTime: 420
                            }
                        }
                    ]
                },
                title: "John Wick",
                genre: "Action"
            }
        ]
    ) {
        movies {
            title
            genre
        }
    }
}

如果已订阅两种类型的CREATE_RELATIONSHIP事件,则应提示两个事件

{
    # from `movieRelationshipCreated`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "John Wick",
        genre: "Action"
    }
    relationshipFieldName: "actors",
    createdRelationship {
        actors: {
            screenTime: 420,
            node: {
                name: "Keanu Reeves"
            }
        },
        directors: null
    }
},
{
    # from `actorRelationshipCreated`
    event: "CREATE_RELATIONSHIP"
    timestamp
    actor {
        name: "Keanu Reeves"
    }
    relationshipFieldName: "movies",
    createdRelationship {
        movies: {
            screenTime: 420,
            node: {
                title: "John Wick",
                genre: "Action"
            }
        }
    }
}

现在,由于类型MovieDirector之间的DIRECTED关系**不是**互惠的,因此执行此变异

mutation {
    createMovies(
        input: [
            {
                directors: {
                    Actor: { # relationship 1
                        create: [
                            {
                                node: {
                                    name: "Woody Allen"
                                },
                                edge: {
                                    year: 1989
                                }
                            }
                        ]
                    },
                    Person: { # relationship 2
                        create: [
                            {
                                node: {
                                    name: "Francis Ford Coppola",
                                    reputation: 100
                                },
                                edge: {
                                    year: 1989
                                }
                            }
                        ]
                    }
                },
                title: "New York Stories",
                genre: "Comedy"
            }
        ]
    ) {
        movies {
            title
            genre
        }
    }
}

如果您已订阅了类型为 MovieCREATE_RELATIONSHIP 事件,则应触发两个事件。

{
    # relationship 1 - from `movieRelationshipCreated`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "New York Stories",
        genre: "Comedy"
    }
    relationshipFieldName: "directors",
    createdRelationship {
        actors: null,
        directors: {
            year: 1989,
            node: {
                name: "Woody Allen"
            }
        }
    }
},
{
    # relationship 2 - from `movieRelationshipCreated`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "New York Stories",
        genre: "Comedy"
    }
    relationshipFieldName: "directors",
    createdRelationship {
        actors: null,
        directors: {
            year: 1989,
            node: {
                 name: "Francis Ford Coppola",
                reputation: 100
            }
        }
    }
}

使用相同 Neo4j 标签的类型

需要考虑的一种情况是,Neo4j 标签被特定的 GraphQL 类型覆盖。这可以通过使用 @node 指令并指定 label 参数来实现。但是,在大多数情况下,**不建议**使用这种方法来设计您的 API。

例如,考虑以下类型定义

type Actor @node(label: "Person") {
    name: String
    movies: [Movie!]! @relationship(type: "PART_OF", direction: OUT)
}

typePerson {
    name: String
    movies: [Movie!]! @relationship(type: "PART_OF", direction: OUT)
}

type Movie {
    title: String
    genre: String
    people: [Person!]!  @relationship(type: "PART_OF", direction: IN)
    actors: [Actor!]!  @relationship(type: "PART_OF", direction: IN)
}

尽管示例包含 3 个 GraphQL 类型,但在 Neo4j 中,节点类型应该只有 2 种:标记为 Movie 或标记为 Person

在数据库级别,ActorPerson 之间没有区别。因此,在创建类型为 PART_OF 的新关系时,每种类型都应该有一个事件。

考虑以下订阅

subscription {
    movieRelationshipCreated {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        createdRelationship {
           people { # corresponds to the `PART_OF` relationship type
                node {
                    name
                }
           }
           actors { # corresponds to the `PART_OF` relationship type
                node {
                    name
                }
           }
        }
    }
}

subscription {
    actorRelationshipCreated {
        event
        timestamp
        actor {
            name
        }
        relationshipFieldName
        createdRelationship {
           movies { # corresponds to the `PART_OF` relationship type
                node {
                    title
                    genre
                }
           }
        }
    }
}

运行如下所示的 mutation

mutation {
    createMovies(
        input: [
            {
                people: { # relationship 1
                    create: [
                        {
                            node: {
                                name: "John Logan"
                            }
                        }
                    ]
                },
                actors: {  # relationship 2
                    create: [
                        {
                            node: {
                                name: "Johnny Depp"
                            }
                        }
                    ]
                },
                title: "Sweeney Todd",
                genre: "Horror"
            }
        ]
    ) {
        movies {
            title
            genre
        }
    }
}

应该会得到以下结果

{
    # relationship 1 `people` - for GraphQL types `Movie`, `Person`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "people",
    createdRelationship {
        people: {
            node: {
                name: "John Logan"
            }
        },
        actors: null
    }
},
{
    # relationship 1 `people` - for GraphQL types `Movie`, `Actor`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "actors",
    createdRelationship {
        people: null,
        actors: {
            node: {
                name: "John Logan"
            }
        }
    }
},
{
    # relationship 1 `movies` - for GraphQL types `Actor`, `Movie`
    event: "CREATE_RELATIONSHIP"
    timestamp
    actor {
        name: "John Logan"
    }
    relationshipFieldName: "movies",
    createdRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},
{
    # relationship 2 `actors` - for GraphQL types `Movie`,`Person`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "people",
    createdRelationship {
        people: {
            node: {
                name: "Johnny Depp"
            }
        },
        actors: null
    }
},
{
    # relationship 2 `actors` - for GraphQL types `Movie`, `Actor`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "actors",
    createdRelationship {
        people: null,
        actors: {
            node: {
                name: "Johnny Depp"
            }
        }
    }
},
{
    # relationship 2 `movies` - for GraphQL types `Actor`, `Movie`
    event: "CREATE_RELATIONSHIP"
    timestamp
    actor {
        name: "Johnny Depp"
    }
    relationshipFieldName: "movies",
    createdRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},

如果您也订阅了 Person,则应该会收到另外两个事件。

{
    # relationship 1 `movies` - for GraphQL types `Person`, `Movie`
    event: "CREATE_RELATIONSHIP"
    timestamp
    actor {
        name: "John Logan"
    }
    relationshipFieldName: "movies",
    createdRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},
{
    # relationship 2 `movies` - for GraphQL types `Person`, `Movie`
    event: "CREATE_RELATIONSHIP"
    timestamp
    actor {
        name: "Johnny Depp"
    }
    relationshipFieldName: "movies",
    createdRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},

DELETE_RELATIONSHIP

订阅 DELETE_RELATIONSHIP 事件会监听关系被删除,并包含有关指定类型先前连接节点的信息。这种订阅

  • 仅适用于定义关系字段的类型。

  • 包含特定于关系的信息,例如关系字段名称和包含指定类型所有关系字段名称的对象。此对象应根据已删除的关系填充属性。

  • 如果关系在 mutation 后被删除,并且目标类型负责在模式中定义两个或多个关系,则会触发数量相同的事件。

  • 包含使用新删除的关系属性填充的关系对象,但仅限于一个关系名称(所有其他关系名称的值应为 null)。

  • 包含通过关系连接的节点的属性,以及新删除的关系的属性(如果有)。

在此订阅中不包括可能已在过程中被删除或未被删除的断开连接的节点。要订阅这些节点的更新,请使用 DELETE 订阅。

可以使用顶级订阅 [type]RelationshipDeleted 订阅 DELETE_RELATIONSHIP 事件,该订阅包含以下字段

  • event:触发此订阅的事件(在本例中为 DELETE_RELATIONSHIP)。

  • timestamp:进行变异的时间戳。如果同一个查询触发多个事件,则它们应具有相同的时间戳。

  • <typename>:目标节点的顶级属性(不包括关系),在触发 DELETE_RELATIONSHIP 操作之前。

  • relationshipFieldName:新删除的关系的字段名称。

  • deletedRelationship:一个对象,包含受新删除的关系影响的节点的所有字段名称。虽然与 relationshipFieldName 无关的任何事件都应为 null,但相关的事件应包含关系属性(如果已定义)和一个包含关系另一侧节点属性的节点键。仅提供顶级属性(不包括关系),并且它们是在 DELETE_RELATIONSHIP 操作发生之前已存在的属性。

无论数据库中关系的方向如何,DELETE_RELATIONSHIP 事件都绑定到订阅的目标类型。因此,如果类型 A 和 B 有非互惠关系,并且 GraphQL 操作删除了它们之间的关系(尽管在数据库中之前已断开连接),则 DELETE_RELATIONSHIP 事件应仅返回对类型 A 的订阅。

例如,考虑以下类型定义

type Movie {
    title: String
    genre: String
    actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
    reviewers: [Reviewer] @relationship(type: "REVIEWED", direction: IN, properties: "Reviewed")
}

type Actor {s
    name: String
}

type ActedIn @relationshipProperties {
    screenTime: Int!
}

type Reviewer {
    name: String
    reputation: Int
}

type Reviewed @relationshipProperties {
    score: Int!
}

现在考虑一个 mutation,删除名为 Tom HardyActor 和名为 JaneReviewer,它们通过关系连接到标题为 InceptionMovie。在这种情况下,DELETE_RELATIONSHIP 订阅应接收以下事件

{
    # 1  - relationship type `ACTED_IN`
    event: "DELETE_RELATIONSHIP",
    timestamp,
    movie: {
        title: "Inception",
        genre: "Adventure"
    },
    relationshipFieldName: "actors", # notice the field name specified here is populated below in the `createdRelationship` object
    deletedRelationship: {
        actors: {
            screenTime: 1000, # relationship properties for the relationship type `ACTED_IN` that was deleted
            node: { # top-level properties of the node at the other end of the relationship, in this case `Actor` type, before the delete occurred
                name: "Tom Hardy"
            }
        },
        reviewers: null # relationship declared by this field name is not covered by this event, check out the following...
    }
}
{
    # 2 - relationship type `REVIEWED`
    event: "DELETE_RELATIONSHIP",
    timestamp,
    movie: {
        title: "Inception",
        genre: "Adventure"
    },
    relationshipFieldName: "reviewers", # this event covers the relationship declared by this field name
    deletedRelationship: {
        actors: null, # relationship declared by this field name is not covered by this event
        reviewers: { # field name equal to `relationshipFieldName`
            score: 8,
            node: {
                name: "Jane",
                reputation: 9
            }
        }
    }
}

标准类型

例如,考虑以下类型定义

type Movie {
    title: String
    genre: String
    actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
}

type Actor {
    name: String
}

type ActedIn @relationshipProperties {
    screenTime: Int!
}

对任何已删除的 Movie 关系的订阅如下所示

subscription {
    movieRelationshipDeleted {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        deletedRelationship {
            actors {
                screenTime
                node {
                    name
                }
            }
        }
    }
}

使用抽象类型删除关系

当使用带有关系的抽象类型时,您需要在执行订阅操作时指定一个或多个相应的具体类型。

这些类型由库生成,并符合 [type]EventPayload 格式,其中 [type] 是一个**具体类型**。

联合示例

考虑以下类型定义

type Movie {
    title: String
    genre: String
    directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN)
}

union Director = Person | Actor

type Actor {
    name: String
}

type Person {
    name: String
    reputation: Int
}

type Directed @relationshipProperties {
    year: Int!
}

对已删除的 Movie 关系的订阅如下所示

subscription {
    movieRelationshipDeleted {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        deletedRelationship {
           directors {
                year
                node {
                    ... on PersonEventPayload { # generated type
                        name
                        reputation
                    }
                    ... on ActorEventPayload { # generated type
                        name
                    }
                }
            }
        }
    }
}
接口示例

考虑以下类型定义

type Movie {
    title: String
    genre: String
    reviewers: [Reviewer!]! @relationship(type: "REVIEWED", properties: "Review", direction: IN)
}

interface Reviewer {
    reputation: Int!
}

type Magazine implements Reviewer {
    title: String
    reputation: Int!
}

type Influencer implements Reviewer {
    name: String
    reputation: Int!
}

type Review @relationshipProperties {
    score: Int!
}

对已删除的 Movie 关系的订阅如下所示

subscription {
    movieRelationshipDeleted {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        deletedRelationship {
            reviewers {
                score
                node {
                    reputation
                    ... on MagazineEventPayload { # generated type
                        title
                        reputation
                    }
                    ... on InfluencerEventPayload { # generated type
                        name
                        reputation
                    }
                }
            }
        }
    }
}

非互惠关系

考虑以下类型定义

type Movie {
    title: String
    genre: String
    actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
    directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN)
}

type Actor {
    name: String
    movies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT)
}

type Person {
    name: String
    reputation: Int
}

union Director = Person | Actor

type ActedIn @relationshipProperties {
    screenTime: Int!
}

type Directed @relationshipProperties {
    year: Int!
}

类型定义包含 2 个关系:类型 ACTED_INDIRECTED

可以观察到,ACTED_IN 关系在 MovieActor 类型中都定义了相应的字段。因此,我们可以说 ACTED_IN 是一个互惠关系。

另一方面,DIRECTED 仅在 Movie 类型中定义。Director 类型未定义匹配的字段。因此,我们可以说 DIRECTED **不是**互惠关系。

现在让我们看看如何订阅上面定义的 3 种类型的已删除关系

Movie
subscription {
    movieRelationshipDeleted {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        deletedRelationship {
           actors { # corresponds to the `ACTED_IN` relationship type
                screenTime
                node {
                    name
                }
           }
           directors { # corresponds to the `DIRECTED` relationship type
                year
                node {
                    ... on PersonEventPayload {
                        name
                        reputation
                    }
                    ... on ActorEventPayload {
                        name
                    }
                }
            }
        }
    }
}
Person

由于 Person 类型未定义任何关系,因此**无法**订阅此类型的 DELETE_RELATIONSHIP 事件。

Actor
subscription {
    actorRelationshipDeleted {
        event
        timestamp
        actor {
            name
        }
        relationshipFieldName
        deletedRelationship {
           movies { # corresponds to the `ACTED_IN` relationship type
                screenTime
                node {
                    title
                    genre
                }
           }
           # no other field corresponding to the `DIRECTED` relationship type
        }
    }
}

actorRelationshipDeleted 订阅中 deletedRelationship 内存在 movie 字段反映了 ACTED_IN 类型关系是互惠的事实。

因此,当此类型的关系被删除时,例如通过运行以下 mutation

mutation {
    createMovies(
        input: [
            {
                actors: {
                    create: [
                        {
                            node: {
                                name: "Keanu Reeves"
                            },
                            edge: {
                                screenTime: 420
                            }
                        }
                    ]
                },
                title: "John Wick",
                genre: "Action"
            }
        ]
    ) {
        movies {
            title
            genre
        }
    }
}

mutation {
    deleteMovies(
        where: {
            title: "John Wick"
        }
    ) {
        nodesDeleted
    }
}

将发布两个事件(假设我们已订阅了两种类型的 DELETE_RELATIONSHIP 事件)。

{
    # from `movieRelationshipDeleted`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "John Wick",
        genre: "Action"
    }
    relationshipFieldName: "actors",
    deletedRelationship {
        actors: {
            screenTime: 420,
            node: {
                name: "Keanu Reeves"
            }
        },
        directors: null
    }
},
{
    # from `actorRelationshipDeleted`
    event: "DELETE_RELATIONSHIP"
    timestamp
    actor {
        name: "Keanu Reeves"
    }
    relationshipFieldName: "movies",
    deletedRelationship {
        movies: {
            screenTime: 420,
            node: {
                title: "John Wick",
                genre: "Action"
            }
        }
    }
}

由于类型 MovieDirector 之间的 DIRECTED 关系**不是**互惠关系,因此执行以下 mutation

mutation {
    createMovies(
        input: [
            {
                directors: {
                    Actor: { # relationship 1
                        create: [
                            {
                                node: {
                                    name: "Woody Allen"
                                },
                                edge: {
                                    year: 1989
                                }
                            }
                        ]
                    },
                    Person: { # relationship 2
                        create: [
                            {
                                node: {
                                    name: "Francis Ford Coppola",
                                    reputation: 100
                                },
                                edge: {
                                    year: 1989
                                }
                            }
                        ]
                    }
                },
                title: "New York Stories",
                genre: "Comedy"
            }
        ]
    ) {
        movies {
            title
            genre
        }
    }
}

mutation {
    deleteMovies(
        where: {
            title: "New York Stories"
        }
    ) {
        nodesDeleted
    }
}

将发布两个事件(假设我们已订阅了 Movie 类型的 DELETE_RELATIONSHIP 事件)。

{
    # relationship 1 - from `movieRelationshipDeleted`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "New York Stories",
        genre: "Comedy"
    }
    relationshipFieldName: "directors",
    deletedRelationship {
        actors: null,
        directors: {
            year: 1989,
            node: {
                name: "Woody Allen"
            }
        }
    }
},
{
    # relationship 2 - from `movieRelationshipDeleted`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "New York Stories",
        genre: "Comedy"
    }
    relationshipFieldName: "directors",
    deletedRelationship {
        actors: null,
        directors: {
            year: 1989,
            node: {
                 name: "Francis Ford Coppola",
                reputation: 100
            }
        }
    }
}

特殊注意事项

使用相同 Neo4j 标签的类型

一个需要特别考虑的情况是,覆盖特定 GraphQL 类型的 Neo4j 中的标签。这可以通过使用 @node 指令并指定 label 参数来实现。

虽然本节起到了信息作用,但需要说明的是,在大多数情况下,**不建议**使用这种方法来设计您的 API。

考虑以下类型定义

type Actor @node(label: "Person") {
    name: String
    movies: [Movie!]! @relationship(type: "PART_OF", direction: OUT)
}

typePerson {
    name: String
    movies: [Movie!]! @relationship(type: "PART_OF", direction: OUT)
}

type Movie {
    title: String
    genre: String
    people: [Person!]!  @relationship(type: "PART_OF", direction: IN)
    actors: [Actor!]!  @relationship(type: "PART_OF", direction: IN)
}

尽管我们有 3 个 GraphQL 类型,但在 Neo4j 中,节点类型应该只有 2 种:标记为 Movie 或标记为 Person

在数据库级别,ActorPerson 之间没有区别。因此,在删除类型为 PART_OF 的关系时,每种类型都应该有一个事件。

考虑以下订阅

subscription {
    movieRelationshipDeleted {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        deletedRelationship {
           people { # corresponds to the `PART_OF` relationship type
                node {
                    name
                }
           }
           actors { # corresponds to the `PART_OF` relationship type
                node {
                    name
                }
           }
        }
    }
}

subscription {
    actorRelationshipDeleted {
        event
        timestamp
        actor {
            name
        }
        relationshipFieldName
        deletedRelationship {
           movies { # corresponds to the `PART_OF` relationship type
                node {
                    title
                    genre
                }
           }
        }
    }
}

运行以下 mutation

mutation {
    createMovies(
        input: [
            {
                people: { # relationship 1
                    create: [
                        {
                            node: {
                                name: "John Logan"
                            }
                        }
                    ]
                },
                actors: {  # relationship 2
                    create: [
                        {
                            node: {
                                name: "Johnny Depp"
                            }
                        }
                    ]
                },
                title: "Sweeney Todd",
                genre: "Horror"
            }
        ]
    ) {
        movies {
            title
            genre
        }
    }
}

mutation {
    deleteMovies(
        where: {
            title: "Sweeney Todd"
        }
    ) {
        nodesDeleted
    }
}

导致以下事件

{
    # relationship 1 `people` - for GraphQL types `Movie`, `Person`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "people",
    deletedRelationship {
        people: {
            node: {
                name: "John Logan"
            }
        },
        actors: null
    }
},
{
    # relationship 1 `people` - for GraphQL types `Movie`, `Actor`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "actors",
    deletedRelationship {
        people: null,
        actors: {
            node: {
                name: "John Logan"
            }
        }
    }
},
{
    # relationship 1 `movies` - for GraphQL types `Actor`, `Movie`
    event: "DELETE_RELATIONSHIP"
    timestamp
    actor {
        name: "John Logan"
    }
    relationshipFieldName: "movies",
    deletedRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},
{
    # relationship 2 `actors` - for GraphQL types `Movie`,`Person`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "people",
    deletedRelationship {
        people: {
            node: {
                name: "Johnny Depp"
            }
        },
        actors: null
    }
},
{
    # relationship 2 `actors` - for GraphQL types `Movie`, `Actor`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "actors",
    deletedRelationship {
        people: null,
        actors: {
            node: {
                name: "Johnny Depp"
            }
        }
    }
},
{
    # relationship 2 `movies` - for GraphQL types `Actor`, `Movie`
    event: "DELETE_RELATIONSHIP"
    timestamp
    actor {
        name: "Johnny Depp"
    }
    relationshipFieldName: "movies",
    deletedRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},

如果我们也订阅了 Person,则会收到另外两个事件。

{
    # relationship 1 `movies` - for GraphQL types `Person`, `Movie`
    event: "DELETE_RELATIONSHIP"
    timestamp
    actor {
        name: "John Logan"
    }
    relationshipFieldName: "movies",
    deletedRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},
{
    # relationship 2 `movies` - for GraphQL types `Person`, `Movie`
    event: "DELETE_RELATIONSHIP"
    timestamp
    actor {
        name: "Johnny Depp"
    }
    relationshipFieldName: "movies",
    deletedRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},