ballerina-library
ballerina-library copied to clipboard
[Proposal] Decoupling GraphQL API development from API design
Summary
When developing APIs there are two standard practises: code first and contract first. In the case of contract first you first design the GraphQL schema and then implement the agreed schema. This proposal is to improve Ballerina for contract first approach.
Goals
- Improve contract first approach experience in Ballerina
Motivation
As mentioned in the summary API development can be broken into two main practises: code first and contract first. In the case of contract first, the schema design is done first and then the implementation is done second. This allows the schema to have its own evolution life-cycle and code to have its own. During schema design domain expert may play a significant role whereas during implementation technology expert may play a significant role. The time taken to design a particular schema and the time taken to implement a designed schema could also vary. Therefore, it makes sense to provide a way to independently evolve each of these.
Description
At the moment, Ballerina has a visualiser which allows us to design the schema. However the problem with the current implementation is we generate a service skeleton. This binds the design and development together.
Instead of generating the service skeleton it is possible to generate service types. Following is an sample code.
import ballerina/graphql;
type snowtooth service object {
*graphql:Service;
resource function get allLifts(Status? status) returns Lift[];
};
public type Lift service object {
*graphql:NestetedService;
isolated resource function get id () returns string;
isolated resource function get name () returns string;
isolated resource function get status () returns string;
isolated resource function get capacity () returns int;
isolated resource function get night () returns boolean;
isolated resource function get elevationgain () returns int;
isolated resource function get trailAccess () returns Trail[];
};
public type Trail service object {
*graphql:NestetedService;
isolated resource function get id ();
isolated resource function get name () returns string;
isolated resource function get status () returns string;
isolated resource function get difficulty () returns string?;
isolated resource function get groomed () returns boolean;
isolated resource function get trees () returns boolean;
isolated resource function get night () returns boolean;
isolated resource function get accessByLifts () returns Lift[];
};
public enum Status {
OPEN,
CLOSED,
HOLD
}
This part of the code can be in a separate Ballerina package and can be released independently. Then the implementation code can add the dependency to the release package and continue implementing the schema.
service foo:snowtooth /graphql on new graphql:Listener(9090) {
}
In addition, the above code is enough to generate the GraphQL schema from the code. Effectively, this becomes an alternate syntax to define GraphQL schema.
However, the above to work, it is needed to introduce a new service type as graphql:NestetedService
. Ballerina GraphQL module by default map service object types to GraphQL interfaces. Therefore, to distinguish GraphQL interfaces from GraphQL nested services, the proposed service type is needed.
Please note that, it is not going to be a breaking change.
Dependencies
- Tooling needs to be improved to provide code suggestion as needed when the service type is explicitly mentioned
- Update the GraphQL CLI tool
- Update the VSCode visualiser
@hevayo @ThisaruGuruge @sachiniSam @sameerajayasoma
We have discussed doing this same thing for HTTP services as well.
I am +1 for this.
One concern is the name NestedService
, which I think does not convey the proper message, we can come up with an alternative. Maybe graphql:Object
? The idea is that when the user sees the code, it is clearly visible that it is a GraphQL object. This might not be aligned with the Ballerina terminology, but graphql:Object
reads perfectly in this scenario.