Global IDs
Viaduct's identity model assumes that every reference between objects flows through a Global ID. Fields don't carry raw foreign keys — they carry typed Global IDs, and any object that needs to be fetched on its own implements the Node interface and is retrieved via the node(id:) entry point. Adopting this model is what unlocks Viaduct's batching, type-safe references, and storage-independent client contracts.
A Global ID combines two pieces of information:
- Type: the GraphQL type name (for example, "Character", "Film", "Planet").
- Internal ID: your application's internal identifier for that entity.
Format and encoding¶
The raw form is "<Type>:<InternalID>", which is then base64-encoded.
// Encoded form for Character with internal ID "1":
val gid: String = Character.Reflection.globalId("1") // "Q2hhcmFjdGVyOjE="
When building objects in resolvers, use the execution context helper to attach a typed Global ID:
GRTStarship.of(ctx) {
id(ctx.globalIDFor<GRTStarship>(starship.id))
name(starship.name)
model(starship.model)
starshipClass(starship.starshipClass)
manufacturers(starship.manufacturers)
costInCredits(starship.costInCredits?.toDouble())
length(starship.length?.toDouble())
crew(starship.crew)
passengers(starship.passengers)
maxAtmospheringSpeed(starship.maxAtmospheringSpeed)
hyperdriveRating(starship.hyperdriveRating?.toDouble())
MGLT(starship.mglt)
cargoCapacity(starship.cargoCapacity?.toDouble())
consumables(starship.consumables)
created(starship.created.toString())
edited(starship.edited.toString())
}
Treat Global IDs as opaque at the network boundary. Clients pass them around — in
node(id:)queries, in input arguments, in cached responses — but should not parse them. Inside resolvers you can decode them throughctx.idorGlobalID.toInternalID()to recover the type and internal ID; just don't ask clients to do the same.
Using Global IDs in node resolvers¶
Node resolvers receive a decoded Global ID; use the internal ID to load the entity:
@Resolver
class CharacterNodeResolver
@Inject
constructor(
private val characterRepository: CharacterRepository
) : NodeResolvers.Character() {
// Node resolvers can also be batched to optimize multiple requests
// tag::node_batch_resolver_example[21] Example of a node resolver
override suspend fun batchResolve(contexts: List<Context>): List<FieldValue<Character>> {
// Extract all unique character IDs from the contexts
val characterIds = contexts.map { it.id.internalID }
// Perform a single batch query to get film counts for all characters
// We only compute one time for each character, despite multiple requests
val characters = characterIds.mapNotNull {
characterRepository.findById(it)
}
// For each context gets the character ID and map to the viaduct object
return contexts.map { ctx ->
val characterId = ctx.id.internalID
characters.firstOrNull { it.id == characterId }?.let {
FieldValue.ofValue(
CharacterBuilder(ctx).build(it)
)
} ?: FieldValue.ofError(IllegalArgumentException("Character not found: $characterId"))
}
}
}
Client usage via node(id:)¶
Clients pass a Global ID to retrieve a specific entity, independent of the underlying storage key format:
Schema hinting with @idOf¶
Annotate ID fields and arguments with @idOf to bind them to a concrete GraphQL type, enabling type-safe handling in resolvers and tooling:
type Character implements Node @scope(to: ["default"]) @resolver(isBatching: true) {
"""
The GlobalID of this character - uniquely identifies this Character in the graph (internal use only)
"""
id: ID!
Do and don’t¶
- Do treat Global IDs as opaque and stable across the API surface.
- Do generate them in resolvers using
ctx.globalIDForor<Type>.Reflection.globalId(...). - Do use
@idOfon schema fields/arguments carrying Global IDs. - Don’t expose internal IDs at the network boundary or ask clients to decode Global IDs. Encoding and decoding happen inside Viaduct on both ends; clients treat them as opaque tokens.
- Don’t embed business logic or access control information in IDs.
See Best Practices for the consolidated reference. For the encoding format, how to generate and consume
GlobalIDvalues in resolvers, and schema hints with@idOf, see the Global IDs developer reference.