-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Proposal: Allow interfaces to implement other interfaces #295
Comments
As I've continued the think about this, I wanted to clarify a point: If an interface implements another interface, it MUST specify compatible fields in its own This keeps all the simplicity of the current system (with the total absence of inheritance, and a flat interface implementation strategy) while adding the functionality of chained interfaces. |
I also have a "real world" use case for this and would love to see this added to the spec. What are the concerns? |
@mike-marcacci, only a suggestion: would it be possible to consider using |
Hi @stanishev! Thanks for the comment – I definitely thought about using this terminology, and considered how it might be interpreted by programmers of various languages. When writing this issue, I decided against In fact, it's quite possible that there is a use-case for true inheritance (by which I mean applying the field definitions of one or more parents), while my use case simply needs the ability to describe a hierarchy. I would argue that Also, I wanted to avoid bringing into the discussion the complexities of inference in general: In what order of precedence might a multi-parent inheritance effected? What happens when parents have non-identical (but potentially compatible) field types (type variance)? Can I "sub-class" a parent to have a non-compatible field type? etc... All that said, if the GraphQL team chose to add this to the spec under the keyword |
Not sure how mixins/inheritance got involved, but could that discussion happen in a new thread if you'd like to advocate for it? Either of those concepts would be more complicated than what this issue proposes, and I'm very interesting in seeing this proposal added so would rather not see the discussion veer off. (Mixins sound interesting, but the GraphQL team seems acutely interested in keeping the spec as simple as it can possibly be, and IMHO mixins are not simple.) Any thoughts from the core team on this? Would you be open to a PR? I'd love to help @mike-marcacci out with this but wouldn't want to waste the effort if you think it's unlikely to be accepted. |
While this is being discussed, does anyone have a clean workaround that they are using? |
I would also make use of this at Zillow, where our type system includes a hierarchy of property records, in which the most basic type is a PublicRecord, which is extended by a PropertyRecord (includes attributes added by Zillow systems), which in turn is extended by a PropertyListing (includes attributes added by an agent or owner). Is anyone already working on a PR to incorporate this proposal into the spec? |
@MatthewHerbst @jwb While I don't want to reintroduce mixins/partials as a solution to this issue, I do wanted to point you to graphql/graphql-js#703 (comment) and the solution I outlined there and the transpiler/polyfill I created to solve my need for making my schema more DRY by adding mixins/partials: https://github.com/Sydsvenskan/node-graphql-partials (It's a solution that would also help in making heavy usage of interfaces manageable – ensuring all implementations are in sync with the fields defined) @tylercrompton Did you create another issue for the mixin case? Would be good to link graphql/graphql-js#703 to that one if so |
If mixin support were added, I would make use of it. I feel that enabling inheritance of interfaces is a basic feature of a type system and I need it more than I need mixins, since I can accomplish what I would use from mixins (minus inheritance) with code generation. |
For anybody following along, I went ahead and made a PR to add this to the spec: #373 If I can get some kind of indication from the GraphQL WG that this would be considered, I'll go ahead and implement it in graphql-js. |
Thanks for pressing forward with this, @mike-marcacci, and my apologies for not addressing this PR much earlier. Perhaps you could speak to this at the next GraphQL working group meeting (I'm working on putting up an agenda for that today). I believe we considered this during the original design and turned it down for YAGNI reasons. It's just much simpler to have Object types describe all the interfaces they implement directly rather than having to walk a hierarchy. I think having concrete examples of type systems which are challenging or unsustainable without this feature could make a really compelling argument. My opinion based on what you've drafted up so far is that it's eminently reasonable, and requiring |
@leebyron thanks so much for the very thoughtful response! I'll definitely join the next WG meeting if that would be helpful (I just subscribed to the wg repo so I can add myself once the agenda is up).
I thought about requiring implementing types to re-declare indirectly-implemented interfaces. In the toy example above, then,
I definitely understand the resulting change cost here. I skimmed through the WG notes and CONTRIBUTING.md but didn't see any formal policy on how/when breaking changes are applied to the spec. I'll work on a companion PR to graphql-js and dive into relay to get a better understanding of what kind of changes this would require. |
Adding myself [as suggested](graphql/graphql-spec#295 (comment)) to talk about allowing interfaces to implement other interfaces.
This is implemented in graphql/graphql-js#1218 |
Per discussion at this week's WG meeting, I set out to better describe my real-world case here as a justification for this feature, as opposed to the highly contrived example from this issue. Here's the essence of my use-case, but using Connection terminology to avoid having to explain the intricacies of my system: interface Node {
id: ID!
}
interface Edge {
cursor: String
node: Node
}
interface Connection {
pageInfo: PageInfo!
edges: [Edge]
}
type PageInfo {
hasPreviousPage: Boolean
hasNextPage: Boolean
startCursor: String
endCursor: String
}
interface NamedNode {
id: ID!
name: String
}
type NamedEdge implements Edge {
cursor: String
node: NamedNode
}
type NamedConnection implements Connection {
pageInfo: PageInfo!
edges: [NamedEdge]
}
type SomeNamedThing implements NamedNode & Node {
id: ID!
name: String
}
type Query {
someQuery: NamedConnection
} Note that this schema fails validation with: [
{
"locations": [
{
"column": 9
"line": 7
}
{
"column": 9
"line": 29
}
]
"message": "Interface field Edge.node expects type Node but NamedEdge.node is type NamedNode."
"path": [undefined]
}
] the solution is simply to allow interface NamedNode implements Node {
id: ID!
name: String
} Using this updated schema in the proposal's branch of graphql-js works exactly as expected and is able to simultaneously communicate that:
query {
someQuery {
edges {
node {
name
}
}
}
}
EDIT: I've circled back to this proposal. While queries can be generated without this, you lose out on the expressiveness and type guarantees that graphql provides natively. Hierarchal interfaces would cause compile-time validation errors for invalid schemas, which are far preferable to runtime bugs. So while I do believe this proposal generally improves the expressiveness of GraphQL and I think the example above clearly demonstrates a limitation of the current spec, I am going to ease off my campaign for it. I'd like to leave these issues/PRs open until the next WG meeting to allow anybody else with a real-world use case to chime in. I'd also really be interested in any estimations of change cost, particularly from the perspective of tooling maintainers. If we don't have a compelling case for this change at the next meeting, I'll happily close these issues :-) |
@mike-marcacci Great post. Can you please show any of the code you used to achieve the workaround? |
Hey @MatthewHerbst, I’m not actually using this for connections (I let relay handle that) but instead custom wrapper types for revision control & offline support, so none of my code is going to make sense without a lot of explaining. However, I can tell you that I essentially keep a list of |
Another use, also tied with pagination is when different interfaces need to be paginated. Suppose we have an interface Profile, and two implementations: UserProfile and TeamProfile Within the current featureset, I cannot see a solution to be able to deduct that UserProfilePagnated and TeamProfilePaginated can be supplied where we are expecting PaginedProfiles. My first thought was generics and covariance: With generics - although not trivial, one can make a complex analyzer that understands the notion of co and contravariance, and can deduct that PaginatedProfiles is an interface of UserPaginatedProfiles, although one might need to signal this somehow. But @mike-marcacci's interface extensions could also be a solution, if the interfaces redefined the pagination fields |
Github GraphQL API uses interfaces https://developer.github.com/v4/interface |
This adds `inheritResolversFromInterfaces` to `addResolveFunctionsToSchema` and `makeExecutableSchema` Later on we'll want to support interfaces implementing other interfaces graphql/graphql-spec#295
This adds `inheritResolversFromInterfaces` to `addResolveFunctionsToSchema` and `makeExecutableSchema` Later on we'll want to support interfaces implementing other interfaces graphql/graphql-spec#295
This adds `inheritResolversFromInterfaces` to `addResolveFunctionsToSchema` and `makeExecutableSchema` Later on we'll want to support interfaces implementing other interfaces graphql/graphql-spec#295
This adds `inheritResolversFromInterfaces` to `addResolveFunctionsToSchema` and `makeExecutableSchema` Later on we'll want to support interfaces implementing other interfaces graphql/graphql-spec#295
This adds `inheritResolversFromInterfaces` to `addResolveFunctionsToSchema` and `makeExecutableSchema` Later on we'll want to support interfaces implementing other interfaces graphql/graphql-spec#295
This adds a `inheritResolversFromInterfaces` option to `addResolveFunctionsToSchema` and `makeExecutableSchema`. Later on we'll want to support interfaces implementing other interfaces: graphql/graphql-spec#295
I'm sorry that I can't point at code, but I really want to my schema to express the relationship:
|
In an effort to reduce the simultaneous channels of discussion, I'm going to close this issue in favor of the open #373 where this RFC has continued to develop. |
Adding myself [as suggested](graphql/graphql-spec#295 (comment)) to talk about allowing interfaces to implement other interfaces.
This proposal expands upon the ideas described in #294 (which ended up already being implemented).
I'd like to suggest that GraphQL be expanded to allow an
interface
toimplement
another interface. This would allow for higher order interfaces and open the door for frameworks and tooling to describe their requirements in GraphQL, which would be implemented in userland interfaces and types.Here's a (very contrived) example borrowed from #294 and expanded upon to show how hierarchies would exist:
What's important here is that in a context where we generically have
Pet
s, each pet'sfather
is only guaranteed to be aPet
. When we know we have aMule
, though, we know that its father is anEquine
.With these two features, tools which currently depend on schema conventions (like the Relay specs) could codify their requirements with GraphQL.
A Real-World Case
I have been working on a system which allows offline, concurrent modifications to versioned documents. The problem and solution are very generic, but the current state of the GraphQL type system doesn't allow me to describe these general-purpose shapes. Much like the Relay team had to define a convention for any type ending in
Connection
, I have an internal spec describing any type with a name ending inObject
,Entity
, andChange
. So, for example, I have aHikeObject
,HikeEntity
, andHikeChange
; I also have aClimbObject
,ClimbEntity
, andClimbChange
.I've found myself wishing I could use the GraphQL type system to describe these requirements, and because the current spec is so close, I find myself thinking of the schema in these terms.
This would make that possible, and bring GraphQL's type safety to tools that solve generic problems with GraphQL.
The text was updated successfully, but these errors were encountered: