Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: remove legacy features preventing federating 2 PostGraphiles #25

Merged
merged 3 commits into from
Jul 28, 2021

Conversation

codef0rmerz
Copy link
Contributor

@codef0rmerz codef0rmerz commented Jul 6, 2021

## Description

This plugin suffixes the Node interface with nodeIdFieldName and deletes query/node fields from the root Query type in order to fix GraphQLSchemaValidationError: There can be only one type named "Nodequery/node" error. This helps Apollo Gateway to consume two or more PostgraphQL services.

Only requirement for this to work is to set a unique nodeIdFieldName in the options parameter of createPostGraphQLSchema/postgraphile methods per PostgraphQL service.

More Info: graphile/crystal#1505


New description by Benjie

When you attempt to federate two PostGraphile instances you get various errors; these turn out to boil down to conflicts on fields on the Query type itself. By removing legacy features from the Query type, two or more PostGraphile instances may be merged via Apollo Federation.

Legacy features removed:

  • Query.query: Query field - a hack for Relay 1, not required for modern clients
  • Query implementing the Node interface - there's no need for Query to implement Node; we did it "for completeness" but I've never seen anyone using it in a valuable way
  • Query.nodeId field - see above - no need.
  • Query.node(id: ID!): Node field - this is required as part of the GraphQL Global Object Identification Specification spec, however we're not implementing that spec with Apollo Federation so we should be able to drop it.

🚨 THIS IS A BREAKING CHANGE 🚨

@codef0rmerz
Copy link
Contributor Author

@benjie can you review it?

@benjie
Copy link
Member

benjie commented Jul 14, 2021

It's a breaking change so I cannot merge it at this time, I'm also uncomfortable with renaming the nodeId field purely for Apollo Federation compatibility; however people can use the plugin that you have written here with PostGraphile without needing this to be merged into this plugin, so it's a great example for others - thanks!

src/index.ts Outdated Show resolved Hide resolved
src/index.ts Outdated Show resolved Hide resolved
@codef0rmerz codef0rmerz force-pushed the feat/apollo-federation branch 3 times, most recently from 0ceb7c3 to 732265c Compare July 16, 2021 15:57
@codef0rmerz
Copy link
Contributor Author

@benjie I've improved the implementation so that it will not affect Relay. Kindly review and let me know if it's possible to merge now?

Copy link
Member

@benjie benjie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be cleaner to simply delete the query and node fields from the Query type. Since the Relay object identification specification isn't really supported by Apollo Federation anyway, I don't think keeping node is necessary; and the query field was always a hack/workaround for really old Relay anyway and is not needed for modern GraphQL clients.

@codef0rmerz codef0rmerz force-pushed the feat/apollo-federation branch from 732265c to bbe7633 Compare July 17, 2021 07:33
@codef0rmerz
Copy link
Contributor Author

codef0rmerz commented Jul 17, 2021

I think it would be cleaner to simply delete the query and node fields from the Query type. Since the Relay object identification specification isn't really supported by Apollo Federation anyway, I don't think keeping node is necessary; and the query field was always a hack/workaround for really old Relay anyway and is not needed for modern GraphQL clients.

@benjie Done. I've checked Apollo Federation which works fine regardless of query/node fields.

Copy link
Member

@benjie benjie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we don't actually need to rename the nodeIdFieldName, we just need an alias to use for the Node type itself - if this is the case, can you use a new separate option instead such as nodeTypeName and use that directly: if (value === 'Node') { return nodeTypeName; } and update the comments to match?

@codef0rmerz
Copy link
Contributor Author

It looks like we don't actually need to rename the nodeIdFieldName, we just need an alias to use for the Node type itself - if this is the case, can you use a new separate option instead such as nodeTypeName and use that directly: if (value === 'Node') { return nodeTypeName; } and update the comments to match?

I actually wanted to reuse the value of nodeIdFieldName because for Apollo Federation to work with two or more PostgraphQL services, each one has to have a unique nodeId. Having said that we can reuse the same nodeId as a prefix for Node interface.

So I think the current implementation is valid since users have less burden to set correct configurations for it to work.
Alternatively, we can just use random prefix without introducing nodeTypeName option as well.

Let me know what you think?

@benjie
Copy link
Member

benjie commented Jul 17, 2021

Where does the nodeId field name conflict occur, and why does it not occur for other fields such as id or username? (Keep in mind I'm not super familiar with Apollo Federation, this is a genuine question.)

@codef0rmerz
Copy link
Contributor Author

Where does the nodeId field name conflict occur, and why does it not occur for other fields such as id or username? (Keep in mind I'm not super familiar with Apollo Federation, this is a genuine question.)

When two or more PostgraphQL services are consumed by Apollo Gateway, the nodeId type conflicts occur with Field "Query.nodeId" can only be defined once. error. Hence, in order to use more than one PostgraphQL service with Apollo Federation, one has to set a unique nodeIdFieldName.

@benjie
Copy link
Member

benjie commented Jul 18, 2021

Please delete the Query.nodeId field and test again 👍

@codef0rmerz
Copy link
Contributor Author

Please delete the Query.nodeId field and test again 👍

The nodeId field can not be deleted since it's a primary key by default to many queries/mutations.

@benjie
Copy link
Member

benjie commented Jul 19, 2021

Just the nodeId on the Query type, not all of them 👍

@codef0rmerz
Copy link
Contributor Author

codef0rmerz commented Jul 19, 2021

Just the nodeId on the Query type, not all of them 👍

Can not delete it because of Interface field Node.nodeId expected but Query does not provide it. error. That's why I am prefixing Node interface with nodeIdFieldName

@benjie
Copy link
Member

benjie commented Jul 20, 2021

Ah; remove the Node interface from Query too 👍

@codef0rmerz
Copy link
Contributor Author

Ah; remove the Node interface from Query too 👍

We should not remove Node interface because it Relay related. Plus, we have to remove it from lots of types not just Query.

Here is a default schema (without Federation) for my postgraphql database having two tables, securities and securityschedules for you to understand the depth of the issue.

"""The root query type which gives access points into the data universe."""
type Query implements Node {
  """
  Exposes the root query type nested one level down. This is helpful for Relay 1
  which can only query top level fields if they are in a particular form.
  """
  query: Query!

  """
  The root query type must be a `Node` to work well with Relay 1 mutations. This just resolves to `query`.
  """
  nodeId: ID!

  """Fetches an object given its globally unique `ID`."""
  node(
    """The globally unique `ID`."""
    nodeId: ID!
  ): Node

  """Reads and enables pagination through a set of `Security`."""
  allSecurities(
    """Only read the first `n` values of the set."""
    first: Int

    """Only read the last `n` values of the set."""
    last: Int

    """
    Skip the first `n` values from our `after` cursor, an alternative to cursor
    based pagination. May not be used with `last`.
    """
    offset: Int

    """Read all values in the set before (above) this cursor."""
    before: Cursor

    """Read all values in the set after (below) this cursor."""
    after: Cursor

    """The method to use when ordering `Security`."""
    orderBy: [SecuritiesOrderBy!] = [PRIMARY_KEY_ASC]

    """
    A condition to be used in determining which values should be returned by the collection.
    """
    condition: SecurityCondition
  ): SecuritiesConnection

  """Reads and enables pagination through a set of `Securityschedule`."""
  allSecurityschedules(
    """Only read the first `n` values of the set."""
    first: Int

    """Only read the last `n` values of the set."""
    last: Int

    """
    Skip the first `n` values from our `after` cursor, an alternative to cursor
    based pagination. May not be used with `last`.
    """
    offset: Int

    """Read all values in the set before (above) this cursor."""
    before: Cursor

    """Read all values in the set after (below) this cursor."""
    after: Cursor

    """The method to use when ordering `Securityschedule`."""
    orderBy: [SecurityschedulesOrderBy!] = [PRIMARY_KEY_ASC]

    """
    A condition to be used in determining which values should be returned by the collection.
    """
    condition: SecurityscheduleCondition
  ): SecurityschedulesConnection
  securityById(id: Int!): Security
  securityscheduleById(id: Int!): Securityschedule

  """Reads a single `Security` using its globally unique `ID`."""
  security(
    """The globally unique `ID` to be used in selecting a single `Security`."""
    nodeId: ID!
  ): Security

  """Reads a single `Securityschedule` using its globally unique `ID`."""
  securityschedule(
    """
    The globally unique `ID` to be used in selecting a single `Securityschedule`.
    """
    nodeId: ID!
  ): Securityschedule
}

"""An object with a globally unique `ID`."""
interface Node {
  """
  A globally unique identifier. Can be used in various places throughout the system to identify this single value.
  """
  nodeId: ID!
}

"""A connection to a list of `Security` values."""
type SecuritiesConnection {
  """A list of `Security` objects."""
  nodes: [Security]!

  """
  A list of edges which contains the `Security` and cursor to aid in pagination.
  """
  edges: [SecuritiesEdge!]!

  """Information to aid in pagination."""
  pageInfo: PageInfo!

  """The count of *all* `Security` you could get from the connection."""
  totalCount: Int!
}

type Security implements Node {
  """
  A globally unique identifier. Can be used in various places throughout the system to identify this single value.
  """
  nodeId: ID!
  id: Int!
  name: String!

  """Reads and enables pagination through a set of `Securityschedule`."""
  securityschedulesBySecuritymasterid(
    """Only read the first `n` values of the set."""
    first: Int

    """Only read the last `n` values of the set."""
    last: Int

    """
    Skip the first `n` values from our `after` cursor, an alternative to cursor
    based pagination. May not be used with `last`.
    """
    offset: Int

    """Read all values in the set before (above) this cursor."""
    before: Cursor

    """Read all values in the set after (below) this cursor."""
    after: Cursor

    """The method to use when ordering `Securityschedule`."""
    orderBy: [SecurityschedulesOrderBy!] = [PRIMARY_KEY_ASC]

    """
    A condition to be used in determining which values should be returned by the collection.
    """
    condition: SecurityscheduleCondition
  ): SecurityschedulesConnection!
}

"""A connection to a list of `Securityschedule` values."""
type SecurityschedulesConnection {
  """A list of `Securityschedule` objects."""
  nodes: [Securityschedule]!

  """
  A list of edges which contains the `Securityschedule` and cursor to aid in pagination.
  """
  edges: [SecurityschedulesEdge!]!

  """Information to aid in pagination."""
  pageInfo: PageInfo!

  """
  The count of *all* `Securityschedule` you could get from the connection.
  """
  totalCount: Int!
}

type Securityschedule implements Node {
  """
  A globally unique identifier. Can be used in various places throughout the system to identify this single value.
  """
  nodeId: ID!
  id: Int!
  striketime: String!
  securitymasterid: Int

  """Reads a single `Security` that is related to this `Securityschedule`."""
  securityBySecuritymasterid: Security
}

"""A `Securityschedule` edge in the connection."""
type SecurityschedulesEdge {
  """A cursor for use in pagination."""
  cursor: Cursor

  """The `Securityschedule` at the end of the edge."""
  node: Securityschedule
}

"""A location in a connection that can be used for resuming pagination."""
scalar Cursor

"""Information about pagination in a connection."""
type PageInfo {
  """When paginating forwards, are there more items?"""
  hasNextPage: Boolean!

  """When paginating backwards, are there more items?"""
  hasPreviousPage: Boolean!

  """When paginating backwards, the cursor to continue."""
  startCursor: Cursor

  """When paginating forwards, the cursor to continue."""
  endCursor: Cursor
}

"""Methods to use when ordering `Securityschedule`."""
enum SecurityschedulesOrderBy {
  NATURAL
  ID_ASC
  ID_DESC
  STRIKETIME_ASC
  STRIKETIME_DESC
  SECURITYMASTERID_ASC
  SECURITYMASTERID_DESC
  PRIMARY_KEY_ASC
  PRIMARY_KEY_DESC
}

"""
A condition to be used against `Securityschedule` object types. All fields are
tested for equality and combined with a logical ‘and.’
"""
input SecurityscheduleCondition {
  """Checks for equality with the object’s `id` field."""
  id: Int

  """Checks for equality with the object’s `striketime` field."""
  striketime: String

  """Checks for equality with the object’s `securitymasterid` field."""
  securitymasterid: Int
}

"""A `Security` edge in the connection."""
type SecuritiesEdge {
  """A cursor for use in pagination."""
  cursor: Cursor

  """The `Security` at the end of the edge."""
  node: Security
}

"""Methods to use when ordering `Security`."""
enum SecuritiesOrderBy {
  NATURAL
  ID_ASC
  ID_DESC
  NAME_ASC
  NAME_DESC
  PRIMARY_KEY_ASC
  PRIMARY_KEY_DESC
}

"""
A condition to be used against `Security` object types. All fields are tested
for equality and combined with a logical ‘and.’
"""
input SecurityCondition {
  """Checks for equality with the object’s `id` field."""
  id: Int

  """Checks for equality with the object’s `name` field."""
  name: String
}

"""
The root mutation type which contains root level fields which mutate data.
"""
type Mutation {
  """Creates a single `Security`."""
  createSecurity(
    """
    The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
    """
    input: CreateSecurityInput!
  ): CreateSecurityPayload

  """Creates a single `Securityschedule`."""
  createSecurityschedule(
    """
    The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
    """
    input: CreateSecurityscheduleInput!
  ): CreateSecurityschedulePayload

  """Updates a single `Security` using its globally unique id and a patch."""
  updateSecurity(
    """
    The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
    """
    input: UpdateSecurityInput!
  ): UpdateSecurityPayload

  """Updates a single `Security` using a unique key and a patch."""
  updateSecurityById(
    """
    The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
    """
    input: UpdateSecurityByIdInput!
  ): UpdateSecurityPayload

  """
  Updates a single `Securityschedule` using its globally unique id and a patch.
  """
  updateSecurityschedule(
    """
    The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
    """
    input: UpdateSecurityscheduleInput!
  ): UpdateSecurityschedulePayload

  """Updates a single `Securityschedule` using a unique key and a patch."""
  updateSecurityscheduleById(
    """
    The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
    """
    input: UpdateSecurityscheduleByIdInput!
  ): UpdateSecurityschedulePayload

  """Deletes a single `Security` using its globally unique id."""
  deleteSecurity(
    """
    The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
    """
    input: DeleteSecurityInput!
  ): DeleteSecurityPayload

  """Deletes a single `Security` using a unique key."""
  deleteSecurityById(
    """
    The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
    """
    input: DeleteSecurityByIdInput!
  ): DeleteSecurityPayload

  """Deletes a single `Securityschedule` using its globally unique id."""
  deleteSecurityschedule(
    """
    The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
    """
    input: DeleteSecurityscheduleInput!
  ): DeleteSecurityschedulePayload

  """Deletes a single `Securityschedule` using a unique key."""
  deleteSecurityscheduleById(
    """
    The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields.
    """
    input: DeleteSecurityscheduleByIdInput!
  ): DeleteSecurityschedulePayload
}

"""The output of our create `Security` mutation."""
type CreateSecurityPayload {
  """
  The exact same `clientMutationId` that was provided in the mutation input,
  unchanged and unused. May be used by a client to track mutations.
  """
  clientMutationId: String

  """The `Security` that was created by this mutation."""
  security: Security

  """
  Our root query field type. Allows us to run any query from our mutation payload.
  """
  query: Query

  """An edge for our `Security`. May be used by Relay 1."""
  securityEdge(
    """The method to use when ordering `Security`."""
    orderBy: [SecuritiesOrderBy!] = [PRIMARY_KEY_ASC]
  ): SecuritiesEdge
}

"""All input for the create `Security` mutation."""
input CreateSecurityInput {
  """
  An arbitrary string value with no semantic meaning. Will be included in the
  payload verbatim. May be used to track mutations by the client.
  """
  clientMutationId: String

  """The `Security` to be created by this mutation."""
  security: SecurityInput!
}

"""An input for mutations affecting `Security`"""
input SecurityInput {
  id: Int
  name: String!
}

"""The output of our create `Securityschedule` mutation."""
type CreateSecurityschedulePayload {
  """
  The exact same `clientMutationId` that was provided in the mutation input,
  unchanged and unused. May be used by a client to track mutations.
  """
  clientMutationId: String

  """The `Securityschedule` that was created by this mutation."""
  securityschedule: Securityschedule

  """
  Our root query field type. Allows us to run any query from our mutation payload.
  """
  query: Query

  """Reads a single `Security` that is related to this `Securityschedule`."""
  securityBySecuritymasterid: Security

  """An edge for our `Securityschedule`. May be used by Relay 1."""
  securityscheduleEdge(
    """The method to use when ordering `Securityschedule`."""
    orderBy: [SecurityschedulesOrderBy!] = [PRIMARY_KEY_ASC]
  ): SecurityschedulesEdge
}

"""All input for the create `Securityschedule` mutation."""
input CreateSecurityscheduleInput {
  """
  An arbitrary string value with no semantic meaning. Will be included in the
  payload verbatim. May be used to track mutations by the client.
  """
  clientMutationId: String

  """The `Securityschedule` to be created by this mutation."""
  securityschedule: SecurityscheduleInput!
}

"""An input for mutations affecting `Securityschedule`"""
input SecurityscheduleInput {
  id: Int
  striketime: String!
  securitymasterid: Int
}

"""The output of our update `Security` mutation."""
type UpdateSecurityPayload {
  """
  The exact same `clientMutationId` that was provided in the mutation input,
  unchanged and unused. May be used by a client to track mutations.
  """
  clientMutationId: String

  """The `Security` that was updated by this mutation."""
  security: Security

  """
  Our root query field type. Allows us to run any query from our mutation payload.
  """
  query: Query

  """An edge for our `Security`. May be used by Relay 1."""
  securityEdge(
    """The method to use when ordering `Security`."""
    orderBy: [SecuritiesOrderBy!] = [PRIMARY_KEY_ASC]
  ): SecuritiesEdge
}

"""All input for the `updateSecurity` mutation."""
input UpdateSecurityInput {
  """
  An arbitrary string value with no semantic meaning. Will be included in the
  payload verbatim. May be used to track mutations by the client.
  """
  clientMutationId: String

  """
  The globally unique `ID` which will identify a single `Security` to be updated.
  """
  nodeId: ID!

  """
  An object where the defined keys will be set on the `Security` being updated.
  """
  securityPatch: SecurityPatch!
}

"""
Represents an update to a `Security`. Fields that are set will be updated.
"""
input SecurityPatch {
  id: Int
  name: String
}

"""All input for the `updateSecurityById` mutation."""
input UpdateSecurityByIdInput {
  """
  An arbitrary string value with no semantic meaning. Will be included in the
  payload verbatim. May be used to track mutations by the client.
  """
  clientMutationId: String

  """
  An object where the defined keys will be set on the `Security` being updated.
  """
  securityPatch: SecurityPatch!
  id: Int!
}

"""The output of our update `Securityschedule` mutation."""
type UpdateSecurityschedulePayload {
  """
  The exact same `clientMutationId` that was provided in the mutation input,
  unchanged and unused. May be used by a client to track mutations.
  """
  clientMutationId: String

  """The `Securityschedule` that was updated by this mutation."""
  securityschedule: Securityschedule

  """
  Our root query field type. Allows us to run any query from our mutation payload.
  """
  query: Query

  """Reads a single `Security` that is related to this `Securityschedule`."""
  securityBySecuritymasterid: Security

  """An edge for our `Securityschedule`. May be used by Relay 1."""
  securityscheduleEdge(
    """The method to use when ordering `Securityschedule`."""
    orderBy: [SecurityschedulesOrderBy!] = [PRIMARY_KEY_ASC]
  ): SecurityschedulesEdge
}

"""All input for the `updateSecurityschedule` mutation."""
input UpdateSecurityscheduleInput {
  """
  An arbitrary string value with no semantic meaning. Will be included in the
  payload verbatim. May be used to track mutations by the client.
  """
  clientMutationId: String

  """
  The globally unique `ID` which will identify a single `Securityschedule` to be updated.
  """
  nodeId: ID!

  """
  An object where the defined keys will be set on the `Securityschedule` being updated.
  """
  securityschedulePatch: SecurityschedulePatch!
}

"""
Represents an update to a `Securityschedule`. Fields that are set will be updated.
"""
input SecurityschedulePatch {
  id: Int
  striketime: String
  securitymasterid: Int
}

"""All input for the `updateSecurityscheduleById` mutation."""
input UpdateSecurityscheduleByIdInput {
  """
  An arbitrary string value with no semantic meaning. Will be included in the
  payload verbatim. May be used to track mutations by the client.
  """
  clientMutationId: String

  """
  An object where the defined keys will be set on the `Securityschedule` being updated.
  """
  securityschedulePatch: SecurityschedulePatch!
  id: Int!
}

"""The output of our delete `Security` mutation."""
type DeleteSecurityPayload {
  """
  The exact same `clientMutationId` that was provided in the mutation input,
  unchanged and unused. May be used by a client to track mutations.
  """
  clientMutationId: String

  """The `Security` that was deleted by this mutation."""
  security: Security
  deletedSecurityId: ID

  """
  Our root query field type. Allows us to run any query from our mutation payload.
  """
  query: Query

  """An edge for our `Security`. May be used by Relay 1."""
  securityEdge(
    """The method to use when ordering `Security`."""
    orderBy: [SecuritiesOrderBy!] = [PRIMARY_KEY_ASC]
  ): SecuritiesEdge
}

"""All input for the `deleteSecurity` mutation."""
input DeleteSecurityInput {
  """
  An arbitrary string value with no semantic meaning. Will be included in the
  payload verbatim. May be used to track mutations by the client.
  """
  clientMutationId: String

  """
  The globally unique `ID` which will identify a single `Security` to be deleted.
  """
  nodeId: ID!
}

"""All input for the `deleteSecurityById` mutation."""
input DeleteSecurityByIdInput {
  """
  An arbitrary string value with no semantic meaning. Will be included in the
  payload verbatim. May be used to track mutations by the client.
  """
  clientMutationId: String
  id: Int!
}

"""The output of our delete `Securityschedule` mutation."""
type DeleteSecurityschedulePayload {
  """
  The exact same `clientMutationId` that was provided in the mutation input,
  unchanged and unused. May be used by a client to track mutations.
  """
  clientMutationId: String

  """The `Securityschedule` that was deleted by this mutation."""
  securityschedule: Securityschedule
  deletedSecurityscheduleId: ID

  """
  Our root query field type. Allows us to run any query from our mutation payload.
  """
  query: Query

  """Reads a single `Security` that is related to this `Securityschedule`."""
  securityBySecuritymasterid: Security

  """An edge for our `Securityschedule`. May be used by Relay 1."""
  securityscheduleEdge(
    """The method to use when ordering `Securityschedule`."""
    orderBy: [SecurityschedulesOrderBy!] = [PRIMARY_KEY_ASC]
  ): SecurityschedulesEdge
}

"""All input for the `deleteSecurityschedule` mutation."""
input DeleteSecurityscheduleInput {
  """
  An arbitrary string value with no semantic meaning. Will be included in the
  payload verbatim. May be used to track mutations by the client.
  """
  clientMutationId: String

  """
  The globally unique `ID` which will identify a single `Securityschedule` to be deleted.
  """
  nodeId: ID!
}

"""All input for the `deleteSecurityscheduleById` mutation."""
input DeleteSecurityscheduleByIdInput {
  """
  An arbitrary string value with no semantic meaning. Will be included in the
  payload verbatim. May be used to track mutations by the client.
  """
  clientMutationId: String
  id: Int!
}

@benjie
Copy link
Member

benjie commented Jul 20, 2021

Please remove the Node interface from Query and let me know the next error you get. Here's a plugin to achieve that:

https://github.com/graphile/gatsby-source-pg/blob/master/lib/RemoveNodeInterfaceFromQueryPlugin.js

The Query type supporting the Node interface is not, as far as I know, required by Relay.

@codef0rmerz
Copy link
Contributor Author

codef0rmerz commented Jul 20, 2021

@benjie Removed the interface and then deleted Query.nodeId field as well for Apollo Gateway to work. But,

  1. There are other queries/mutations which take nodeId as param, not sure what will happen to them since we can not query nodeId anymore.

  2. I found this a basic schema which supports the Relay Node interface. in https://www.graphile.org/graphile-build/getting-started, regarding Node Interface's connection with Relay.

Let me know if you still want to proceed (I still feel that just renaming nodeIdFieldName is better than removing the Node interface) and I'll update the MR.

@benjie
Copy link
Member

benjie commented Jul 21, 2021

since we can not query nodeId anymore.

Why not? We only removed it from Query where it was causing conflicts, not any of the other types where it’s actually useful.

I still feel that just renaming nodeIdFieldName is better than removing the Node interface

Again we’re only removing it from Query, not from User or Forum or Post or any other type. Honestly it’s not particularly useful having it on Query, most Relay schemas do not have a Node interface on the Query type, it’s fine.

I’m trying to lead us towards the most minimally invasive way of addressing the conflicts you raise. I’m hoping Federation is smart enough to spot the Node interface is equivalent on both schemas and use it as-is regardless of naming conflict.

@codef0rmerz codef0rmerz force-pushed the feat/apollo-federation branch from bbe7633 to 26928fb Compare July 21, 2021 11:43
@codef0rmerz
Copy link
Contributor Author

codef0rmerz commented Jul 21, 2021

Why not? We only removed it from Query where it was causing conflicts, not any of the other types where it’s actually useful.

=> My Bad, do not know what I was thinking (may be late night OSS 😄). You can query nodeId as before.

Again we’re only removing it from Query, not from User or Forum or Post or any other type. Honestly it’s not particularly useful having it on Query, most Relay schemas do not have a Node interface on the Query type, it’s fine.

=> Makes sense.

I’m trying to lead us towards the most minimally invasive way of addressing the conflicts you raise. I’m hoping Federation is smart enough to spot the Node interface is equivalent on both schemas and use it as-is regardless of naming conflict.

=> Agree. Have pushed the updates, Please take a look.

@benjie
Copy link
Member

benjie commented Jul 22, 2021

Gosh look at this, isn't it beautifully simple?! 🙌

Does it definitely work?

@codef0rmerz
Copy link
Contributor Author

@benjie Yeah, I'm also amazed at the final fix after spending so many hours to figure every possible way 😄

Indeed, it is working.

image

src/index.ts Outdated Show resolved Hide resolved
src/index.ts Outdated Show resolved Hide resolved
src/index.ts Outdated Show resolved Hide resolved
src/index.ts Show resolved Hide resolved
@benjie benjie changed the title feat(ApolloGateway): rename Node/PageInfo/query/node/cursor types to be unique per PostgraphQL service fix: remove legacy features from Query that prevent two PostGraphile instances being federated Jul 26, 2021
@benjie
Copy link
Member

benjie commented Jul 26, 2021

@codef0rmerz I've merged in the changes I would like and have updated the issue description and title; if you can get CI to pass then we can merge 👍 You should be able to run yarn test -u to update the snapshots which'll get you some of the way there; you may need to run prettier against the codebase too.

Please do not rebase. We'll be squash merging at the end anyway; by not rebasing you make my code review easier.

@codef0rmerz
Copy link
Contributor Author

@benjie Fixed the broken tests.

@codef0rmerz
Copy link
Contributor Author

@benjie I can see that you have approved the PR but do not think I can merge it. Can you?

@benjie benjie changed the title fix: remove legacy features from Query that prevent two PostGraphile instances being federated fix: remove legacy features preventing federating 2 PostGraphiles Jul 28, 2021
@benjie benjie merged commit d5b6564 into graphile:master Jul 28, 2021
@benjie
Copy link
Member

benjie commented Jul 28, 2021

Released in @graphile/[email protected] after adding #28 and #29

@codef0rmerz
Copy link
Contributor Author

Thanks @benjie

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants