Skip to content

Commit

Permalink
Implement @cost and @listsize directives for demand control (#3074)
Browse files Browse the repository at this point in the history
<!-- ROUTER-384 -->
# Overview

Implements two new directives for demand control, based on the [IBM Cost
Specification](https://ibm.github.io/graphql-specs/cost-spec.html).

```
directive @cost(weight: Int!) on 
  | ARGUMENT_DEFINITION
  | ENUM
  | FIELD_DEFINITION
  | INPUT_FIELD_DEFINITION
  | OBJECT
  | SCALAR

directive @listsize(
  assumedSize: Int,
  slicingArguments: [String!],
  sizedFields: [String!],
  requireOneSlicingArgument: Boolean = true
) on FIELD_DEFINITION
```

The `@cost` directive allows users to specify a custom weight for
fields, enums, input objects, and arguments. The weight is used in the
demand control cost calculation, both for static estimates as well as
actual cost calculations.

The `@listSize` directive allows users to specify expected sizes of list
fields in their schema. This can be a static value set through
`assumedSize` or a dynamic value using `slicingArguments` to get the
value from some paging parameters.

## Differences from the spec

The main difference from the IBM spec is that we use an `Int!` for
weight argument of `@cost`. This allows the parser to enforce this is
parameterized with proper numeric values instead of finding out at
runtime that an invalid `String!` weight was passed.

## Caveats for shared fields

When `@cost` or `@listSize` are used on a `@shareable` field with
different values, the composed directive will use a merged value that
takes the maximum weight or assumed size, when applicable.
  • Loading branch information
tninesling authored Jul 22, 2024
1 parent 6cf28f2 commit 0ccfd93
Show file tree
Hide file tree
Showing 16 changed files with 841 additions and 17 deletions.
6 changes: 6 additions & 0 deletions .changeset/happy-bats-exist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@apollo/composition": minor
"@apollo/federation-internals": minor
---

Implements two new directives for defining custom costs for demand control. The `@cost` directive allows setting a custom weight to a particular field in the graph, overriding the default cost calculation. The `@listSize` directive gives the cost calculator information about how to estimate the size of lists returned by subgraphs. This can either be a static size or a value derived from input arguments, such as paging parameters.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ exports[`composing custom core directives custom tag directive works when federa
"schema
@link(url: \\"https://specs.apollo.dev/link/v1.0\\")
@link(url: \\"https://specs.apollo.dev/join/v0.3\\", for: EXECUTION)
@link(url: \\"https://specs.apollo.dev/tag/v0.3\\", as: \\"mytag\\")
@link(url: \\"https://specs.apollo.dev/tag/v0.3\\", import: [{name: \\"@tag\\", as: \\"@mytag\\"}])
@link(url: \\"https://custom.dev/tag/v1.0\\", import: [\\"@tag\\"])
{
query: Query
Expand Down
6 changes: 3 additions & 3 deletions composition-js/src/__tests__/compose.composeDirective.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,7 @@ describe('composing custom core directives', () => {
expect(errors(result)).toStrictEqual([
[
'DIRECTIVE_COMPOSITION_ERROR',
'Could not find matching directive definition for argument to @composeDirective "@fooz" in subgraph "subgraphA". Did you mean "@foo"?',
'Could not find matching directive definition for argument to @composeDirective "@fooz" in subgraph "subgraphA". Did you mean "@foo" or "@cost"?',
]
]);
});
Expand Down Expand Up @@ -926,8 +926,8 @@ describe('composing custom core directives', () => {
expectCoreFeature(schema, 'https://custom.dev/tag', '1.0', [{ name: '@tag' }]);
const feature = schema.coreFeatures?.getByIdentity('https://specs.apollo.dev/tag');
expect(feature?.url.toString()).toBe('https://specs.apollo.dev/tag/v0.3');
expect(feature?.imports).toEqual([]);
expect(feature?.nameInSchema).toEqual('mytag');
expect(feature?.imports).toEqual([{ name: '@tag', as: '@mytag' }]);
expect(feature?.nameInSchema).toEqual('tag');
expect(printSchema(schema)).toMatchSnapshot();
});

Expand Down
Loading

0 comments on commit 0ccfd93

Please sign in to comment.