https://kotlinlang.org logo
#apollo-kotlin
Title
# apollo-kotlin
c

Chris Fillmore

08/03/2023, 8:49 PM
Following up on this here because it is more Apollo-specific https://kotlinlang.slack.com/archives/C0B8MA7FA/p1691092134884899
@mbonnin do you know if it would be practical for Apollo to create custom scalar adapters for value classes as part of codegen?
The use case I have in mind is generating adapters for value classes for data model
ID
types
But I’m actually unsure if people commonly map their id’s to a specific value class
m

mbonnin

08/03/2023, 10:14 PM
Custom scalar adapters are a runtime thing, I don't think there is much value in generating them
In the value class case it's a few lines to define your adapter
Copy code
object GraphQLIDAdapter: Adapter<ID> {
    override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): ID {
        return ID(reader.nextString()!!)
    }

    override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: ID) {
        writer.value(value.value)
    }
}
If you go that route, make sure to register your adapter at build time to avoid boxing
Copy code
mapScalar("ID", "ID", "GraphQLIDAdapter")
c

Chris Fillmore

08/03/2023, 10:42 PM
But I would need to repeat this for every value class, despite the behaviour being identical
I have dozens of domain models with ids
s

Stylianos Gakis

08/03/2023, 10:44 PM
Are all those IDs separate scalars?
c

Chris Fillmore

08/03/2023, 10:45 PM
Not actually, no. But in an ideal world I would like them to be
s

Stylianos Gakis

08/03/2023, 10:45 PM
The adapter should just work for the scalar they are adapting no matter in which type it appears. If they all got an ID with the scalar type
ID
then 1 adapter is enough. Or am I misunderstanding you here?
You would like them to all have a unique scalar? That’s interesting, in that scenario yeah I guess you would need an adapter per scalar.
m

mbonnin

08/03/2023, 11:04 PM
That could be useful for arguments:
Copy code
type Query {
  user(id: UserId!): User
}
No risk of passing the "wrong" id. But I don't know of any API that does this
Also at the end of the day, ids are also common to all types. A lot of schemas have a
Node
base type:
Copy code
type Query {
  node(id: ID): Node
}
Here the
UserId
thing breaks. Unless you start adding inheritance on IDs but certainly not worth the trouble
c

Chris Fillmore

08/04/2023, 1:41 PM
For my regular http/rest-type calls, I use
kotlinx.serialization
, so my value classes are all marked
@Serializable
, so it’s not possible to confuse two fields like, for example
productId: String
and
productVariantId: String
, because they are typed as
productId: ProductId
and
productVariantId: VariantId
. This is genuinely useful.
It seems reasonable to me that a schema would define unique scalars for each type of model id. But I admit I have limited experience in this area, whether with graphql or schemas in general. But my sense is that if Apollo offered tooling to support this use case, that spared the client from manually writing adapters for each scalar, it would be easier to adopt.
Hypothetically, then, I could do this
Copy code
apollo {
  service("my-app") {
    mapScalar("MyScalarId", "com.example.MyValueClass")
  }
}
And so long as
MyValueClass
conformed to an expected format, like this:
Copy code
value class MyValueClass(val value: String)
Then an adapter could be generated automatically.
This is a bit hand-wavey at the moment but maybe you get where I’m going
m

mbonnin

08/04/2023, 1:59 PM
This only makes sense if there are a lot of scalars and if the pattern is applied uniformaly in your schema
Copy code
# generate adapters for those
scalar UserId
scalar ProductId

# but not for those
scalar Date
Also the
node(id: ID): Node
argument makes it very unlikely that your backend ever uses different types for different ids
c

Chris Fillmore

08/04/2023, 2:00 PM
The client would specify which scalars should have adapters
Also the
node(id: ID): Node
argument makes it very unlikely that your backend ever uses different types for different ids
Ah ok. This part I’m not really clear on
m

mbonnin

08/04/2023, 2:01 PM
There are a lot of places where you want to use "id" without a type attached
c

Chris Fillmore

08/04/2023, 2:02 PM
Ok. Like cacheing?
m

mbonnin

08/04/2023, 2:02 PM
One place is if you have interfaces and therefore multiple types will share ids
And caching indeed. Our normalized cache store everything without a type under the hood so when you want to read from the cache, it's
ApolloStore.read<T>(id: ID)
We could start binding the result type parameter (
T
) to the id
ID
but it sounds like a lot of complications
c

Chris Fillmore

08/04/2023, 2:03 PM
Mmm ok I see
m

mbonnin

08/04/2023, 2:04 PM
But I don't want to stop you from exploring 😄
c

Chris Fillmore

08/04/2023, 2:05 PM
Ok thanks for the discussion. Yeah it would certainly be something I’d be curious to explore.
m

mbonnin

08/04/2023, 2:05 PM
You could do all of this from user land I think. That's a nice project to add a
graphql.typed.ids
Gradle plugin that sits on top of Apollo
If it picks up we could then make this first party but at the moment, it's hard to justify prioritizing this and also maintaining one more API for very few users
c

Chris Fillmore

08/04/2023, 2:07 PM
Thanks Martin I appreciate all the feedback
m

mbonnin

08/04/2023, 2:07 PM
Sure thing, types are fun 🤓 !
🙌 1
2 Views