Skip to content

Commit

Permalink
smallrye#521: initial GraphQL Federation extension
Browse files Browse the repository at this point in the history
  • Loading branch information
t1 committed May 27, 2021
1 parent adfb49a commit 3aeb34d
Show file tree
Hide file tree
Showing 12 changed files with 730 additions and 1 deletion.
43 changes: 43 additions & 0 deletions server/federation/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
= GraphQL Federation

Extension library to add support for https://www.apollographql.com/docs/federation/federation-spec/[GraphQL Federation].

The `api` module defines the directives `@Key`, etc. The `runtime` module hooks into the SmallRye GraphQL events to dynamically enhance the GraphQL schema with the Federation extras (`_Entity`, `_Service`, etc.).

== API

Annotate the type you want to extend with `@Key`, e.g.:

[source,java]
----------
@Key(fields = "id")
public class Film {
String id;
String year;
String name;
// ...
}
----------

In the service that extends that type, declare a reduced view that only defines the keys, e.g. `id`. Annotate the type as `@Extends` and the field as `@External`, e.g.:

[source,java]
----------
@Extends @Key(fields = "id")
public class Film {
@External String id;
}
----------

And write a resolver method that adds the fields to that type, just like a 'local' `@Source` resolver would, but use `@FederatedSource` instead, e.g.:

[source,java]
----------
class Reviews {
public List<Review> reviews(@FederatedSource Film film) {
//...
}
}
----------

For a full example, see https://github.com/t1/graphql-federation-demo
28 changes: 28 additions & 0 deletions server/federation/api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-graphql-federation-parent</artifactId>
<version>1.2.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>smallrye-graphql-federation-api</artifactId>

<dependencies>
<dependency>
<groupId>org.eclipse.microprofile.graphql</groupId>
<artifactId>microprofile-graphql-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-graphql-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.smallrye.graphql.federation.api;

import static io.smallrye.graphql.api.DirectiveLocation.INTERFACE;
import static io.smallrye.graphql.api.DirectiveLocation.OBJECT;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;

import org.eclipse.microprofile.graphql.Description;

import io.smallrye.graphql.api.Directive;

/** <b><code>directive @extends on OBJECT | INTERFACE</code></b> */
@Directive(on = { OBJECT, INTERFACE })
@Description("Some libraries such as graphql-java don't have native support for type extensions in their printer. " +
"Apollo Federation supports using an @extends directive in place of extend type to annotate type references.")
@Retention(RUNTIME)
public @interface Extends {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.smallrye.graphql.federation.api;

import static io.smallrye.graphql.api.DirectiveLocation.FIELD_DEFINITION;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;

import org.eclipse.microprofile.graphql.Description;

import io.smallrye.graphql.api.Directive;

/** <b><code>directive @external on FIELD_DEFINITION</code></b> */
@Directive(on = FIELD_DEFINITION)
@Description("The @external directive is used to mark a field as owned by another service. " +
"This allows service A to use fields from service B while also knowing at runtime the types of that field.")
@Retention(RUNTIME)
public @interface External {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.smallrye.graphql.federation.api;

import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
* A federated resolver method is the federated equivalent to a 'local' resolver method (with a parameter annotated as
* <code>{@link org.eclipse.microprofile.graphql.Source @Source}</code>), i.e. it adds a field with the name of the method
* and the type of the method return type to the source object.
* The class of the source parameter must be annotated as <code>{@link Extends @Extends}</code>
* and have at least one field annotated as <code>{@link External @External}</code>.
*/
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface FederatedSource {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.smallrye.graphql.federation.api;

import static io.smallrye.graphql.api.DirectiveLocation.INTERFACE;
import static io.smallrye.graphql.api.DirectiveLocation.OBJECT;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;

import org.eclipse.microprofile.graphql.Description;
import org.eclipse.microprofile.graphql.NonNull;

import io.smallrye.graphql.api.Directive;

/** <b><code>directive @key(fields: _FieldSet!) on OBJECT | INTERFACE</code></b> */
@Directive(on = { OBJECT, INTERFACE })
@Description("The @key directive is used to indicate a combination of fields that can be used to uniquely identify " +
"and fetch an object or interface.")
@Retention(RUNTIME)
public @interface Key {
@NonNull
String[] fields();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.smallrye.graphql.federation.api;

import static io.smallrye.graphql.api.DirectiveLocation.FIELD_DEFINITION;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;

import org.eclipse.microprofile.graphql.Description;
import org.eclipse.microprofile.graphql.NonNull;

import io.smallrye.graphql.api.Directive;

/** <b><code>directive @provides(fields: _FieldSet!) on FIELD_DEFINITION</code></b> */
@Directive(on = FIELD_DEFINITION)
@Description("When resolving the annotated field, this service can provide additional, normally `@external` fields.")
@Retention(RUNTIME)
public @interface Provides {
@NonNull
String[] fields();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.smallrye.graphql.federation.api;

import static io.smallrye.graphql.api.DirectiveLocation.FIELD_DEFINITION;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;

import org.eclipse.microprofile.graphql.Description;
import org.eclipse.microprofile.graphql.NonNull;

import io.smallrye.graphql.api.Directive;

/** <b><code>directive @requires(fields: _FieldSet!) on FIELD_DEFINITION</code></b> */
@Directive(on = FIELD_DEFINITION)
@Description("In order to resolve the annotated field, this service needs these additional `@external` fields, " +
"even when the client didn't request them.")
@Retention(RUNTIME)
public @interface Requires {
@NonNull
String[] fields();
}
39 changes: 39 additions & 0 deletions server/federation/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-graphql-server-parent</artifactId>
<version>1.2.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>smallrye-graphql-federation-parent</artifactId>
<packaging>pom</packaging>

<modules>
<module>api</module>
<module>runtime</module>
</modules>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
44 changes: 44 additions & 0 deletions server/federation/runtime/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-graphql-federation-parent</artifactId>
<version>1.2.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>smallrye-graphql-federation-runtime</artifactId>

<dependencies>
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-graphql-federation-api</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-graphql</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.smallrye</groupId>
<artifactId>smallrye-graphql-schema-builder</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Loading

0 comments on commit 3aeb34d

Please sign in to comment.