Skip to main content
Version: pre-release

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 your entities in all your subgraphs. 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.

Subgraph A

Federation v2 relaxed entity ownership and now every subgraph that defines given entity is its owner. In the example below, we define 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 might be possible to resolve Product entity from other subgraphs, we also should 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"))
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
}
}

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

override suspend fun resolve(
environment: DataFetchingEnvironment,
representation: Map<String, Any>
): Product? =
representation["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)
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 {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
product(id: Int!): Product
}

type _Service {
sdl: String!
}

Subgraph B

Each subgraph can extend and provide new functionality to entities defined in other subgraphs. 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 one of the entity @key definitions defined in other subgraphs. Finally, we also need to specify an "entry point" for the federated type - we need to create a FederatedTypeResolver.

@KeyDirective(fields = FieldSet("id"))
data class Product(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 : FederatedTypeSuspendResolver<Product> {
override val typeName = "Product"

override suspend fun resolve(
environment: DataFetchingEnvironment,
representation: Map<String, Any>
): Product? =
representation["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 @key(fields : "id") {
id: Int!
reviews: [Review!]!
}

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

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

type _Service {
sdl: String!
}

Federated Supergraph

Once we have both GraphQL subgraphs up and running, we will also need to configure Federated Gateway to combine them into a single supergraph 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.