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

[Proposal] Interface implementation violate "Don't Repeat Yourself" #500

Closed
yaquawa opened this issue Aug 15, 2018 · 8 comments
Closed

[Proposal] Interface implementation violate "Don't Repeat Yourself" #500

yaquawa opened this issue Aug 15, 2018 · 8 comments
Labels
🗑 Rejected (RFC X) RFC Stage X (See CONTRIBUTING.md)

Comments

@yaquawa
Copy link

yaquawa commented Aug 15, 2018

Problem

I found it's very inefficient to implement interface in GraphQL.

interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

type Human implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  starships: [Starship]
  totalCredits: Int
}

In GraphQL, the word implements means "copy all the fields from interface to the target type".
The more types you get, you have to repeat your self more and more... If you add or delete a field in the interface, then you have to delete all the types that implement the interface, which is repeat yourself again.

In GraphQL it's all about "copy and paste", no real implementation there. So why should we repeat ourself like this way?

Proposal

Introducing a simple extends operator just like most of the program languages would be an excellent solution.

type Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

type Human extends Character {
  starships: [Starship]
  totalCredits: Int
}

For consistency,Interface also can be extended using the extends operator.

interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

interface Human extends Character {
  starships: [Starship]
  totalCredits: Int
}

This approach won't introduce any breaking changes, and it's natural to have such an operator because it's not something new concept and fit with GraphQL very well too.

@IvanGoncharov
Copy link
Member

@yaquawa It's a duplication of #335.
If you think it differs in any significant way please describe this difference and I will reopen this issue.

@yaquawa
Copy link
Author

yaquawa commented Aug 16, 2018

@IvanGoncharov
Hi, thanks for the link.

#335 still need to COPY the implementations into the concrete type.
What I want to emphasize is that there is no Implementation in GraphQL, both interface and type is just about typing something, so we don't need to implement a type, what we actually do for the implementation of a type is just COPY&PASTE works. (Eventually both type and interface is Interface)

@IvanGoncharov IvanGoncharov reopened this Aug 16, 2018
@IvanGoncharov
Copy link
Member

@yaquawa There are many reasons why we need interfaces in GraphQL type system.
But most importantly it's already part of the GraphQL spec (including SDL syntax) and we can't do any breaking changes.

Can you please rewrite your proposal so it doesn't introduce breaking changes to GraphQL?

@yaquawa
Copy link
Author

yaquawa commented Aug 16, 2018

@IvanGoncharov
Thank you for reopening this thread 🤝I've rewritten my proposal.
I think what we need is just a simple extends operator. Solve the problem once and for all.

@yaquawa yaquawa changed the title [Discussion] Interface implementation violate "Don't Repeat Yourself" [proposal] Interface implementation violate "Don't Repeat Yourself" Aug 16, 2018
@yaquawa yaquawa changed the title [proposal] Interface implementation violate "Don't Repeat Yourself" [Proposal] Interface implementation violate "Don't Repeat Yourself" Aug 16, 2018
@leebyron
Copy link
Collaborator

leebyron commented Oct 2, 2018

I agree that the current way requires more writing (sometimes what appears as copy & paste), however this is an intentional tradeoff because the type system definition language favors reading over writing.

I think we've discussed extends in the past and turned it down because it made reading the schema more challenging. The current way a type is written means that all fields on a type can be seen in a single place. Introducing extends would require jumping between definitions to understand what fields are available which would make reading a schema more difficult. I view that as net-lower-value.

Also, types which implement interfaces may be subtypes in which case it certainly is not copy and paste. Consider this contrived but valid example:

interface Animal {
  parent: Animal
}

type Dog implements Animal {
  parent: Dog
}

@joffrey-bion
Copy link

joffrey-bion commented Aug 12, 2020

@leebyron I know I'm very very late to the party, so forgive me for commenting here. I agree with optimizing for reading and not writing, but I disagree that the current state is doing this, so I'd like to understand the decision better.

Even on the reading side, avoiding this repetition would be an improvement IMO:

  • there is no indication currently that a field is inherited from the interface (no override keyword), and this is a problem because readers who are already aware of the interface fields don't know what to "skip" when reading other types, so they waste time
  • the duplication makes the files pretty long, which urges people to skim instead of read the schema. and it becomes a problem because of the first point
  • the documentation might differ between an interface field and an implementing type, because of carelessness in the sync of the doc (because the doc has to be repeated if we want to "read all fields in one place"), or because we do want to "override" the doc for one field, but there is really no way to know when you read.

While the schema can be read directly by some people as a text file, I imagine (from my limited experience) that consumers usually either have tooling to explore the graphql schema (like a playground) or are in an IDE where they can browse to find parent types. In both these cases, there is no need to have the inherited properties explicitly listed because the tooling can show them anyway. So as a reader you could have the "all fields visible in one place" experience without impacting the writers so much.

On the writing side, we're talking about a major maintenance hassle. Having to repeat 30 properties in 10 different types is not ideal. The fields need to be kept in sync manually as the project is maintained, as well as their documentation. And there is no compile-time nor runtime check about the doc sync (because we also might want to "override" the doc of a particular field in a particular type, this shouldn't be considered an error).

Being able to narrow the type down is a separate thing I believe (usually achieved via override, which is already allowed here), so if we need to narrow something down, we can always explicitly specify the overridden field, but it doesn't mean we should force people to redeclare all other inherited fields as well.

The extends approach has been used in many programming languages, and I don't think I have ever heard of it being a problem. Is there more documentation I can find about why the extends approach has been rejected? Is it not possible to consider this change anymore because of backwards compatibility issues?

@joffrey-bion
Copy link

@leebyron I just noticed that #533 actually presents most of my arguments as well, so feel free to ignore or continue the discussion there.

@novikov1337danil
Copy link

I don't understand why this is still not implemented

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🗑 Rejected (RFC X) RFC Stage X (See CONTRIBUTING.md)
Projects
None yet
Development

No branches or pull requests

5 participants