Hey folks, in a multi-module repo, instead of mult...
# apollo-kotlin
g
Hey folks, in a multi-module repo, instead of multi-module setup, is anyone following schema per module setup? How has your experience been? I know this approach will have its own challenges when it comes to mapping of shared types but how bad would that be?
s
Sounds like too much effort for no benefit. Will you also have a separate ApolloClient instance per module? Unless you mean that you have multiple backends and each module targets a different one?
1
g
No, single client and single backend for a federated schema. There are benefits of course. The modules can update the schema and move forward at their own pace. When you have a single schema in the repo, if you update the schema you bring in changes from other teams which may fail the build, unless you fix them. Or you need to update the schema manually, which is not ideal.
s
Do you typically get backwards incompatible schema changes? In which scenarios does a schema version change break the build exactly? Also if it's one backend, not getting a build error will be a false sense of security, since in production you would be hitting the "wrong" schema actually anyway, if anything giving you more room for runtime errors rather than compile time ones.
g
It is mainly problem of scale. To illustrate, consider you have 50-60 teams/subgraphs. Team X has a feature that is not in production yet, behind a feature flag. They decide to remove a field from a type, that is still referenced in an Android/iOS repo in one of the
.graphql
files. 1. This is a safe change for Team X’s operations. The operation is not in production yet so they know the change will not break anything. 2. They don’t wanna go through deprecation path for a type that’s not released, they want to keep the schema clean. 3. This is a safe change for Team X, but this breaks the build in the repository for everyone else. The wise thing to do here would be: a) Deprecation path. b) Clean client usage first, and then remove from schema if you must. We are working towards regulating a) and b) at scale, but also exploring other options from the client side.
m
Can this be solved organizationally? Team X's working on a new feature: • Backend creates a new graph variant (possibly using contracts) with new experimental fields. • Frontend creates a new GitHub branch to develop the feature against the Graph variant. • If everything looks good: ◦ The experimental fields are promoted to production ◦ Frontend switches to the production endpoint and merges the PR
Or is the concern that this would create somewhat long lived branches?
g
The orchestration of all that would be very complex in my opinion. Instead, we are looking into adding schema checks that validates the schema against the source code by using persisted query manifest files. This probably will not cover all the cases, such as removing a value from Enum but this seems to be the best approach. This is what we are experimenting with on the backend side. I am also exploring different options from client side to see if we can improve on it. 2 options I'm thinking are: 1. Superchema per module, as described above. 2. Subgraph schema per module. Vertical ownership. Ideally under the same namespace across the repo. This will be complex as well. Plus, idk how Apollo plugin will work with this.
m
we are looking into adding schema checks that validates the schema against the source code by using persisted query manifest files
I think that makes a lot of sense 👍 . In a way, having codegen use some fields is a kind of field usage, even if no query is made. Removing the field doesn't break the (runtime) end user but breaks the (build time) frontend dev user.
This probably will not cover all the cases, such as removing a value from Enum
This is a larger problem, right? It's impossible to tell if this breaks a runtime or build time use case without looking at the written Kotlin code. Therefore removing an enum value should always be considered breaking both at runtime and build time I think?
💯 1
Subgraph schema per module.
Subgraph schema per module.
I vaguely remember discussing this but this would more or less require to run federation on the client, right? At least the compose step. No need for query planning or runtime on the client but we'd need to be able to compose all those subgraphs
g
This is a larger problem, right? It’s impossible to tell if this breaks a runtime or build time use case without looking at the written Kotlin code.
Yes. One option is to build the repos, but that will add overhead to development. Or we will need to write a static analysis tool. In my opinion the biggest gap is the awareness. Backend devs are not aware that their changes can break the build. Once the schema check shows green they assume it is a safe thing to do. We are also working on providing some internal guidelines and training so they understand the impact, and we show those changes as warnings.
I vaguely remember discussing this but this would more or less require to run federation on the client, right?
Yes, this will be the complex part. This is how I think it would work: • 1 subgraph for shared types. Used in a root level module Every module consumes this. • X number of supgraphs per domain. A Gradle module per subgraph Before codegen, we stitch them all together and create the superschema and run the codegen. I’m probably oversimplifying this and there will be unknown unknowns, but let me know if this sounds reasonable 🙂
👍 1
m
Apologies, got completely sidetracked. It doesn't sound straightforward but definitely an interesting thing to investigate. There's a question whether the backend subgraph should translate 1:1 in the frontend (this has been a huge no-no so far) or whether we want to allow a more "fluid" namespacing
Do you want to open an issue for that that I can ask feedback from iOS/web too?
g
Thanks Martin! I will create an issue.
whether the backend subgraph should translate 1:1 in the frontend
I didn’t quite understand this 🤔
m
If we’re saying feature modules have a “limited” view of the schema to not depend on the full 10k types, we could say those “views” match the federation subgraphs
But maybe a different segmentation would be useful