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

How to reorder execution of sbt-graphql plugins? #84

Closed
gschambial opened this issue Apr 22, 2020 · 9 comments
Closed

How to reorder execution of sbt-graphql plugins? #84

gschambial opened this issue Apr 22, 2020 · 9 comments
Labels

Comments

@gschambial
Copy link

gschambial commented Apr 22, 2020

Hi Guys, I am trying to achieve following goals:

  1. Generate a .schema file from my Schema Definition
  2. Validate queries using .schema file generated in 1st step
  3. Generate code for the queries using .schema file generated in 1st step

I am able to achieve first 2 goals via following configuration in build.sbt:

// generate a schema file
graphqlSchemaSnippet := "root.SchemaDefinition.StarWarsSchema"
target in graphqlSchemaGen := new File("src/main/graphql/schema")

// Validating queries
sourceDirectory in (Test, graphqlValidateQueries) := new File("src/main/graphql/queries")

enablePlugins(GraphQLSchemaPlugin, GraphQLQueryPlugin)

and when i run sbt graphqlValidateQueries, You can see from logs that it first creates a schema file and then validates my queries against that schema file.

[info] Running graphql.SchemaGen /Users/gschambial/git/sangria-akka-http-example/src/main/graphql/schema/schema.graphql
[info] Generating schema in src/main/graphql/schema/schema.graphql
[info] Checking graphql files in src/main/graphql/queries
[info] Validate src/main/graphql/queries/human.graphql
[success] All 1 graphql files are valid
[success] Total time: 6 s, completed Apr 22, 2020 10:50:49 AM

Now to achieve goal no. 3, when I enable GraphQLCodegenPlugin I start getting errors:

// generate a schema file
graphqlSchemaSnippet := "root.SchemaDefinition.StarWarsSchema"
target in graphqlSchemaGen := new File("src/main/graphql/schema")

// Validating queries
sourceDirectory in (Test, graphqlValidateQueries) := new File("src/main/graphql/queries")

// telling Codegen plugin to use this schema file
graphqlCodegenSchema := new File("src/main/graphql/schema/schema.graphql")

enablePlugins(GraphQLSchemaPlugin, GraphQLQueryPlugin, GraphQLCodegenPlugin)

I am expecting the order of execution to be:

  1. Generate Schema
  2. Validate Queries
  3. Generate code for queries

But, no matter which command I trigger sbt graphqlSchemaGen sbt graphqlValidateQueries or sbt graphqlCodegen, following order is executed:

  1. Generate code for queries
  2. Generate schema
  3. Validate schema
[info] Set current project to sangria-akka-http-example (in build file:/Users/gschambial/git/sangria-akka-http-example/)
[info] Generate code for 1 queries
[info] Use schema src/main/graphql/schema/schema.graphql for query validation
[error] java.io.FileNotFoundException: src/main/graphql/schema/schema.graphql (No such file or directory)
[error] (Compile / graphqlCodegen) java.io.FileNotFoundException: src/main/graphql/schema/schema.graphql (No such file or directory)

As, schema is not generated before step 1 is executed it fails. Is there a way to reorder this execution? Apologies, if it is some noob sbt issue.

Any help would be highly appreciated.

@muuki88
Copy link
Owner

muuki88 commented Apr 22, 2020

Hi @gschambial

Thanks for the detailed question. You altered the example a bit so it doesn't work they way you expected.

Instead of depending on a task that generates the schema

graphqlCodegenSchema := graphqlRenderSchema.toTask("starwars").value

you directly access the file

graphqlCodegenSchema := new File("src/main/graphql/schema/schema.graphql")

So sbt has no way of knowing if this file should be generated or not. You probably need to change this to

graphqlCodegenSchema := graphqlRenderSchema.toTask("build").value

@muuki88 muuki88 closed this as completed Apr 22, 2020
@gschambial
Copy link
Author

gschambial commented Apr 22, 2020

hi @muuki88

Thanks for quick response.

I changed as per your suggestion. But, running any of sbt-graphql commands gets stuck at Rendering step and never proceeds.

I have added example code of my project here: https://github.com/gschambial/sangria-sbt-graphql-example/

I have following understanding:

graphqlCodegenSchema := graphqlRenderSchema.toTask("build").value will render schema from build schema definition right?

If yes, then how does the build definition gets loaded?

graphqlSchemaSnippet := "root.SchemaDefinition.StarWarsSchema"
target in graphqlSchemaGen := new File("src/main/graphql/schema")

Is above code enough to load build or do we need to graphqlSchemas to load schema definition?

@gschambial
Copy link
Author

If I am understanding correctly, using following snippet tells codegen plugin to use schema file rendered from build schema?

graphqlCodegenSchema := graphqlRenderSchema.toTask("build").value

if yes, then build schema should be present before this plugin executes and loading of build schema is done by GraphqlSchemaGen plugin right? But from logs it seems codegen plugin executes before schemagen that's why it is not able to find build schema during rendering and hangs.

@felixbr
Copy link
Collaborator

felixbr commented Apr 22, 2020

The reason why graphqlRenderSchema hangs is a circular task dependency.

We never encountered this because usually you have separate projects for client and server, so it doesn't come up.
Let me see if I can come up with a workaround and we'll have to find a proper fix (or at least better documentation around the issue) for the future.

As a side-note: When you generate the client code it also validates that your queries match the schema (i.e. your production-schema), so the 3 steps that you mentioned are probably just 2 (one on the server, one on the client).

@felixbr
Copy link
Collaborator

felixbr commented Apr 22, 2020

I refactored the build to have separate subprojects for server and client and that seems to solve your issues (PR here: gschambial/sangria-sbt-graphql-example#1)

@gschambial
Copy link
Author

gschambial commented Apr 22, 2020

Thanks @felixbr

schema and code generation would be part of server and client application respectively in my case as well and your PR solves the issue I am facing.

But, still wondering about use case where a server might need to expose models for it's queries to be used by some other backend service then both schema and code generation might be needed in same app.

@felixbr
Copy link
Collaborator

felixbr commented Apr 23, 2020

I don't see why a server should expose its models (as in Scala types). With the plugin you generate whatever models you need at the client, so all you need is the schema (which can even be pulled automatically from the production-server you want to connect to).

From our own experience with both thrift and graphql I can assure you that sharing Scala types is currently much worse than generating the needed code at the client-side. In part because Scala types aren't binary compatible across versions.

There is one use-case where you have predefined queries on the server-side and the client only gets to decide which query should be used but not how the query looks like. This can be done to lock down APIs but you also lose a lot of advantages of GraphQL. Usually you define the query on the client-side and the server doesn't need to know about them (it only knows the schema and how to resolve its fields/types).

edit: If you have something that both server and client need, then you usually add another subproject in sbt called "shared" and depend on that. In my current solution the client depends on the server (client -> server) but often you have something that should be shared between them (server -> shared; client -> shared).
This doesn't have much to do with graphql, you'd do the same for any self-contained client/server application (e.g. play as server + scala.js as client)

@gschambial
Copy link
Author

Agree with you on both points. Things were under discussion and we were evaluating options and as you rightly said generating code on client side using schema from server removes tight coupling and also fits well with GraphQL philosophy.

This discussion has cleared a lot of doubts I had and I guess I am in a better position now to invest on the right approach.

@felixbr
Copy link
Collaborator

felixbr commented Apr 23, 2020

I'd like to add that we generate Scala code from the Schema (using this plugin) as well as TypeScript code (using Apollo codegen) as we do both server-side and client-side (= SPA) rendering respectively for different use-cases.

The schema is the source of truth and the client-language doesn't matter but in the case of Scala/TypeScript you get end-to-end type-safety because the generated code always matches the query and the query always matches the production-schema.

edit: Here's the slides to a talk I gave last year about our setup, if you're interested: https://slides.com/felixbr/one-and-a-half-years-of-graphql-in-production#/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants