Using `3.8.2` I got a root apollo module with ```....
# apollo-kotlin
s
Using
3.8.2
I got a root apollo module with
Copy code
...
packageName.set("octopus")
generateApolloMetadata.set(true)
generateDataBuilders.set(true)
Then in a feature module, I got
Copy code
apollo {
  service("octopus") {
    packageName.set("octopus") // Tried with "octopus.home" too, to have them be different
    generateDataBuilders.set(true)
  }
}
. In my feature module then in a test, I was trying to create an instance of a response, and it seems like it’s not generating the right thing which comes from my feature module. So in my root apollo module, I got a query which asks for a type, but only asks for a subset of the fields in there. The feature module asks for a unique field which wasn’t asked before, and the data builder does not generate the right builder. Basically in one place, for the
public fun BuilderScope.buildProductVariant(block: ProductVariantBuilder.() -> Unit)
function, the
ProductVariantBuilder
only contains the fields that are present in the home module queries. I have confirmed now that adding the field in the root module (despite not needing it there), generates the right data builder, and only the fields that I have there, not the ones that are in the feature module. Does this sound like something that I am doing wrong, or something that may be wrong with data builders in 3.x? I didn’t check with 4.x atm, but if you know for example that this is solved there then just let me know and I’ll work around it for now until I can go to 4.x.
m
In both v3 and v4 you need to either: 1. generate all types in the root module (
alwaysGenerateTypesMatching.set(listOf("*"))
) 2. or configure the "back links" so that the root module gets the used coordinates from the feature module
2. is significantly improved in v4 so if you're already doing this, it's worth trying with v4
s
Hmm, how does
configuring back links
look like? I don’t think I’ve read the docs around that yet 👀
m
Or if you can't just yet, add your field manually with
alwaysGenerateTypesMatching.set(listOf("Type.yourField"))
(I think this works in V3 but wouldn't put my hand on it)
Oh yea, so that's where multimodule gets really hairy
s
I haven’t been doing any of the above so far because for the time being we always have had the gql files all in the one big module, haven’t split them at all. I am now doing this for the first time basically, hence it’s the first time I meet this issue 😄
m
I would workaround in V3 and only set this up once you're in v4. The configuration is substantially different
s
Okay that’s good to know. Which part of the docs should I be looking at for this for v4? I can read up so I am ready for when we do the migration 😊
m
That's the v4 docs
thank you color 1
Data builders are quite complex because multiple modules might contribute fields to the same type so the only solution is to generate it in the root module.
There is some stuff in v3 using Gradle
apolloUsedCoordinates
configurations or so but it's not documented and had a bunch of issues so better wait until v4
👍 1
s
Yup, that makes sense. So in v4, you’ll do X amount of
isADependencyOf
for X feature modules you got with this configuration + 1
dependsOn
per feature module with gql files, and it should then all work well together
m
Yes, exactly.
I'm still not completely hyped by this because it feels like there should be a way to express this bi-directional relationship in a single place but AFAIK, Gradle doesn't allow that because it breaks project isolation
s
Sounds good, yeah. I will add a dummy query in the root module for now which queries the right things, and just not use it. And remove it on v4 then. That should work.
👍 1
Yeah, I was thinking with this if there’s a way for some in-house convention plugin to make this happen, which would maybe? work? But would probably be a per-case basis, and not really something you can provide for us I would guess.
m
I've looked at this problem from different angles but didn't find anything. The closest is https://github.com/gradle/gradle/issues/22514 but that wouldn't help us here I think
s
Like how some convention plugins, like the one from slack I think allows each feature module to do something like
Copy code
features {
  anvil()
  blahblah()
}
And have that maybe somehow hook everything together. But this is definitely a stretch, I am only imagining here, I do not have anything concrete on my mind. Seen something like this mentioned here https://androidstudygroup.slack.com/archives/C0SFT151T/p1674252933934949
👀 1
m
Oh yea, that's the Groovy/Kotlin DSL interop thingie?
That's the "easy" part 😅 The "hard" part is how to do
Copy code
project(":schema").dependencies {
  add("apolloUsedCoordinates", project)
}
in a way that doesn't break all the Gradle logic
BTW, for Groovy/Kotlin DSL interop, the trick is to write all your plugin API using
block: Action<T>
parameters. Gradle will generate magic bytecode to have your function accept both a Kotlin function type (works out of the box with SAM conversion) and a Groovy closure (requires magic bytecode)
s
I don’t know. How I understood is just project-specific DSL to setup features quickly. So I imagine perhaps for our app for example I could do
Copy code
hedvig {
  apollo()
}
Which would setup the dependencies + setup
dependsOn(project(":schema"))
Now at that point I don’t know if that convention plugin can also check all those callers who call
apollo()
and then also setup
isADependencyOf
accordingly. A question, if the root module does
isADependencyOf
for all modules? So that you can just add that and only need to care for the child modules after that? Or does that fail during compilation?
m
I don’t know if that convention plugin can also check all those callers who call
apollo()
and then also setup
isADependencyOf
accordingly.
Yea, that's the issue sad panda . I don't think it's safe to iterate the projects from a convention plugin
root module
I try to refer it as the "schema module". It doesn't have to be your root project in the Gradle acceptation of the term
you can just add that and only need to care for the child modules after that
If you can iterate all projects, then it might work but I'm not sure how "project isolation-compatible" this is.
My hunch is that if it's allowed to iterate in the "schema module", it's probably allowed to lookup a given module from another one so might as well do everything in the convention plugin
s
Yes sorry, when I said
root module
I did in fact mean
the root apollo module
not the actual root module. That’s what we do too.
m
Yep, I've made the mistake multiple times 🙂 . This is why I'm forcing myself to say "schema module" now. Removes any possible confusion
Takes a while to get used to but I'll get there before I replace all the "gradle enterprises" with "develocity" 😄
😂 2
s
“Schema module” it is, I like it more, I just couldn’t come up with it myself 😅 Oh boy, the world isn’t ready for Develocity. I certainly am not for one 😂
😄 1
y
Would a settings plugin help here?
m
Maybe? Is there anything about settings plugins that make it easier to work with project isolation?
y
not sure, but IIRC you can do subproject / allprojects etc without breaking project isolation
just read https://github.com/gradle/gradle/issues/22514 again, maybe not 😅
😄 1
m
We might as well decide to give up project isolation, I'm not sure if this will ever be a reality...
2
y
I’m finally starting to migrate to v4 😄 so is there a way to configure the backlink automatically (ignoring project isolation 🙈)?
m
isADependencyOf()
should do it!
Ah wait, that’s not “automatic”, sorry, even I start getting lost there.
😂 1
Copy code
dependsOn(dependencyNotation = project(":schema"), bidirectional = true)
This is more like what you’re looking for ☝️
y
Oh nice!
I like that you have to pick your poison 😬
m
Yeaaa TBH, I feel like it’s not the end of the story but at least should give you a solution to configure everything in your convention plugin if needed
💯 1
y
while we’re here, doesn’t this
dependsOn
also break project isolation?
m
I don’t think so because it only uses the “name” of the other project
Just like doing
Copy code
dependencies {
  implementation(project(":schema"))
}
would be compatible
I’d love to enable PI on some of my projects but it’s really hard right now. I think KGP has “some” support for JVM only projects but last time I tried I bumped into corner cases that made projects fail
y
I see, so
bidirectional
needs to resolve more things from
schema
when configuring the project?
m
Yep
It’s actually getting an instance of the project and mutating stuff on it
(
dependencies.project(":schema")
doesn’t return a
Project
instance,
Project.project(":schema")
does)
💡 1
y
trying out
bidirectional
, is it only meant for the modules that has a schema, or does it also work on the other modules that produces metadata e.g. for sharing fragment?
m
It's meant for modules that do not have a schema. They depend on the schema module with a double link so that the schema module can get the used types
y
sorry I mean the “target”
for example I have a
:feature
module that has
dependsOn
on
:schema
, but also another
dependsOn
on a
:sdui
module with GraphQL fragment
should I have
dependsOn(project(":schema"), bidirectional = true)
and
dependsOn(project(":sdui"), bidirectional = true)
?
m
Gotcha. In that case, no need 👍
You still need it in your
sdui
module
In a future version, we may have intermediate "nodes" but as of today all types get generated in the schema module
👍 1
y
Cool, that brings us back to the “client schema composition” thing but I won’t hijack the thread 😬
👍 1
BTW with this optimization is the build time improvement and presumably binary size reduction only expected on the schema modules?
m
Yes, mostly. You pay a small price in each feature module (because of the IR serialisation/serialization) that should pay off for the schema module (because no need to generate, and compile unused types
Although I must say this is really hard to measure. If you have a large codebase, would love to hear about numbers there
y
yeah doing some basic tests now
m
I think the biggest gain is incremental builds. If you change only one module, no need to recompile the other ones
(unless you add a new type...)
y
you mean changing the graphql documents in a feature module right?
m
Yep!
Changing the schema most likely recompiles everything but that's supposed to happen less often
👍 1
y
hmm this seems to have no effect at all, I’m pretty sure we have unused types 😅
even the size of
build
folder is identical
m
Did you remove
alwaysGenerateTypesMatching
?
y
yeah we had
alwaysGenerateTypesMatching.set(listOf(".*"))
and I removed it immediately
👍 1
can confirm we have unused types in the schema