Federation๐
Undine provides opt-in support for Apollo Federation 2, letting you expose an Undine schema as a subgraph in a federated supergraph. Federation 2 lets a router combine multiple subgraphs into a single GraphQL API, so different teams can own different parts of the schema.
Setup๐
You can build a Federation 2 subgraph-compliant schema by importing create_federation_schema instead of
create_schema.
Compared with create_schema, create_federation_schema additionally:
- Validates
FEDERATION_VERSIONis supported. - Auto-generates a schema-level
@linkdirective. - Injects
Query._servicewhich returns the subgraph SDL for the router.
Directives๐
LinkDirective๐
Subgraph schemas opt in to Apollo Federation 2 by applying the @link
directive to the schema type. Undine automatically compiles the correct directive for you in create_federation_schema
based on the directives in use in your schema.
The @link directive also accepts two optional arguments: as renames the linked spec's namespace prefix,
so directives are addressed as @<as>__key instead of @key, and for declares the purpose of the link,
either SECURITY or EXECUTION, which Apollo Router uses to decide how to compose the subgraph.
See the spec for more information.
KeyDirective๐
The @key directive marks a QueryType as an entity.
To use the directive, you must define a set of fields that a subgraph can use to uniquely identify
any instance of the entity. These fields must be defined on the QueryType.
Set resolvable=False when this subgraph only references the entity but does not resolve it. The
router uses the declaration to stitch results together, but never routes resolution requests here.
ShareableDirective๐
the @shareable directive indicates that a field, or all fields of a type,
are allowed to be resolved by multiple subgraphs.
Note: if a field is included in an entity's
@keydirective, that field is automatically considered@shareableand the directive is not required in the corresponding subgraph(s).
ExternalDirective๐
The @external directive marks a field as owned by another subgraph.
This subgraph declares the field so it can be referenced (e.g. by @requires) but does
not resolve it.
RequiresDirective๐
The @requires directive declares that resolving a field on this
subgraph requires additional @external fields from the entity to be fetched first.
ProvidesDirective๐
The @provides directive tells the router that this subgraph can
resolve the listed fields of a referenced entity, avoiding an extra round-trip to the owning subgraph.
OverrideDirective๐
The @override directive tells the router that this subgraph takes
over resolution of a field previously owned by another subgraph. from_ names the subgraph being overridden.
Pass an optional label to opt into progressive override โ the router uses the label to route a
configurable fraction of traffic to this subgraph rather than switching over all at once.
Requires
FEDERATION_VERSION"2.7"or higher for thelabelargument.
InaccessibleDirective๐
The @inaccessible directive hides a schema element from the
supergraph. The element remains defined in the subgraph but the router never exposes it to clients.
TagDirective๐
The @tag directive attaches arbitrary metadata strings to schema
elements. Contract composition and other Apollo tooling read the tags for filtering. Repeatable.
ComposeDirectiveDirective๐
The @composeDirective directive tells the router to preserve a
custom directive from this subgraph in the supergraph schema. Apply it at the schema level via
schema_definition_directives.
Requires
FEDERATION_VERSION"2.1"or higher.
InterfaceObjectDirective๐
The @interfaceObject directive lets a subgraph contribute
fields to an entity that is declared as an interface in another subgraph. Apply it to a QueryType alongside
@key.
Requires
FEDERATION_VERSION"2.3"or higher.
AuthenticatedDirective๐
The @authenticated directive marks a schema element as
requiring an authenticated request. Enforcement happens at the router.
Requires
FEDERATION_VERSION"2.5"or higher.
RequiresScopesDirective๐
The @requiresScopes directive marks a schema element as
requiring the caller to hold one of the listed scope sets. Each inner list is an "all-of" group; the outer
list is "any-of".
Requires
FEDERATION_VERSION"2.5"or higher.
PolicyDirective๐
The @policy directive marks a schema element as gated by named
authorization policies. The list-of-lists shape matches @requiresScopes.
Requires
FEDERATION_VERSION"2.6"or higher.
ContextDirective๐
The @context directive names a context that the annotated type
contributes to. Arguments elsewhere resolve their value from that context via
@fromContext. Repeatable.
Requires
FEDERATION_VERSION"2.8"or higher.
FromContextDirective๐
The @fromContext directive populates a directive argument from
a named context declared via @context.
Requires
FEDERATION_VERSION"2.8"or higher.
CostDirective๐
The @cost directive attaches a demand-control weight to a schema
element. The router's cost analyzer sums these to estimate the total cost of a query.
Requires
FEDERATION_VERSION"2.9"or higher.
ListSizeDirective๐
The @listSize directive tells the demand-control cost analyzer
how large a list-returning field can grow. assumed_size is a fixed upper bound; slicing_arguments and
sized_fields describe pagination-based bounds.
Requires
FEDERATION_VERSION"2.9"or higher.
CacheTagDirective๐
The @cacheTag directive attaches a cache-tag template to a schema
element. The router uses the rendered tags to invalidate response cache entries. Repeatable, and applicable
to both object types and individual fields.
Requires
FEDERATION_VERSION"2.12"or higher.
Entities๐
An entity is an object type marked with @key that can be fetched with one or more
unique keys and resolve its fields from multiple data sources in a federated graph. The router fetches
entities across subgraphs by sending representations (dicts like { "__typename": "TaskType", "id": 1 })
to your subgraph and expects the matching entities back.
Undine wires this up automatically in create_federation_schema whenever the schema contains at
least one resolvable @key entity, adding an _entities field to the Query type that the router calls
to fetch entities by their representations.
Reference resolver๐
The reference resolver runs for each representation after the router dispatches by __typename. By default,
Undine builds one for you, but the default resolver only supports a QueryType with a single resolvable
@key whose fields is a single, unaliased token that maps to a declared Field. In any other case,
you need define a custom __resolve_reference__ on the QueryType.
Returning None from __resolve_reference__ yields null for that entry. Raising propagates as a
GraphQL error on that entry's response slot.
FederationType๐
A FederationType contributes fields to an entity that is owned by another subgraph
and is not backed by a Django model. You still mark it with @KeyDirective
so the router knows how to identify entities of this type.
Note:
schema_namemust match the name to the shared entity name across subgraphs.
FederationField๐
A FederationField is used to define a queryable value on a FederationType.
It mirrors Field on a QueryType.
A FederationField resolves via one of three rules:
- Explicit resolver โ
@<field>.resolvewas used, or the decorator form@FederationField(...)wrapped a function. - Key field โ the field's name appears in some
@KeyDirective(fields=...). The router populates the attribute from the representation; the resolver is a plain attribute lookup on the instance. @externalโ the field carries@ExternalDirective(). Populated by another subgraph via@requires; again, a plain attribute lookup.
Anything else โ a computed field with no resolver, no key membership, and no @external โ is a definition
error and raises an exception when the schema is built.
The following declares a computed overdue_task_count that depends on timezone from another subgraph:
Both timezone (an @external attribute) and id (a key field) are populated by the router; only
overdue_task_count needs an explicit resolver.
Stub references๐
A stub declares an entity type this subgraph references but does not resolve โ the router routes
resolution requests elsewhere. Mark the key as resolvable=False, declare the key field(s) explicitly,
and leave the rest of the type empty. You can then use the stub as a Field reference on a resolvable
QueryType, and return a representation dict from the resolver so the router can fetch the rest of the
entity from the owning subgraph:
The stub still appears in _service.sdl (so the router can compose it), but it does not appear in the
_Entity union and no _entities dispatch is wired for it.
Permissions๐
When the router fetches entities through the injected _entities endpoint, it bypasses your
top-level query entrypoints. To keep authorization consistent, Undine invokes the entity's
__permissions__ classmethod on every entity resolved through _entities, whether the entity is backed
by a QueryType or by a FederationType.
For a QueryType, the same __permissions__ that runs on regular entrypoints
runs here too โ no extra wiring is needed.
For a FederationType, define __permissions__ on the class; the _entities resolver invokes it right
after __resolve_reference__ builds the instance:
To gate an individual FederationField, decorate a method with @<field>.permissions. Sibling fields
keep resolving independently:
Both hooks may also be defined async. If a permission check fails, the failing entity slot or field
becomes null with the error attached, leaving the rest of the batch intact.
Visibility๐
This is an experimental feature that needs to be enabled using the
EXPERIMENTAL_VISIBILITY_CHECKSsetting.
You can hide a FederationType from certain users by using the __is_visible__ method.
Selecting a hidden FederationType results in an error that looks exactly
like the type didn't exist in the first place.
You can hide a FederationField from certain users by decorating a method with the
<field_name>.visible decorator. Hiding a FederationField means that trying to select it results
in an error that looks exactly like the field didn't exist in the first place.
Note that visibility expects clients to introspect and query the your schema directly. In a federation scenario, the composed supergraph that the router advertises does not change per request. This results in hidden types and fields showing when they shouldn't. This also applies to other schema entities where visibility can be used, see visibility rule.
Apollo compatibility๐
Undine is verified against Apollo's subgraph compatibility harness, which
composes a subgraph with Apollo's users and inventory subgraphs plus a router and runs
Apollo's canonical query set against the composed supergraph.
A reference implementation of Apollo's products subgraph in Undine lives at
tests/test_federation/compatibility/. It contains a small Django project you can read end-to-end to see
entities, FederationType extensions, @key/@requires/@provides/@override, custom directives
via @composeDirective, and reference resolvers wired against real models. See its README.md for
how to boot it locally and run Apollo's compliance runner.
| Federation 1 Support | Federation 2 Support | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
Federated tracing๐
Undine ships FederatedTracingHook, a lifecycle hook that implements Apollo's
Federated Tracing v1 (ftv1) protocol. Register it in LIFECYCLE_HOOKS to opt in:
The hook requires the protobuf package, which is not installed by default.
Install it via the federation-tracing extra:
When a request carries the apollo-federation-include-trace: ftv1 header, the
hook records timings for every resolver call and attaches a message under extensions.ftv1 on the response.
See Apollo docs for more information.