Skip to main content
Version: 4.x.x

Federated Schemas

graphql-kotlin-federation library extends the functionality of the graphql-kotlin-schema-generator and allows you to easily generate federated GraphQL schemas directly from the code. Federated schema is generated by calling toFederatedSchema function that accepts federated configuration as well as a list of regular queries, mutations and subscriptions exposed by the schema.

All federated directives are provided as annotations that are used to decorate your classes, properties and functions. Since federated types might not be accessible through the regular query execution path, they are explicitly picked up by the schema generator based on their directives. Due to the above, we also need to provide a way to instantiate the underlying federated objects by implementing corresponding FederatedTypeResolvers. See type resolution wiki for more details on how federated types are resolved. Final federated schema is then generated by invoking the toFederatedSchema function (link).

In order to generate valid federated schemas, you will need to annotate both your base schema and the one extending it. Federated Gateway (e.g. Apollo) will then combine the individual graphs to form single federated graph.

caution

If you are using custom Query type then all of you federated GraphQL services have to use the same type. It is not possible for federated services to have different definitions of Query type.

Base Schema

Base schema defines GraphQL types that will be extended by schemas exposed by other GraphQL services. In the example below, we define base Product type with id and description fields. id is the primary key that uniquely identifies the Product type object and is specified in @key directive. Since it is a base schema that doesn't expose any extended functionality our FederatedTypeRegistry does not include any federated resolvers.

@KeyDirective(fields = FieldSet("id"))
data class Product(val id: Int, val description: String)

class ProductQuery {
fun product(id: Int): Product? {
// grabs product from a data source, might return null
}
}

// Generate the schema
val hooks = FederatedSchemaGeneratorHooks(emptyList())
val config = FederatedSchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = hooks)
val queries = listOf(TopLevelObject(ProductQuery()))

toFederatedSchema(config, queries)

Example above generates the following schema with additional federated types:

schema {
query: Query
}

union _Entity = Product

type Product @key(fields : "id") {
description: String!
id: Int!
}

type Query @extends {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service
product(id: Int!): Product
}

type _Service {
sdl: String!
}

Extended Schema

Extended federated GraphQL schemas provide additional functionality to the types already exposed by other GraphQL services. In the example below, Product type is extended to add new reviews field to it. Primary key needed to instantiate the Product type (i.e. id) has to match the @key definition on the base type. Since primary keys are defined on the base type and are only referenced from the extended type, all of the fields that are part of the field set specified in @key directive have to be marked as @external. Finally, we also need to specify an "entry point" for the federated type - we need to create a FederatedTypeResolver that will be used to instantiate the federated Product type when processing federated queries.

@KeyDirective(fields = FieldSet("id"))
@ExtendsDirective
data class Product(@ExternalDirective val id: Int) {
// Add the "reviews" field to the type
suspend fun reviews(): List<Review> = getReviewByProductId(id)
}

data class Review(val reviewId: String, val text: String)

// Resolve a "Product" type from the _entities query
class ProductResolver : FederatedTypeResolver<Product> {
override val typeName = "Product"

override suspend fun resolve(environment: DataFetchingEnvironment, representations: List<Map<String, Any>>): List<Product?> = representations.map { keys ->
keys["id"]?.toString()?.toIntOrNull()?.let { id -> Product(id) }
}
}

// Generate the schema
val resolvers = listOf(ProductResolver())
val hooks = FederatedSchemaGeneratorHooks(resolvers)
val config = FederatedSchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = hooks)

toFederatedSchema(config)

Our extended schema will then be generated as:

schema {
query: Query
}

union _Entity = Product

type Product @extends @key(fields : "id") {
id: Int! @external
reviews: [Review!]!
}

type Query @extends {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service
}

type Review {
reviewId: String!
text: String!
}

type _Service {
sdl: String!
}

Federated GraphQL schema

Once we have both base and extended GraphQL services up and running, we will also need to configure Federated Gateway to combine them into a single schema. Using the examples above, our final federated schema will be generated as:

schema {
query: Query
}

type Product {
description: String!
id: Int!
reviews: [Review!]!
}

type Review {
reviewId: String!
text: String!
}

type Query {
product(id: String!): Product
}

See our federation example for additional details.