diff --git a/.github/workflows/compatibility.yaml b/.github/workflows/compatibility.yaml
new file mode 100644
index 0000000000..46dce22f52
--- /dev/null
+++ b/.github/workflows/compatibility.yaml
@@ -0,0 +1,37 @@
+name: Federation Specification Compatibility Test
+
+on:
+ pull_request:
+ branches:
+ - series/2.x
+
+jobs:
+ compatibility:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout current branch (full)
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Setup Java (temurin@17)
+ uses: actions/setup-java@v2
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+
+ - name: Compatibility Test
+ uses: apollographql/federation-subgraph-compatibility@v2
+ with:
+ compose: 'apollo-compatibility/docker-compose.yaml'
+ schema: 'apollo-compatibility/schema.graphql'
+ path: 'graphql'
+ port: 4001
+ debug: true
+ token: ${{ secrets.GITHUB_TOKEN }}
+ # Boolean flag to indicate whether any failing test should fail the script
+ failOnWarning: false
+ # Boolean flag to indicate whether any required test should fail the script
+ failOnRequired: false
+ # Working directory to run the test from
+ workingDirectory: ''
\ No newline at end of file
diff --git a/apollo-compatibility/Dockerfile b/apollo-compatibility/Dockerfile
new file mode 100644
index 0000000000..39d3940abe
--- /dev/null
+++ b/apollo-compatibility/Dockerfile
@@ -0,0 +1,12 @@
+FROM hseeberger/scala-sbt:17.0.2_1.9.9_3.1.1 AS build
+
+WORKDIR /build
+COPY build.sbt .
+COPY project ./project
+COPY core ./core
+COPY federation ./federation
+COPY adapters/quick ./adapters/quick
+COPY macros ./macros
+COPY apollo-compatibility/src ./apollo-compatibility/src
+EXPOSE 4001
+CMD sbt apollo-compatibility/run
\ No newline at end of file
diff --git a/apollo-compatibility/README.md b/apollo-compatibility/README.md
new file mode 100644
index 0000000000..3fec355afe
--- /dev/null
+++ b/apollo-compatibility/README.md
@@ -0,0 +1,18 @@
+# federated subgraph to test apollo federation spec compatibility
+
+Implementation of a federated subgraph aligned to the requirements outlined in [apollo-federation-subgraph-compatibility](https://github.com/apollographql/apollo-federation-subgraph-compatibility).
+
+The subgraph can be used to verify compability against [Apollo Federation Subgraph Specification](https://www.apollographql.com/docs/federation/subgraph-spec).
+
+### Run compatibility tests
+Execute the following command from the root of the repo
+
+```
+npx @apollo/federation-subgraph-compatibility docker --compose apollo-compatibility/docker-compose.yml --schema apollo-compatibility/schema.graphql
+```
+
+### Printing the GraphQL Schema (SDL)
+
+```
+sbt "apollo-compability/run printSchema"
+```
\ No newline at end of file
diff --git a/apollo-compatibility/docker-compose.yaml b/apollo-compatibility/docker-compose.yaml
new file mode 100644
index 0000000000..deb5596a50
--- /dev/null
+++ b/apollo-compatibility/docker-compose.yaml
@@ -0,0 +1,7 @@
+services:
+ products:
+ build:
+ context: .
+ dockerfile: ./apollo-compatibility/Dockerfile
+ ports:
+ - 4001:4001
\ No newline at end of file
diff --git a/apollo-compatibility/products.graphql b/apollo-compatibility/products.graphql
new file mode 100644
index 0000000000..79b4848fa0
--- /dev/null
+++ b/apollo-compatibility/products.graphql
@@ -0,0 +1,83 @@
+extend schema
+@link(
+ url: "https://specs.apollo.dev/federation/v2.3"
+ import: [
+ "@composeDirective"
+ "@extends"
+ "@external"
+ "@key"
+ "@inaccessible"
+ "@interfaceObject"
+ "@override"
+ "@provides"
+ "@requires"
+ "@shareable"
+ "@tag"
+ ]
+)
+@link(url: "https://myspecs.dev/myCustomDirective/v1.0", import: ["@custom"])
+@composeDirective(name: "@custom")
+
+directive @custom on OBJECT
+
+type Product
+@custom
+@key(fields: "id")
+@key(fields: "sku package")
+@key(fields: "sku variation { id }") {
+ id: ID!
+ sku: String
+ package: String
+ variation: ProductVariation
+ dimensions: ProductDimension
+ createdBy: User @provides(fields: "totalProductsCreated")
+ notes: String @tag(name: "internal")
+ research: [ProductResearch!]!
+}
+
+type DeprecatedProduct @key(fields: "sku package") {
+ sku: String!
+ package: String!
+ reason: String
+ createdBy: User
+}
+
+type ProductVariation {
+ id: ID!
+}
+
+type ProductResearch @key(fields: "study { caseNumber }") {
+ study: CaseStudy!
+ outcome: String
+}
+
+type CaseStudy {
+ caseNumber: ID!
+ description: String
+}
+
+type ProductDimension @shareable {
+ size: String
+ weight: Float
+ unit: String @inaccessible
+}
+
+extend type Query {
+ product(id: ID!): Product
+ deprecatedProduct(sku: String!, package: String!): DeprecatedProduct
+ @deprecated(reason: "Use product query instead")
+}
+
+extend type User @key(fields: "email") {
+ averageProductsCreatedPerYear: Int
+ @requires(fields: "totalProductsCreated yearsOfEmployment")
+ email: ID! @external
+ name: String @override(from: "users")
+ totalProductsCreated: Int @external
+ yearsOfEmployment: Int! @external
+}
+
+type Inventory @interfaceObject @key(fields: "id") {
+ id: ID!
+ deprecatedProducts: [DeprecatedProduct!]!
+}
\ No newline at end of file
diff --git a/apollo-compatibility/src/main/scala/Main.scala b/apollo-compatibility/src/main/scala/Main.scala
new file mode 100644
index 0000000000..262b7f959e
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/Main.scala
@@ -0,0 +1,35 @@
+import caliban.CalibanError
+import zio._
+import caliban.quick._
+import services.{ InventoryService, ProductService, UserService }
+import zio.http.Server
+
+object Main extends ZIOAppDefault {
+
+ def run = for {
+ args <- ZIOAppArgs.getArgs
+ _ <- (args match {
+ case Chunk("printSchema") => printSchema
+ case _ => runServer
+ })
+ } yield ()
+
+ val printSchema = Console.printLine(ProductSchema.print)
+
+ val runServer = {
+ val server: ZIO[
+ ProductService with UserService with InventoryService with Server,
+ CalibanError.ValidationError,
+ Int
+ ] = (ProductSchema.api.toApp("/graphql") flatMap Server.install)
+
+ (server *> Console.printLine("Press any key to exit...") *> Console.readLine).orDie
+ .provide(
+ Server.defaultWithPort(4001),
+ ProductService.inMemory,
+ UserService.inMemory,
+ InventoryService.inMemory
+ )
+ }
+
+}
diff --git a/apollo-compatibility/src/main/scala/ProductSchema.scala b/apollo-compatibility/src/main/scala/ProductSchema.scala
new file mode 100644
index 0000000000..c602d0731a
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/ProductSchema.scala
@@ -0,0 +1,103 @@
+import caliban._
+import caliban.federation.EntityResolver
+import caliban.federation.tracing.ApolloFederatedTracing
+import caliban.introspection.adt.{ __Directive, __DirectiveLocation }
+import caliban.schema.Annotations.GQLDeprecated
+import caliban.schema.{ GenericSchema, Schema }
+import models._
+import services.{ InventoryService, ProductService, UserService }
+import zio.query.ZQuery
+import zio.{ URIO, ZIO }
+
+case class Query(
+ product: QueryProductArgs => URIO[ProductService, Option[models.Product]],
+ @GQLDeprecated("Use product query instead") deprecatedProduct: DeprecatedProductArgs => URIO[
+ ProductService,
+ Option[DeprecatedProduct]
+ ]
+)
+
+object Query {
+ object apiSchema extends GenericSchema[ProductService with UserService]
+ implicit val schema: Schema[ProductService with UserService, Query] = apiSchema.gen
+}
+
+object ProductSchema extends GenericSchema[ProductService with UserService] {
+ val productResolver: EntityResolver[ProductService with UserService] =
+ EntityResolver[ProductService with UserService, ProductArgs, models.Product] {
+ case ProductArgs.IdOnly(id) =>
+ ZQuery.serviceWithZIO[ProductService](_.getProductById(id.id))
+ case ProductArgs.SkuAndPackage(sku, p) =>
+ ZQuery.serviceWithZIO[ProductService](_.getProductBySkuAndPackage(sku, p))
+ case ProductArgs.SkuAndVariationId(sku, variation) =>
+ ZQuery.serviceWithZIO[ProductService](_.getProductBySkuAndVariationId(sku, variation.id.id))
+ }
+
+ val userResolver: EntityResolver[UserService with ProductService] =
+ EntityResolver[UserService with ProductService, UserArgs, User] { args =>
+ ZQuery.serviceWithZIO[UserService](_.getUser)
+ }
+
+ val productResearchResolver: EntityResolver[UserService with ProductService] =
+ EntityResolver.from[ProductResearchArgs] { args =>
+ ZQuery.some(
+ ProductResearch(
+ CaseStudy(caseNumber = args.study.caseNumber, Some("Federation Study")),
+ None
+ )
+ )
+ }
+
+ val deprecatedProductResolver: EntityResolver[ProductService with UserService] =
+ EntityResolver[ProductService with UserService, DeprecatedProductArgs, DeprecatedProduct] { args =>
+ ZQuery.some(
+ models.DeprecatedProduct(
+ sku = "apollo-federation-v1",
+ `package` = "@apollo/federation-v1",
+ reason = Some("Migrate to Federation V2"),
+ createdBy = ZIO.serviceWithZIO[UserService](_.getUser)
+ )
+ )
+ }
+
+ val inventoryResolver: EntityResolver[InventoryService with UserService] =
+ EntityResolver[InventoryService with UserService, InventoryArgs, Inventory] { args =>
+ ZQuery.serviceWith[InventoryService](_.getById(args.id.id))
+ }
+
+ val api: GraphQL[ProductService with UserService with InventoryService] =
+ graphQL(
+ RootResolver(
+ Query(
+ args => ZIO.serviceWithZIO[ProductService](_.getProductById(args.id.id)),
+ args =>
+ ZIO.some(
+ models.DeprecatedProduct(
+ sku = "apollo-federation-v1",
+ `package` = "@apollo/federation-v1",
+ reason = Some("Migrate to Federation V2"),
+ createdBy = ZIO.serviceWithZIO[UserService](_.getUser)
+ )
+ )
+ )
+ ),
+ directives = List(
+ __Directive(
+ "custom",
+ None,
+ Set(__DirectiveLocation.OBJECT),
+ _ => Nil,
+ isRepeatable = false
+ )
+ )
+ ) @@ federated(
+ productResolver,
+ userResolver,
+ productResearchResolver,
+ deprecatedProductResolver,
+ inventoryResolver
+ ) @@ ApolloFederatedTracing.wrapper()
+
+ val print = api.render
+
+}
diff --git a/apollo-compatibility/src/main/scala/models/CaseStudy.scala b/apollo-compatibility/src/main/scala/models/CaseStudy.scala
new file mode 100644
index 0000000000..573245d9f2
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/CaseStudy.scala
@@ -0,0 +1,12 @@
+package models
+
+import caliban.schema.Schema
+
+case class CaseStudy(
+ caseNumber: ID,
+ description: Option[String]
+)
+
+object CaseStudy {
+ implicit val schema: Schema[Any, CaseStudy] = Schema.gen
+}
diff --git a/apollo-compatibility/src/main/scala/models/CaseStudyArgs.scala b/apollo-compatibility/src/main/scala/models/CaseStudyArgs.scala
new file mode 100644
index 0000000000..eb056ecb61
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/CaseStudyArgs.scala
@@ -0,0 +1,10 @@
+package models
+
+import caliban.schema.{ ArgBuilder, Schema }
+
+case class CaseStudyArgs(caseNumber: ID)
+
+object CaseStudyArgs {
+ implicit val schema: Schema[Any, CaseStudyArgs] = Schema.gen
+ implicit val argBuilder: ArgBuilder[CaseStudyArgs] = ArgBuilder.gen
+}
diff --git a/apollo-compatibility/src/main/scala/models/Custom.scala b/apollo-compatibility/src/main/scala/models/Custom.scala
new file mode 100644
index 0000000000..8ad77d2b9e
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/Custom.scala
@@ -0,0 +1,6 @@
+package models
+
+import caliban.parsing.adt.Directive
+import caliban.schema.Annotations.GQLDirective
+
+case class Custom() extends GQLDirective(Directive("custom"))
diff --git a/apollo-compatibility/src/main/scala/models/DeprecatedProduct.scala b/apollo-compatibility/src/main/scala/models/DeprecatedProduct.scala
new file mode 100644
index 0000000000..9bf6bcbbd9
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/DeprecatedProduct.scala
@@ -0,0 +1,19 @@
+package models
+
+import caliban.schema.{ GenericSchema, Schema }
+import services.UserService
+import zio.URIO
+
+@GQLKey("sku package")
+case class DeprecatedProduct(
+ sku: String,
+ `package`: String,
+ reason: Option[String],
+ createdBy: URIO[UserService, Option[User]]
+)
+
+object DeprecatedProduct {
+ object apiSchema extends GenericSchema[UserService]
+
+ implicit val schema: Schema[UserService, DeprecatedProduct] = apiSchema.gen[UserService, DeprecatedProduct]
+}
diff --git a/apollo-compatibility/src/main/scala/models/DeprecatedProductArgs.scala b/apollo-compatibility/src/main/scala/models/DeprecatedProductArgs.scala
new file mode 100644
index 0000000000..bc005b22bd
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/DeprecatedProductArgs.scala
@@ -0,0 +1,13 @@
+package models
+
+import caliban.schema.{ ArgBuilder, Schema }
+
+case class DeprecatedProductArgs(
+ sku: String,
+ `package`: String
+)
+
+object DeprecatedProductArgs {
+ implicit val schema: Schema[Any, DeprecatedProductArgs] = Schema.gen
+ implicit val argBuilder: ArgBuilder[DeprecatedProductArgs] = ArgBuilder.gen[DeprecatedProductArgs]
+}
diff --git a/apollo-compatibility/src/main/scala/models/ID.scala b/apollo-compatibility/src/main/scala/models/ID.scala
new file mode 100644
index 0000000000..59404f6fe4
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/ID.scala
@@ -0,0 +1,11 @@
+package models
+
+import caliban.schema.{ ArgBuilder, Schema }
+import caliban.Value.StringValue
+
+case class ID(id: String) extends AnyVal
+
+object ID {
+ implicit val schema: Schema[Any, ID] = Schema.scalarSchema[ID]("ID", None, None, None, id => StringValue(id.id))
+ implicit val argBuilder: ArgBuilder[ID] = ArgBuilder.string.map(ID(_))
+}
diff --git a/apollo-compatibility/src/main/scala/models/Inventory.scala b/apollo-compatibility/src/main/scala/models/Inventory.scala
new file mode 100644
index 0000000000..582792d376
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/Inventory.scala
@@ -0,0 +1,16 @@
+package models
+
+import caliban.schema.{ GenericSchema, Schema }
+import services.{ InventoryService, UserService }
+
+@GQLInterfaceObject
+@GQLKey("email")
+case class Inventory(
+ id: ID,
+ deprecatedProducts: List[DeprecatedProduct]
+)
+
+object Inventory {
+ object genSchema extends GenericSchema[InventoryService with UserService]
+ implicit val schema: Schema[InventoryService with UserService, Inventory] = genSchema.gen
+}
diff --git a/apollo-compatibility/src/main/scala/models/InventoryArgs.scala b/apollo-compatibility/src/main/scala/models/InventoryArgs.scala
new file mode 100644
index 0000000000..f672a6d9de
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/InventoryArgs.scala
@@ -0,0 +1,10 @@
+package models
+
+import caliban.schema.{ ArgBuilder, Schema }
+
+case class InventoryArgs(id: ID)
+
+object InventoryArgs {
+ implicit val schema: Schema[Any, InventoryArgs] = Schema.gen
+ implicit val argBuilder: ArgBuilder[InventoryArgs] = ArgBuilder.gen
+}
diff --git a/apollo-compatibility/src/main/scala/models/MyFederation.scala b/apollo-compatibility/src/main/scala/models/MyFederation.scala
new file mode 100644
index 0000000000..142c66a696
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/MyFederation.scala
@@ -0,0 +1,14 @@
+package models
+
+import caliban.federation.v2x._
+
+abstract class MyFederation
+ extends caliban.federation.v2x.FederationV2(
+ Versions.v2_3 :: Link(
+ "https://myspecs.dev/myCustomDirective/v1.0",
+ List(
+ Import("@custom")
+ )
+ ) :: ComposeDirective("@custom") :: Nil
+ )
+ with FederationDirectivesV2_3
diff --git a/apollo-compatibility/src/main/scala/models/Product.scala b/apollo-compatibility/src/main/scala/models/Product.scala
new file mode 100644
index 0000000000..acfea6a0cd
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/Product.scala
@@ -0,0 +1,25 @@
+package models
+
+import caliban.schema.Schema
+import zio.UIO
+
+@GQLKey("id")
+@GQLKey("sku package")
+@GQLKey("sku variation { id }")
+@Custom
+case class Product(
+ id: ID,
+ sku: Option[String],
+ `package`: Option[String],
+ variation: Option[ProductVariation],
+ dimensions: Option[ProductDimension],
+ @GQLProvides("totalProductsCreated") createdBy: UIO[Option[User]],
+ @GQLTag("internal") notes: Option[String],
+ research: List[ProductResearch]
+)
+
+object Product {
+
+ implicit val schema: Schema[Any, Product] = Schema.gen
+
+}
diff --git a/apollo-compatibility/src/main/scala/models/ProductArgs.scala b/apollo-compatibility/src/main/scala/models/ProductArgs.scala
new file mode 100644
index 0000000000..94eb063a17
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/ProductArgs.scala
@@ -0,0 +1,29 @@
+package models
+
+import caliban.InputValue
+import caliban.schema.{ ArgBuilder, Schema }
+
+sealed trait ProductArgs
+
+object ProductArgs {
+ case class IdOnly(id: ID) extends ProductArgs
+ case class SkuAndPackage(sku: String, `package`: String) extends ProductArgs
+ case class SkuAndVariationId(sku: String, variation: ProductVariation) extends ProductArgs
+
+ private implicit val variationArgs: ArgBuilder[ProductVariation] = ArgBuilder.gen[ProductVariation]
+ val idOnlyArgBuilder: ArgBuilder[IdOnly] = ArgBuilder.gen[IdOnly]
+ val skuAndPackageArgBuilder: ArgBuilder[SkuAndPackage] = ArgBuilder.gen[SkuAndPackage]
+ val skuAndVariationIdArgBuilder: ArgBuilder[SkuAndVariationId] = ArgBuilder.gen[SkuAndVariationId]
+
+ implicit val argBuilder: ArgBuilder[ProductArgs] = (input: InputValue) =>
+ (for {
+ error <- skuAndVariationIdArgBuilder.build(input).swap
+ _ <- skuAndPackageArgBuilder.build(input).swap
+ _ <- idOnlyArgBuilder.build(input).swap
+ } yield error).swap
+
+ implicit val idOnlySchema: Schema[Any, ProductArgs.IdOnly] = Schema.gen
+ implicit val skuAndPackageSchema: Schema[Any, ProductArgs.SkuAndPackage] = Schema.gen
+ implicit val skuAndVariationIdSchema: Schema[Any, ProductArgs.SkuAndVariationId] = Schema.gen
+
+}
diff --git a/apollo-compatibility/src/main/scala/models/ProductDimension.scala b/apollo-compatibility/src/main/scala/models/ProductDimension.scala
new file mode 100644
index 0000000000..d5110051bf
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/ProductDimension.scala
@@ -0,0 +1,14 @@
+package models
+
+import caliban.schema.Schema
+
+@GQLShareable
+case class ProductDimension(
+ size: Option[String],
+ weight: Option[Float],
+ @GQLInaccessible unit: Option[String]
+)
+
+object ProductDimension {
+ implicit val schema: Schema[Any, ProductDimension] = Schema.gen
+}
diff --git a/apollo-compatibility/src/main/scala/models/ProductResearch.scala b/apollo-compatibility/src/main/scala/models/ProductResearch.scala
new file mode 100644
index 0000000000..72cb7b30f5
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/ProductResearch.scala
@@ -0,0 +1,13 @@
+package models
+
+import caliban.schema.Schema
+
+@GQLKey("study { caseNumber }")
+case class ProductResearch(
+ study: CaseStudy,
+ outcome: Option[String]
+)
+
+object ProductResearch {
+ implicit val schema: Schema[Any, ProductResearch] = Schema.gen
+}
diff --git a/apollo-compatibility/src/main/scala/models/ProductResearchArgs.scala b/apollo-compatibility/src/main/scala/models/ProductResearchArgs.scala
new file mode 100644
index 0000000000..8e6a67897d
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/ProductResearchArgs.scala
@@ -0,0 +1,10 @@
+package models
+
+import caliban.schema.{ ArgBuilder, Schema }
+
+case class ProductResearchArgs(study: CaseStudyArgs)
+
+object ProductResearchArgs {
+ implicit val schema: Schema[Any, ProductResearchArgs] = Schema.gen
+ implicit val argBuilder: ArgBuilder[ProductResearchArgs] = ArgBuilder.gen
+}
diff --git a/apollo-compatibility/src/main/scala/models/ProductVariation.scala b/apollo-compatibility/src/main/scala/models/ProductVariation.scala
new file mode 100644
index 0000000000..a97e716a0b
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/ProductVariation.scala
@@ -0,0 +1,11 @@
+package models
+
+import caliban.schema.Schema
+
+case class ProductVariation(
+ id: ID
+)
+
+object ProductVariation {
+ implicit val schema: Schema[Any, ProductVariation] = Schema.gen
+}
diff --git a/apollo-compatibility/src/main/scala/models/QueryProductArgs.scala b/apollo-compatibility/src/main/scala/models/QueryProductArgs.scala
new file mode 100644
index 0000000000..73982bfac5
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/QueryProductArgs.scala
@@ -0,0 +1,10 @@
+package models
+
+import caliban.schema.{ ArgBuilder, Schema }
+
+case class QueryProductArgs(id: ID)
+
+object QueryProductArgs {
+ implicit val argBuilder: ArgBuilder[QueryProductArgs] = ArgBuilder.gen
+ implicit val schema: Schema[Any, QueryProductArgs] = Schema.gen
+}
diff --git a/apollo-compatibility/src/main/scala/models/User.scala b/apollo-compatibility/src/main/scala/models/User.scala
new file mode 100644
index 0000000000..8b1d30e996
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/User.scala
@@ -0,0 +1,17 @@
+package models
+
+import caliban.schema.Schema
+
+@GQLKey("email")
+@GQLExtend
+case class User(
+ @GQLExternal email: ID,
+ @GQLExternal totalProductsCreated: Option[Int],
+ @GQLOverride("users") name: Option[String],
+ @GQLRequires("totalProductsCreated yearsOfEmployment") averageProductsCreatedPerYear: Option[Int],
+ @GQLExternal yearsOfEmployment: Int
+)
+
+object User {
+ implicit val schema: Schema[Any, User] = Schema.gen
+}
diff --git a/apollo-compatibility/src/main/scala/models/UserArgs.scala b/apollo-compatibility/src/main/scala/models/UserArgs.scala
new file mode 100644
index 0000000000..27ef3319bf
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/UserArgs.scala
@@ -0,0 +1,10 @@
+package models
+
+import caliban.schema.{ ArgBuilder, Schema }
+
+case class UserArgs(email: ID)
+
+object UserArgs {
+ implicit val schema: Schema[Any, UserArgs] = Schema.gen
+ implicit val argBuilder: ArgBuilder[UserArgs] = ArgBuilder.gen
+}
diff --git a/apollo-compatibility/src/main/scala/models/package.scala b/apollo-compatibility/src/main/scala/models/package.scala
new file mode 100644
index 0000000000..0a8d098dcd
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/models/package.scala
@@ -0,0 +1 @@
+package object models extends MyFederation
diff --git a/apollo-compatibility/src/main/scala/services/InventoryService.scala b/apollo-compatibility/src/main/scala/services/InventoryService.scala
new file mode 100644
index 0000000000..a60ce6d164
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/services/InventoryService.scala
@@ -0,0 +1,30 @@
+package services
+
+import models.{ DeprecatedProduct, ID, Inventory }
+import zio.{ ULayer, ZIO, ZLayer }
+
+trait InventoryService {
+
+ def getById(id: String): Option[Inventory]
+
+}
+
+object InventoryService {
+ val inventory = List(
+ Inventory(
+ id = ID("apollo-oss"),
+ deprecatedProducts = List(
+ DeprecatedProduct(
+ sku = "apollo-federation-v1",
+ `package` = "@apollo/federation-v1",
+ reason = Some("Migrate to Federation V2"),
+ createdBy = ZIO.serviceWithZIO[UserService](_.getUser)
+ )
+ )
+ )
+ )
+
+ val inMemory: ULayer[InventoryService] = ZLayer.succeed(new InventoryService {
+ def getById(id: String): Option[Inventory] = inventory.find(_.id.id == id)
+ })
+}
diff --git a/apollo-compatibility/src/main/scala/services/ProductService.scala b/apollo-compatibility/src/main/scala/services/ProductService.scala
new file mode 100644
index 0000000000..d6e828381a
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/services/ProductService.scala
@@ -0,0 +1,83 @@
+package services
+
+import zio.{ Ref, UIO, ZIO, ZLayer }
+
+trait ProductService {
+ def getProductById(id: String): UIO[Option[models.Product]]
+ def getProductBySkuAndPackage(sku: String, pack: String): UIO[Option[models.Product]]
+ def getProductBySkuAndVariationId(sku: String, variationId: String): UIO[Option[models.Product]]
+}
+
+object ProductService {
+ val productsResearch = List(
+ models.ProductResearch(
+ study = models.CaseStudy(models.ID("1234"), Some("Federation Study")),
+ outcome = None
+ ),
+ models.ProductResearch(
+ study = models.CaseStudy(models.ID("1235"), Some("Studio Study")),
+ outcome = None
+ )
+ )
+
+ val inMemory: ZLayer[Any, Nothing, ProductService] =
+ ZLayer(
+ Ref
+ .make(
+ List(
+ models.Product(
+ id = models.ID("apollo-federation"),
+ sku = Some("federation"),
+ `package` = Some("@apollo/federation"),
+ variation = Some(models.ProductVariation(models.ID("OSS"))),
+ dimensions = Some(models.ProductDimension(Some("small"), Some(1.0f), Some("kg"))),
+ createdBy = ZIO.some(
+ models.User(
+ models.ID("support@apollographql.com"),
+ Some(1337),
+ Some("Jane Smith"),
+ averageProductsCreatedPerYear = Some(1337 / 10),
+ yearsOfEmployment = 10
+ )
+ ),
+ notes = Some("This is a test product"),
+ research = productsResearch.init
+ ),
+ models.Product(
+ id = models.ID("apollo-studio"),
+ sku = Some("studio"),
+ `package` = Some(""),
+ variation = Some(models.ProductVariation(models.ID("platform"))),
+ dimensions = Some(models.ProductDimension(Some("small"), Some(1.0f), Some("kg"))),
+ createdBy = ZIO.some(
+ models.User(
+ models.ID("support@apollographql.com"),
+ Some(1337),
+ Some("Jane Smith"),
+ averageProductsCreatedPerYear = Some(1337 / 10),
+ yearsOfEmployment = 10
+ )
+ ),
+ notes = Some("This is a note"),
+ research = productsResearch.tail
+ )
+ )
+ )
+ .map { products =>
+ new ProductService {
+ override def getProductById(id: String): UIO[Option[models.Product]] =
+ products.get.map(_.find(_.id.id == id))
+
+ override def getProductBySkuAndPackage(sku: String, pack: String): UIO[Option[models.Product]] =
+ products.get.map(_.find(p => p.sku.contains(sku) && p.`package`.contains(pack)))
+
+ override def getProductBySkuAndVariationId(sku: String, variationId: String): UIO[Option[models.Product]] =
+ products.get.map(
+ _.find(p =>
+ p.sku.contains(sku) && p.variation.contains(models.ProductVariation(models.ID(variationId)))
+ )
+ )
+ }
+ }
+ )
+}
diff --git a/apollo-compatibility/src/main/scala/services/UserService.scala b/apollo-compatibility/src/main/scala/services/UserService.scala
new file mode 100644
index 0000000000..d37df0da0f
--- /dev/null
+++ b/apollo-compatibility/src/main/scala/services/UserService.scala
@@ -0,0 +1,23 @@
+package services
+
+import models.{ ID, User }
+import zio.{ UIO, ULayer, ZIO, ZLayer }
+
+trait UserService {
+
+ def getUser: UIO[Option[User]]
+}
+
+object UserService {
+ private val theUser = User(
+ averageProductsCreatedPerYear = Some(1337 / 10),
+ email = ID("support@apollographql.com"),
+ name = Some("Jane Smith"),
+ totalProductsCreated = Some(1337),
+ yearsOfEmployment = 10
+ )
+
+ val inMemory: ULayer[UserService] = ZLayer.succeed(new UserService {
+ def getUser: zio.UIO[Option[User]] = ZIO.some(theUser)
+ })
+}
diff --git a/build.sbt b/build.sbt
index a67aed4eeb..befe38b657 100644
--- a/build.sbt
+++ b/build.sbt
@@ -98,7 +98,8 @@ lazy val allProjects: Seq[ProjectReference] =
codegenSbt,
federation,
reporting,
- tracing
+ tracing,
+ apolloCompatibility
)
lazy val root = project
@@ -575,6 +576,24 @@ lazy val examples = project
tools
)
+lazy val apolloCompatibility =
+ project
+ .in(file("apollo-compatibility"))
+ .settings(commonSettings)
+ .settings(
+ name := "apollo-compatibility",
+ publish / skip := true,
+ run / fork := true,
+ run / connectInput := true
+ )
+ .settings(
+ skip := (scalaVersion.value != scala213),
+ ideSkipProject := (scalaVersion.value != scala213),
+ crossScalaVersions := Seq(scala213),
+ libraryDependencySchemes += "org.scala-lang.modules" %% "scala-java8-compat" % "always"
+ )
+ .dependsOn(federation, core, quickAdapter)
+
lazy val reporting = project
.in(file("reporting"))
.settings(name := "caliban-reporting")
diff --git a/docs/docs/optimization.html b/docs/docs/optimization.html
index df1d5ad916..1df8563b29 100644
--- a/docs/docs/optimization.html
+++ b/docs/docs/optimization.html
@@ -37,7 +37,9 @@
(opens new window)
Query optimization A GraphQL query may request multiple fields that are using the same resolver. It's not a problem if the resolver is a simple value, but it can be less than optimal when the resolver runs an effect (such as reading from a database).
We might want to:
cache identical queries (deduplication) batch queries to the same source This is possible in Caliban using the ZQuery
(opens new window) data type.
Additionally, one may want to perform optimizations based on the fields selected by the client.
-This optimization can be achieved by field metadata from Caliban that can be referenced in your query classes.
Introducing ZQuery A ZQuery[R, E, A]
is a purely functional description of an effectual query that may contain requests to one or more data sources. Similarly to ZIO[R, E, A]
, it requires an environment R
, may fail with an E
or succeed with an A
. All requests that do not need to be performed sequentially will automatically be batched, allowing for aggressive data source specific optimizations. Requests will also automatically be deduplicated and cached.
This allows for writing queries in a high level, compositional style, with confidence that they will automatically be optimized. For example, consider the following query from a user service.