Today, it's possible to annotate a field with @goModel(forceResolver: true) which will generate a resolver for that field. The issue I have is that I often need to pass a good amount of context between the "parent" resolver for the top level type the "child" resolver for the field. Sometimes this context also isn't serializable, and I don't want to expose it into the schema.
Creating child resolvers for specific fields is valuable because sometimes these child fields are very expensive and we want to decouple them from being fetched together, either to support @defer, or simply in case that field isn't actually being requested.
What did you expect?
I would like to propose a mechanism where we can force an inline resolver — instead of creating a new top level entity resolver, instead a closure is generated on the parent model which the server can then call. The closure enables us to pass arbitrary context between the parent and child resolvers.
Example
GraphQL schema:
type Query {
listing(id: ID): Listing
}
type Listing {
id: ID!
name: String
address: String
# "inlineResolver" instead of "forceResolver"
price: Float! @goField(inlineResolver: true)
}
Generated Go model:
type Listing struct {
ID string `json:"id"`
Name *string `json:"name,omitempty"`
Address *string `json:"address,omitempty"`
Price func (context.Context) (float64, error)
}
Population in resolver:
type queryResolver struct{ *Resolver }
func (r *queryResolver) Listing(
ctx context.Context,
id *string,
) (*model.Listing, error) {
listing, err := r.listingController.GetListing(ctx, id)
if err != nil {
return nil, err
}
return &model.Listing{
ID: listing.ID,
Name: listing.Name,
Address: listing.Address,
Price: func(ctx context.Context) (float64, error) {
return r.listingService.GetPrice(
ctx,
listing.ID,
// accesses internal field we don't want to expose through the schema
listing.InternalPriceModifier,
), nil
},
}, nil
}
Once the struct value is returned by the resolver, the server is responsible for executing this inline function, similar to how it would do for a root resolver (only if the Field is actually requested by the consumer).
Alternative
We could also do something like storing properties in OperationContext, but this isn't type safe, can only support serializable data, and also is a lot of manual / error prone work with implicit contracts.
What happened?
Today, it's possible to annotate a field with
@goModel(forceResolver: true)
which will generate a resolver for that field. The issue I have is that I often need to pass a good amount of context between the "parent" resolver for the top level type the "child" resolver for the field. Sometimes this context also isn't serializable, and I don't want to expose it into the schema.Creating child resolvers for specific fields is valuable because sometimes these child fields are very expensive and we want to decouple them from being fetched together, either to support
@defer
, or simply in case that field isn't actually being requested.What did you expect?
I would like to propose a mechanism where we can force an inline resolver — instead of creating a new top level entity resolver, instead a closure is generated on the parent model which the server can then call. The closure enables us to pass arbitrary context between the parent and child resolvers.
Example
GraphQL schema:
Generated Go model:
Population in resolver:
Once the struct value is returned by the resolver, the server is responsible for executing this inline function, similar to how it would do for a root resolver (only if the Field is actually requested by the consumer).
Alternative
We could also do something like storing properties in OperationContext, but this isn't type safe, can only support serializable data, and also is a lot of manual / error prone work with implicit contracts.
Related to #2864.