ballerina-platform / ballerina-library

The Ballerina Library
https://ballerina.io/learn/api-docs/ballerina/
Apache License 2.0
138 stars 56 forks source link

[Proposal] Decoupling GraphQL API development from API design #4620

Open shafreenAnfar opened 1 year ago

shafreenAnfar commented 1 year ago

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

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.

Screenshot 2023-06-27 at 11 36 36

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

shafreenAnfar commented 1 year ago

@hevayo @ThisaruGuruge @sachiniSam @sameerajayasoma

sanjiva commented 1 year ago

We have discussed doing this same thing for HTTP services as well.

ThisaruGuruge commented 1 year ago

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.