Backing Data
The @backingData directive binds a field to a backing data class that performs transformation, shaping, or
preparation of data before it is exposed by GraphQL. It complements @resolver: the resolver wires the field into
execution, while the backing data class centralizes the mapping logic that would otherwise live inside the resolver.
Basic usage¶
Apply @backingData on a field (often together with @resolver) and point to a Kotlin class that implements the
backing logic.
allCharacters(limit: Int): [Character]
@resolver
@backingData(class: "com.example.starwars.modules.filmography.characters.queries.AllCharactersQueryResolver")
In this demo, the class
com.example.starwars.modules.filmography.characters.queries.AllCharactersQueryResolver
is responsible for shaping the list of Character items returned by allCharacters.
How it integrates at runtime¶
- Execution plan: Viaduct identifies that
allCharactersis a resolver-backed field with a backing data class. - Resolver step: the resolver coordinates arguments and orchestration (for example, reads the
limit). - Backing step: the backing class transforms raw domain models into GraphQL objects (builders), applying any mapping, filtering, or normalization rules required by the schema.
- Result: the field returns objects that already match the schema’s expectations (IDs, formatting, minimal fields).
With @backingData (mapping lives in a backing class)¶
@Resolver
class AllCharactersQueryResolver
@Inject
constructor(
private val characterRepository: CharacterRepository
) : QueryResolvers.AllCharacters() {
override suspend fun resolve(ctx: Context): List<Character?>? {
// Fetch characters with pagination
val limit = ctx.arguments.limit ?: DEFAULT_PAGE_SIZE
val characters = characterRepository.findAll().take(limit)
// Convert StarWarsData.Character objects to Character objects
return characters.map { CharacterBuilder(ctx).build(it) }
}
}
Pros: fewer moving parts. Cons: resolver grows, mapping is harder to share or test in isolation.
Design guidelines¶
- Keep resolvers thin; put mapping/formatting in the backing data class.
- Generate
idvalues withctx.globalIDFor(Type.Reflection, internalId). - Request only the minimal fields you need; defer relationships to field/batch resolvers.
- Prefer immutable outputs from the backing class (builders with final values).