Following up on this here because it is more Apoll...
# apollo-kotlin
c
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
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
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
Are all those IDs separate scalars?
c
Not actually, no. But in an ideal world I would like them to be
s
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
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
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
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
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
There are a lot of places where you want to use "id" without a type attached
c
Ok. Like cacheing?
m
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
Mmm ok I see
m
But I don't want to stop you from exploring 😄
c
Ok thanks for the discussion. Yeah it would certainly be something I’d be curious to explore.
m
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
Thanks Martin I appreciate all the feedback
m
Sure thing, types are fun 🤓 !
🙌 1