Open ThisaruGuruge opened 4 months ago
@ThisaruGuruge Do we have a handwritten sample to see how the generated service code looks like with bal persist
?
@ThisaruGuruge Do we have a handwritten sample to see how the generated service code looks like with
bal persist
?
Updated with an example.
How about generating Movie object type like below. We can do the same for the 1:n relationship. Here we are fetching the relations from the movies resource and bal persist
handles the join query underneath.
type MovieWithDirector record {
string id;
datasource:Director director;
}
type MovieWithSoundTrack record {
string id;
datasource:SoundTrack soundTrack;
}
public isolated service class Movie {
private final readonly & datasource:Movie movie;
public isolated function init(datasource:Movie movie) {
self.movie = movie.cloneReadOnly();
}
// primary key can't be null
isolated resource function get id() returns @graphql:ID string => self.movie.id;
isolated resource function get title() returns string? => self.movie.title;
isolated resource function get director() returns datasource:Director|error? {
MovieWithDirector movieWithDirector = check datasource->/movies/[self.movie.id];
return new (movieWithDirector.director);
}
isolated resource function get soundTracks() returns SoundTrack|error? {
MovieWithSoundTrack movieWithSoundTrack = check datasource->/movies/[self.movie.id];
return new (movieWithSoundTrack.soundTrack);
}
How about generating Movie object type like below. We can do the same for the 1:n relationship. Here we are fetching the relations from the movies resource and
bal persist
handles the join query underneath.type MovieWithDirector record { string id; datasource:Director director; } type MovieWithSoundTrack record { string id; datasource:SoundTrack soundTrack; } public isolated service class Movie { private final readonly & datasource:Movie movie; public isolated function init(datasource:Movie movie) { self.movie = movie.cloneReadOnly(); } isolated resource function get id() returns @graphql:ID string? => self.movie.id; isolated resource function get title() returns string? => self.movie.title; isolated resource function get director() returns datasource:Director|error? { MovieWithDirector movieWithDirector = check datasource->/movies/[self.movie.id]; return new (movieWithDirector.director); } isolated resource function get soundTracks() returns SoundTrack|error? { MovieWithSoundTrack movieWithSoundTrack = check datasource->/movies/[self.movie.id]; return new (movieWithSoundTrack.soundTrack); }
+1. This seems more elegant. I will update the proposal.
Summary
Exposing a database as a GraphQL API is a common pattern in modern applications. This proposal aims to simplify this process using Ballerina's
bal persist
tool and thebal graphql
tool, enabling developers to quickly create GraphQL APIs that interact directly with databases without needing extensive database management knowledge.Goals
Non-Goals
bal persist
tool. As thebal persist
adds support for more databases, the GraphQL service generation will automatically support them.Motivation
Exposing databases as GraphQL APIs is becoming a common pattern in modern software development. There are some user queries about using Ballerina for this purpose. Existing solutions like Hasura and Prisma have addressed this need to some extent. The
bal persist
tool can enhance this capability within the Ballerina ecosystem by providing a streamlined method to expose a database as a GraphQL API, enriching the development experience.Description
The main task of this proposal is to generate a GraphQL service from a given data model. The existing
bal persist
tool will be utilized to generate the persist client and Ballerina data types first. Then the Ballerina GraphQL tool will use these types to generate the corresponding GraphQL types and the service code with minimal manual coding. This integration will handle various CRUD operations and ensure the GraphQL API remains consistent with the underlying database structure.Proposed Solution
This proposal suggests enhancements to the
bal graphql
tool to generate GraphQL services from given data models. The expected user workflow is as follows:Initialize the persist client:
Define the data model and execute the command to generate the persist client:
Generate the GraphQL service:
Implementation
The feature implementation will proceed through the following steps:
bal persist
tool APIs to retrieve the generated persist client and types as syntax trees.Query
andMutation
operations.ID
types with the@graphql:ID
annotation.remote
andresource
) methods forQuery
andMutation
operations, incorporating the generated persist client.Type Generation
We propose an opinionated approach for type generation:
OBJECT
types.INPUT_OBJECT
types.graphql:ID
annotation for key fields in the GraphQL schema.Schema Generation
To generate the GraphQL schema, the following steps will be followed:
Query
type with all theget
methods of the persist client.Mutation
type with all thepost
,update
, anddelete
methods of the persist client.OBJECT
types in the GraphQL schema.INPUT_OBJECT
types in the GraphQL schema.Future Work
bal persist
is now integrated intobal build
command, thebal graphql generate
should also be integrated into thebal build
command.Example
Data Model
Following data model will be used for an example.
GraphQL Service Generation
The GraphQL service will be generated using the generated persist client methods. Following is the generated code for the given data model.
GraphQL Output Object Types
For each entity type, a Ballerina service type will be generated. The
init
method of each generated service type will require the corresponding generated persist type as an input.Generating Service Types
A resource method will be generated for each non-related field of the entity to just return the value.
For related fields, the following conversion will be used:
Handling Relationships
To handle relationships, additional record types will be generated to retrieve related entities. This record type will include the corresponding relationship as a record so that the persist client will handle the join query internally.
GraphQL Input Object Types
Input objects are generated per each mutation operation. The generated input object types will be as follows:
The generated GraphQL schema will be the following:
Following will be the complete generated code:
Alternatives
An alternative would be to integrate GraphQL service generation directly into the
bal persist
command; similar to the following command.However, this approach could overly complicate the
bal persist
command and tightly couple persist client and GraphQL service generation. The proposed method keeps the commands focused and distinct in their functionality.Dependencies