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: Provide a Way to Access the GraphQL Field Information from the Resolver Functions #3893

Closed
ThisaruGuruge opened this issue Jan 3, 2023 · 4 comments · Fixed by ballerina-platform/module-ballerina-graphql#1182
Assignees
Labels
module/graphql Issues related to Ballerina GraphQL module Points/5 Status/Implemented Implemented proposals Team/PCM Protocol connector packages related issues Type/Proposal
Milestone

Comments

@ThisaruGuruge
Copy link
Member

ThisaruGuruge commented Jan 3, 2023

Summary

When a GraphQL field is processed, it is helpful to have additional information about the field such as the child fields. This proposal is to provide a way for the user to access the additional information using the graphql:Field object.

Goals

  • Provide a way to access field information within the resolver functions.

Non-Goals

  • Change the state of the field.

Motivation

In GraphQL, the client requests the field they require, and the server is guaranteed to return only the fields the client requested. In most cases, this functionality is used to optimize the backend database queries so that the database is accessed to retrieve the values requested by the client. In Ballerina, currently, this optimization is not possible because the developer does not have access to this information. This proposal is to provide a way for the developer to access this information so that they can optimize their database queries and execution plans.

Description

The graphql:Field Object

Currently, the Ballerina GraphQL package has a graphql:Field object, which exposes the field name and the alias (if there's any). The graphql:Field object has the following APIs:

  • getName()
    This API returns the name of the field as defined in the GraphQL document.
  • getAlias()
    This API returns the effective alias of the field. This means if the document has an alias defined for the field, this will return the alias. Otherwise, it will return the field name.

In addition to these existing APIs, the following APIs are proposed to introduce.

  • getSubfieldNames()
    This API returns string[] that contains the names of the child fields of the current field. This will include the subfields of the field if the field is a GraphQL OBJECT type. Otherwise, it will return an empty array. The resulting array includes the subfields including the fragment fields if there are any. Following is the definition of the method:
    isolated function getSubfieldNames() returns string[];
  • getPath()
    This API returns the current path of the field from the root operation. If the field is a part of an array, the array index is also included. Following is the definition of the method:
    isolated function getPath() returns (int|string)[];
  • getType()
    This API returns a graphql:__Type record that contains the type information of the field. Following is the definition of the method:
    isolated function getType() returns __Type;

    Note: This will expose the currently package private record types to public that are being used to store schema information, such as __Type, __Field, __TypeKind, etc. These types should be changed to intersection types with readonly & record, so the users cannot mutate the state of the schema.

Note: The graphql:__Field record should not be confused with the graphql:Field object. The graphql:__Field represents a field in the GraphQL schema whereas the graphql:Field object represents a field in a GraphQL document.

Accessing the Field Object

Accessing the graphql:Field is currently supported in interceptors, using the execute method. But for most use cases, the Field object should be accessed, even without an interceptor. Therefore, this proposal proposes to add the graphql:Field object as an input parameter for the resource or remote methods that represents a GraphQL field. This is similar to how the context is accessed.

With this proposal, we are removing the limitations for the graphql:Context and graphql:Field parameter definition rules in the resource and remote methods inside the Ballerina GraphQL services. This means, we no longer validate the parameter position of the graphql:Context or the graphql:Field object in a resource or a remote method. They can be defined anywhere in the parameter list. But it is recommended to use them as the first and the second parameter in a function for better readability in the code.

Example: Accessing the Field Object
import ballerina/graphql;

service on new graphql:Listener(9090) {
    // A resource method that accesses the `graphql:Context` and the `graphql:Field` 
    // objects, along with another input parameter.
    resource function get greetingWithContext(graphql:Context context, graphql:Field 'field, string name)
    returns string {
        // ...
    }

    // A resource method that accesses the `graphql:Field` object,
    // along with another input parameter.
    resource function get greetingWihoutContext(graphql:Field 'field, string name) returns string {
        // ...
    }
}
Counter Example: Field Defined in Invalid Locations
import ballerina/graphql;

service on new graphql:Listener(9090) {
    // If the `graphql:Context` is present, it must be the first parameter. 
    // This will result is a compilation error.
    resource function get greetingWithContext(graphql:Field 'field, graphql:Context context, string name) 
    returns string {
        // ...
    }

    // If the `graphql:Context` is not present and the `graphql:Field` is present, 
    // the `graphql:Field` must be the first parameter. This will result in a compilation error.
    resource function get greetingWihoutContext(string name, graphql:Field 'field) returns string {
        // ...
    }
}

Alternatives

Instead of adding the graphql:FIeld as an input parameter in a resource or a remote method, an alternative approach would be to add the graphql:Field to the graphql:Context object, and introduce an API, getField() in the graphql:Context object. This approach was considered first, but it has a couple of drawbacks.

  • This might affect the parallel execution of the fields since the context has to be updated each time a field is executed. (This is not an issue as per now, as we are executing the fields serially now since the execution logic migration to Ballerina)
  • The DX is a little bit more complex compared with the proposed approach as the users has to define the graphql:Context object whenever they need to access the graphql:Field, even though the graphql:Context might not needed.
  • Tightly-coupling the graphql:Context and the graphql:Field does not seem a good idea as they are designated for two different things:
    • graphql:Context is to transfer/access meta-information per-request.
    • graphql:Field is to access meta-information per-field.

Due to the above reasons, this proposal is going forward with the approach mentioned in the above Accessing the Field Object section.

@ThisaruGuruge ThisaruGuruge added Type/Proposal module/graphql Issues related to Ballerina GraphQL module Team/PCM Protocol connector packages related issues Status/Draft In circulation by the author for initial review and consensus-building labels Jan 3, 2023
@ThisaruGuruge ThisaruGuruge changed the title Proposal: GraphQL Field Object Proposal: Provide a Way to Access the GraphQL Field Information from the Resolver Functions Jan 3, 2023
@shafreenAnfar
Copy link
Contributor

shafreenAnfar commented Jan 9, 2023

A very good proposal!

I am not sure about the order of arguments though. It is a guide line but not a hard and fast rule right.

@shafreenAnfar
Copy link
Contributor

Just to understand why graphql:Field is an object but not a record?

@ThisaruGuruge
Copy link
Member Author

ThisaruGuruge commented Jan 9, 2023

I am not sure about the order of arguments though. It is a guide line but not a hard and fast rule right.

So you mean, we remove the rule altogether or make it possible to use the graphql:Context and graphql:Field interchangeably as the first and the second parameter? (I am +1 with making possible to use either graphql:Context and the graphql:Field as the first and the second parameter, but not to allow them to use after the regular parameters.

Just to understand why graphql:Field is an object but not a record?

This is an already existing object. It is used to pass context.resolve() method when using the interceptors. To use this with context.resolve() method. we need some internal information such as the original nodes, current path, etc. stored in this object, without exposing them. That was the reason for creating an object.

@ThisaruGuruge ThisaruGuruge self-assigned this Jan 10, 2023
@ThisaruGuruge ThisaruGuruge moved this from BackLog to In Progress in Ballerina Team Main Board Jan 10, 2023
@ThisaruGuruge ThisaruGuruge added Status/Active Proposals that are under review and removed Status/Draft In circulation by the author for initial review and consensus-building labels Jan 10, 2023
@ThisaruGuruge
Copy link
Member Author

For the moment, we decided to go ahead with the graphql:Field object, instead of a record due to the following reasons:

  • Current implementation already has defined the graphql:Field object, which is used for interceptors. Changing this will be a breaking change. We do not intend to do a breaking change as of now.
  • Currently, we haven't concluded the information we are going to expose using this object, as this is just the first step. In the future, once this information is finalized and the feature is complete, we can revisit this and use a record type as a union with the graphql:Field object, and deprecate the object later.

Therefore, we are going ahead with this proposal. Hence, marking the proposal as accepted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
module/graphql Issues related to Ballerina GraphQL module Points/5 Status/Implemented Implemented proposals Team/PCM Protocol connector packages related issues Type/Proposal
Projects
Archived in project
Status: Standard Lib - PCM
2 participants