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

Allow extending interface types #7757

Open
cmeeren opened this issue Nov 22, 2024 · 2 comments
Open

Allow extending interface types #7757

cmeeren opened this issue Nov 22, 2024 · 2 comments

Comments

@cmeeren
Copy link
Contributor

cmeeren commented Nov 22, 2024

Product

Hot Chocolate

Is your feature request related to a problem?

Related: #5974

Currently, HC allows extending all types implementing an interface, but not the interface itself.

Adding the ability to extend interfaces would greatly help domain-focused codebases where the domain types do not correspond exactly to the GraphQL types, and where one wants to keep the GraphQL layer as a separate layer on top of the domain layer. I do that in my codebases - I use explicit binding globally, and use type extensions to have full control over what goes in the GraphQL schema.

The solution you'd like

Allow extending interfaces just like we can now extend object types.

In #5974, it was mentioned that a type interceptor might be used, but I do not know how to accomplish this using a type interceptor. I just want to write a normal, friendly type extension class and have it apply to the interface itself.

@michaelstaib
Copy link
Member

I have not found a useful use-case for this. How should this work? Also from an internal execution standpoint. If its just about the GraphQL type representation, than this is already possible. You can create a InterfaceTypeExtension and add more fields to your type. But introducing a secondary interface that represents addition fields is just a dead type from which we would infer usage without any usage of the type itself by the type system.

@cmeeren
Copy link
Contributor Author

cmeeren commented Nov 25, 2024

Thanks for getting back to me!

I described my use-case in general terms in the OP. I will give specifics below, but I'm unclear how my initial description is insufficient, so there's a danger that the below is also insufficient. Please let me know if anything is unclear.


First, a more elaborate general description: In my view, the use-case is exactly the same as being able to extend object types. For object types, it is possible to add fields (resolvers) without modifying the original type. These fields are added to GraphQL and resolve as if they had been defined on the type itself. Similarly, I want to add extensions to interfaces in exactly the same way, using [Parent] on the argument that contains the interface/base type instance.


Here is a more specific example:

First, here's how and why I use object extensions: Consider a type Car. It has an owner, which is a Person. However, in the domain code, the Car does not have a Person property; it only has a PersonId. I ignore that property in GraphQL (I use explicit binding, but one could also use [GraphQLIgnore]), and I use a HC object extension to extend Car with a field called Owner that fetches the owner Person from DB using this ID.

This works great for GraphQL object types, and means I have full separation of my domain layer and my GraphQL layer.

Unfortunately, it does not work for GraphQL interfaces. Let's extend the example by adding a second type Bicycle, which also has an owner. The API clients need to get any vehicle with their owner, so for convenience, I want to add a GraphQL interface Vehicle with a field owner that returns a Person. I would like to be able to do the same here as with objects: Use a HC type extension to say that the Vehicle interface has a field owner that resolves in such and such a way. In my architecture, I don't want to make DB calls in domain code (all DB calls are done above the domain layer and the fetched objects are passed to the domain code as needed, keeping the domain code pure - sync, deterministic and free of side effects). This means that I can't simply have an IVehicle interface in code that has a property GetOwner returning Task<Person>, because Car and Bicycle can't implement it. Instead, I can either have an empty IVehicle interface, or a base type, or even a type that just wraps a Car or a Bicycle, and then in the GraphQL layer say that this is the interface, and it has an Owner field that resolves in such and such a way. This is, again, not possible because HC doesn't support extending interface types in the same way it (wonderfully) supports extending object types.

My current workaround is to have an empty interface IVehicle in domain code, have Car and Bicycle implement this, and define separate wrapping base and inheriting types in the GraphQL layer. I add [InterfaceType] to the base type, and define (as intrinsic methods, not extensions) all the fields I want this interface to have in GraphQL. This works well, but adds otherwise unnecessary type definitions and wrapping, and means I must remember to wrap all returned IVehicle instances in the correct GraphQL wrapper type in my resolvers.


Proposed solution: Add an [ExtendInterfaceType] attribute that works exactly like [ExtendObjectType] for objects: When used to extend an interface type (which is the only valid usage), the defined fields are added to the interface (and the implementing types, of course), and not just to the implementing types (which is the case today if using [ExtendObjectType] on the interface type).

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

No branches or pull requests

2 participants