-
Notifications
You must be signed in to change notification settings - Fork 423
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
Splitting Root Schema to multiple Schemas #646
Comments
For me it is not clear what "merge" means in this context. I would assume that merge means that two queries Q1 and Q2 should be merged path wise? For example, if the path However, maybe you could provide a small example and describe your desired behavior. |
@jmpunkt sorry that it is not clear. Let me take https://typegraphql.com/ example.
I can write similar Now, in juniper case.
This is the one and only Query impl I can have. I was looking something like having
Sorry for little mix of Rust and TypeScript. I m new to Rust and TypeScript is day job language. But I guess you get the point. If I have all the queries in one big fact |
So to be clear, you define two queries, then all fields in the these two queries should be in the RootQuery. So we define the queries. struct UserQuery;
#[juniper::graphql_object]
impl UserQuery {
fn user(&self, user: UserId) -> User { todo!() }
}
struct ProductQuery;
#[juniper::graphql_object]
impl ProductQuery{
fn product(&self, id: ProductId) -> Product { todo!() }
} Then after the "merge", the RootQuery should be the following. #[juniper::graphql_object]
impl RootQuery {
fn user(&self, user: UserId) -> User { todo!() }
fn product(&self, id: ProductId) -> Product { todo!() }
} Sadly there is no way to tell Juniper to merge these objects. Implementing such behavior in Juniper should be possible. For this example, the easiest workaround with Juniper would be pub struct RootQuery;
#[juniper::graphql_object]
impl RootQuery {
fn users(&self -> UserQuery { UserQuery }
fn products(&self) -> ProductQuery { ProductQuery }
} or with a different syntax but the same object #[derive(juniper::GraphQLObject)]
pub struct RootQuery {
users: UserQuery,
products: ProductQuery,
}
impl RootQuery {
pub fn new() -> Self {
Self {
users: UserQuery,
products: ProductQuery,
}
}
} Your GraphQL query requires an additional path segment ( |
@jmpunkt thanks for explaining. I guess I did get my answer. I was looking for similar thing. I don't mind path segment until things stays clear. You can close this issue. Please do the honors. :) |
We had previously discussed something like serde's flatten...would that do what you want? |
@LegNeato I guess you are asking @jmpunkt . Sorry to pitch in, but you are right. Something similar to PS: It might need warning or error message if there is duplicate schema definition. Like |
@kunjee17 @LegNeato @jmpunkt I think the new design described in #553 will solve this issue too without providing any flattening capabilities. Simply, it will allow to specify multiple |
@tyranron that would be great to have. BTW I did tried to have different Dumb question. That is incoming feature right. Or I just missed something already there in Juniper ? |
@kunjee17 it will be incoming feature if community and maintainers will decide so. At the moment it's only a possible incoming feature 🙃 Current |
@tyranron thanks for reply. It would be nice to have such feature. Let's see what community and maintainers decide. :) |
@kunjee17 i think, yes. enum UnitedApiQueryRoot {
Api1(Api1QueryRoot),
Api2(Api2QueryRoot),
} But at the moment, |
@tyranron neat. I should give it a try to this as well. Thanks for info. |
This is also related to #182 My dream is to make a macro that lets you merge an arbitrary number of schemas or gaphql objects like this merge!(user, post, friends); For inspiration, in JavaScript you can merge type definitions, as well as resolvers and schemas. I'm not sure how to iterate over a graphql_object's internals to merge them but I feel that looking at the derive object macro code would be a good starting point because that's where the GraphQLTrait implementation happens. I feel like this would be great for apps with big schemas. It's also worth mentioning this design should be kept strictly compile-time and not be used for distributing graphql schemas and merging them at runtime, which has it's own pitfalls. |
@vladinator1000 As in one project which is type script based we are indeed merging schema but in another project where I am trying Rust. We are not. And frankly I am better of not merging it. It will more readable queries query {
findPersonById(id : String) {
name
}
} against query {
person {
findById(id : string) {
name
}
}
} Again it is question of choice but I like the the second option, where I don't need to merge. |
@kunjee17 yeah that seems totally reasonable, but dependent on how you design your schema. It's also worth noting the graphql spec has an |
Rust supports multiple impl blocks, I wonder if we could use this to our advantage? |
@vladinator1000 I did tried that but as of now current version, juniper don't support that. Also, with my limited knowledge of macros I don't know how it will pick it up. Again, I will say I am quite happy with solution provided here. Even C# graphql advise to go with separated schemas.
instead of
I still get that feature might be useful but it will surely not the road block for using Juniper. There are may couple issues blocked though which you might have to look if you want to use async version of it. |
Just to add one observation to this conversation... I've also been needing to split my schema across multiple domains and this approach has worked well for both queries and mutations:
and then I can implement settings and users in their respective modules. However this doesn't appear to work the same for subscriptions. As far as I can tell subscriptions have to be implemented directly in the root. Trying the same pattern gives:
|
This proposal is very interesting imo. Being able to use default resolvers + impl is nice qol and allowing multiple impl of QueryRoot/MutationRoot is a MUST imo. |
I think this should be worthy goal. It would allow DDD type of splitting of concerns. In practice this could mean like having crates that handle different features. E.g.
Then in your It would be easy then to just add |
Async-graphql was able to achieve this, albeit its not as simple as having multiple impl blocks https://async-graphql.github.io/async-graphql/en/merging_objects.html, but Ive been using it anyway. @tyranron has there been advancements with that new design? |
@Ericnr no, I haven't worked on it recently. |
I also created a issue Best way to organize root resolvers, why Juniper force us to put all root resolves in a single file? for me, this idea is totally insane. even 1 root resolver got 100 lines of code in my case, I can see I will have serval hundreds very soon, it is a nightmare to maintain a file like this. |
@kunjee17 did you find an alternative please? it seems the comunity is not serious about this, which frustrates me a lots. |
@videni currently I'm going with multiple resolvers . Combined in single resolver in root query. As mentioned earlier. Same way dotnet guys are doing it. If you like I ll share example of same. |
@kunjee17 yes, please, sorry for the late response. |
Hi @videni Here is how current code likes for me. impl Query {
fn api(&self) -> ApiQuery {
ApiQuery
}
fn database(&self) -> DatabaseQuery {
DatabaseQuery
}
fn auth_query(&self) -> AuthQuery {
AuthQuery
}
} While ApiQuery looks like this impl ApiQuery {
fn api_version() -> &'static str {
"1.0"
}
} I have skipped the macros for readability purpose. Same goes for mutation and all. This allows separation of modules even at graphQL level. I did have worked with stitching schema approach while working with node js. That is also an option. I feel it is just question of choice. In above approach you have to query query user {
getById {
username
}
}
and if you are stitching the schema it would be like below query getUserById {
username
}
I hope I m clear in explaining. Let me know if something is still not clear. |
The only problem with using this work around is that the graphql response looks like this: #[juniper::graphql_object]
impl RootQuery {
fn api(&self) -> ApiQuery {
ApiQuery
}
}
#[juniper::graphql_object]
impl ApiQuery {
fn version() -> &'static str {
"1.0"
}
}
{
"data": {
"api": {
"version": "1.0"
}
}
} And that implies that client side code generation also accounts on that // Example in js
const response = await makeGeneratedGraphqlResponse();
let usefulResponse = response.data.api.version; With this example of I think all this client-side effect is too much effort to just not allow queries and mutations across multiple files instead a single QueryRoot struct in a single file. @tyranron, juniper has any plans to change this? |
@arthurfiorette it's kinda untrivial to change this preserving ergonomics. So, definitely not in the near future, but yes in longer prespective. |
I managed to put together a working solution that relies on undocumented fields and a minuscule runtime overhead, with an attribute macro named #[derive(Default)]
struct UserQueries;
#[composable_object]
#[graphql_object(Context = Context)]
impl UserQueries {
// ...
}
#[derive(Default)]
struct OrganizationQueries;
#[composable_object]
#[graphql_object(Context = Context)]
impl OrganizationQueries {
// ...
}
composite_object!(Query<Context=Context>(
UserQueries,
OrganizationQueries
)); If anyone is interested let me know, I'll publish it as a crate. I would like to point out that this feature is very important imo. For me it was the biggest downside when weighing this excellent crate against class UserController {
@FieldResolver()
username(): String {
// ...
}
@Query()
users(): User[] {
// ...
}
@Mutation()
createUser(): User {
// ...
}
} Obviously it is harder to do in Rust because it would require some sort of a global registry which doesn't blend well with macro generated code, but at least first class support for merging unit-like objects would be nice. |
@kunjee17 we're aware of this issue and have plans to resolve it, but in a sligtly different manner. It's not quite critical, so is not in our priority list at the moment. Not in 0.16 release, certainly, but in 0.17, probably, yes. |
@tyranron thanks for reply. Looking forward to it. |
Hey @nikis05! Did you publish the crate? I'm really interested :D |
@tyranron It's been more then 2 years any updates on this? |
First of all Sorry, as I m new to Rust language and framework so I might be asking totally dumb question.
I am already working in GraphQL project having 100s of queries and mutations. It is TypeScript / JavaScript based project. So, there we can easily merge all things in single big fat schema as root node.
I tried similar thing with Juniper but was unable to do it. I thought multiple impl of Query will work as it is, but it didn't. Don't know if I m using rust wrong way or juniper is doing some magic.
If only one Query or Mutation is allowed it would be difficult to accommodate too many queries and mutations. I have gone through documentations and examples couple of times but couldn't find any solutions.
It would be great if someone can point me to right direction for the same. Do let me know if any details are unclear or missing.
The text was updated successfully, but these errors were encountered: