Skip to content

Commit

Permalink
Update more sections
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitriy Lazarev <[email protected]>
  • Loading branch information
wKich committed Dec 11, 2023
1 parent 8d7ead8 commit fd512f8
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 62 deletions.
73 changes: 68 additions & 5 deletions www/docs/backstage.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,70 @@
TODO: How to start using backstage graphql plugin
TODO: Link to `@relation`
TODO: Get some text from the RFC
The [Backstage GraphQL plugin][graphql-backend] is designed for schema-first
development of the GraphQL API. It reduces work necessary to expand the schema
using schema directives. [Schema directives](https://the-guild.dev/graphql/tools/docs/schema-directives)
are extensions to GraphQL schema used to automate implementation of the GraphQL API.
In Backstage GraphQL Plugin, schema directives are used to automatically create
resolvers. [Resolvers](https://graphql.org/learn/execution/) tell a GraphQL API
how to provide data for a specific schema type or field. The Backstage GraphQL
Plugin uses what it knows about the Backstage catalog to reduce the need for
writing resolvers that call the catalog.

## Backend Development
Currently, Backstage implemented two backend systems:

The Backstage GraphQL plugin is designed for schema-first development of the GraphQL API. It reduces work necessary to expand the schema using schema directives. [Schema directives](https://the-guild.dev/graphql/tools/docs/schema-directives) are extensions to GraphQL schema used to automate implementation of the GraphQL API. In Backstage GraphQL Plugin, schema directives are used to automatically create resolvers. [Resolvers](https://graphql.org/learn/execution/) tell a GraphQL API how to provide data for a specific schema type or field. The Backstage GraphQL Plugin uses what it knows about the Backstage catalog to reduce the need for writing resolvers that call the catalog.
- [EXPERIMENTAL Backstage backend system](https://backstage.io/docs/backend-system/)
- [Backstage backend plugins](https://backstage.io/docs/plugins/backend-plugin)

### Usage with backend system

The full cover of using GraphQL with EXPERIMENTAL Backstage backend system you can
find in [readme](https://github.com/thefrontside/playhouse/blob/main/plugins/graphql-backend/README.md)

```typescript
// packages/backend/src/index.ts
import { graphqlPlugin } from '@frontside/backstage-plugin-graphql-backend';
import { graphqlModuleCatalog } from '@frontside/backstage-plugin-graphql-backend-module-catalog';

// Initializing Backstage backend
const backend = createBackend();

// Adding GraphQL plugin
backend.use(graphqlPlugin());
// Adding Catalog GraphQL module
backend.use(graphqlModuleCatalog());
```

### Usage with backend plugins

Using the old Backstage backend plugins system is also fully covered in
[readme](https://github.com/thefrontside/playhouse/blob/main/plugins/graphql-backend/docs/backend-plugins.md)

```typescript
// packages/backend/src/plugins/graphql.ts
import { createRouter } from '@frontside/backstage-plugin-graphql-backend';
import { Catalog, createCatalogLoader } from '@frontside/backstage-plugin-graphql-backend-module-catalog';

export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
return await createRouter({
logger: env.logger,
modules: [Catalog],
loaders: { ...createCatalogLoader(env.catalogClient) },
});
}
```

### Catalog module

The single GraphQL plugin isn't useful by itself. To able query Backstage Catalog
using GraphQL queries you need to install the [Catalog module][catalog-module].

The Catalog module provides [`@relation`](./relation) schema directive and data
loader for Catalog API. It also has well written [Catalog GraphQL module][catalog-schema]
with most basic Backstage types. We recommend to use it as a starting point. But
if you'd like to implement your own type structure you can use [Relation GraphQL module][relation-schema].
Relation module contains only `@relation` schema directive and Catalog data loader.

[graphql-backend]: https://github.com/thefrontside/playhouse/blob/main/plugins/graphql-backend
[catalog-module]: https://github.com/thefrontside/playhouse/blob/main/plugins/graphql-backend-module-catalog
[catalog-schema]: https://github.com/thefrontside/playhouse/blob/main/plugins/graphql-backend-module-catalog/src/catalog
[relation-schema]: https://github.com/thefrontside/playhouse/blob/main/plugins/graphql-backend-module-catalog/src/relation
1 change: 1 addition & 0 deletions www/docs/field.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ primitives, such as strings, numbers, booleans, etc. To resolve GraphQL nodes
from the same source or other sources, use [`@resolve`](./resolve) directive.

### Simple example

Mapping `namespace.name` field from source data to `Entity#name` field

```graphql
Expand Down
4 changes: 2 additions & 2 deletions www/docs/introduction.mdx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## GraphQL basics
### GraphQL basics

GraphQL is a powerfull query language and runtime for APIs. It provides a flexible
and efficient approach to requesting and manipulating data from servers. With GraphQL,
Expand Down Expand Up @@ -57,7 +57,7 @@ type Droid implements Character {

To solve these problems, HydraphQL was created.

## What is HydraphQL?
### What is HydraphQL?

HydraphQL is a set of GraphQL directives that allow you to enchance your schema,
declaratively define how the data should be processed and reduce the amount of
Expand Down
8 changes: 4 additions & 4 deletions www/docs/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ And then you can install HydraphQL itself:
yarn add @frontside/hydraphql
```

## Usage
### Usage

So after you've installed packages you can create your first GraphQL application.
HydraphQL uses [graphql-modules] under the hood and returns an instance of
Expand Down Expand Up @@ -113,7 +113,7 @@ query {
}
```

## Adding a data loader
### Adding a data loader

Our application is working, but functionality is pretty limited. We can only fetch
one asset at a time, but what if we want to fetch multiple assets? Or fetch more
Expand Down Expand Up @@ -156,7 +156,7 @@ export async function Exchanges(queries: readonly NodeQuery[]) {

```typescript
// src/application.ts
import http from 'http';
import { createServer } from 'node:http';
import { createHandler } from 'graphql-http';
import { createGraphQLApp, createLoader } from "@frontside/hydraphql";
import { MyModule } from "./modules/my-module/my-module";
Expand All @@ -167,7 +167,7 @@ export async function main() {
// NOTE: We are creating data loader with a few sources
const loader = createLoader({ Assets, Markets, Exchanges });

const server = http.createServer(
const server = createServer(
createHandler({
context: { loader },
schema: application.schema,
Expand Down
50 changes: 29 additions & 21 deletions www/docs/relation.mdx
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
TODO: Link to type resolving
TODO: Mention it's a part of graphql plugin of Backstage
TODO: Link to collections
TODO: Add context about Entity and EntityRef stuff

`@relation` directive allows you to resolve relationships between
entities. Similar to `@field` directive, it writes a resolver for you
so you do not have to write a resolver yourself. It assumes that
relationships are defined as standard `Entity` relationships. The
`name` argument allows you to specify the type of the relationship. It
will automatically look up the entity in the catalog.

1. To define a `User` that is the `owner` of a `Component`:
`@relation` directive is a special case of [`@resolve`](./resolve)
provided by [Backstage Catalog GraphQL plugin][catalog-module]. The
structure of [`Entities`](https://backstage.io/docs/features/software-catalog/references)
doesn't allow to use `@resolve` directive. Catalog model of Backstage
is a graph of entities, where each entity can have relationships with
other entities. Those relationships are defined as a list of references
in [`relations`][relations] field of an entity. If you want to start using
Backstage Catalog GraphQL plugin, check out [Backstage](./backstage) section.
In compare to `@resolve` directive you don't need to create a data loader,
Backstage Catalog GraphQL plugin already has it.

## Usage

Each relationship in entity's `relations` list has a `type` of relationship
and a `targetRef` to another entity. `@relation` directive allows you to
specify a `name` of the relationship type

```graphql
type Component {
owner: User @relation(name: "ownedBy")
}
```

2. The GraphQL server has baked in support for [Relay][relay]. By
default, collections defined by a `@relation` directive are modeled as
arrays. However, if the relationship is large, and should be
paginated, you can specify it with `Connection` as the field type and
use the `nodeType` argument to specify what the target of the
collection should be.
If there are more than one relationship with `ownedBy` type, the first
entity in the list will be returned. If you plan to get multiple entities
you can use array type or `Connection` type. With `Connection` type you have to
specify `nodeType` argument to specify to which type nodes should be resolved.
More about `Connection` type you can read in [Collections](./collections) section.

```graphql
type Repository {
Expand All @@ -34,8 +36,8 @@ type Repository {
}
```

3. If you have different kinds of relationships with the same type you
can filter them by `kind` argument:
If you have different kinds of entities with the same relationship type you
can filter them by `kind` argument:

```graphql
type System {
Expand All @@ -45,3 +47,9 @@ type System {
@relation(name: "hasPart", nodeType: "Resource", kind: "resource")
}
```

> Note: For now `Catalog` loader doesn't support query arguments for filtering,
but in the future it will.

[catalog-module]: https://github.com/thefrontside/playhouse/blob/main/plugins/graphql-backend-module-catalog
[relations]: https://backstage.io/docs/features/software-catalog/descriptor-format#common-to-all-kinds-relations
140 changes: 112 additions & 28 deletions www/docs/resolve.mdx
Original file line number Diff line number Diff line change
@@ -1,19 +1,109 @@
TODO: Mention `__source` field and link to `@discriminates`
TODO: Link to collections

The `@resolve` directive is similar to the [`@field`](./field) directive, but instead of
resolving a field to primitive from the runtime data, it resolves to a GraphQL Node.

### Resolve nodes from runtime data

In following example we have a `Component` type that has a `owner` field.
We tell HydraphQL to take a value of `ownerId` field and resolve it to a `Owner` node
using the same data source used for resolving `Component`.

```graphql
type Component {
owner: Owner @resolve(at: "ownerId")
}
```

If for resolving `Owner` node we have to use a different data source, we can specify it
using `from` argument:

```graphql
type Component {
owner: Owner @resolve(at: "ownerId", from: "OwnersAPI")
}
```

### Managing multiple data sources

With multiple data sources you might want to resolve types accroding to the source name.
HydraphQL data loader adds a `__source` field to every node that contains the name of
the data source that was used to resolve the node. You can use this field to
[discriminates](./discriminates) base interface and then resolve to a specific type.

```graphql
extend interface Node
@discriminates(with: "__source")
@discriminationAlias(value: "ComponentsAPI", type: "Component")
@discriminationAlias(value: "OwnersAPI", type: "Owner") {}
```

### Resolving collection of nodes

In some cases a value of a field refers to a collection of nodes. For example, a `Project`
runtime data might have a `spec.projectId` field by which can be resolved a list of tasks
for that project.

```graphql
type Project {
tasks: [Task!] @resolve(at: "spec.projectId", from: "ExampleCom")
}
```

If there are a lot of nodes in the collection, more than 20, for example, it might be
impropriet to resolve all of them at once. In this case you can use `Connection` interface
for the field type, but you have to describe node type in `nodeType` argument. HydraphQL
will generate a specific connection type for the field and handles type resolving for you.
Check out [collections](./collections) section for more details.

```graphql
type Project {
tasks: Connection! @resolve(at: "spec.projectId", from: "ExampleCom", nodeType: "Task")
}
```

So the final schema will look like this:

```graphql
type Project {
tasks(after: String, before: String, first: Int, last: Int): TaskConnection!
}

# These types are generated by HydraphQL
type TaskConnection implements Connection {
count: Int
edges: [TaskEdge!]!
pageInfo: PageInfo!
}

type TaskEdge implements Edge {
cursor: String!
node: Task!
}
```

### Resolving nodes from runtime data with arguments

One of the cool features of HydraphQL is that if your data source accepts arguments, you
can pass them with a GraphQL query. For example, if we want to filter tasks by `status`.

```graphql
type Project {
tasks(status: TaskStatus): Connection @resolve(at: "spec.projectId", from: "ExampleCom", nodeType: "Task")
}
```

### Creating a data loader

To start using `@resolve` directive you need to create a data loader for your API with
`createLoader` function and then make it available in GraphQL context. You can check
out how to do that for [your GraphQL server](./server). If you are interested in how
resolving process works checkout [type resolving](./resolving) section.

```ts
```typescript
import querystring from "querystring";
import { createLoader, NodeQuery } from "@frontside/hydraphql";

export const loader = createLoader({
async ExampleCom(queries: NodeQuery[]) {
async ExampleCom(queries: readonly NodeQuery[]) {
return Promise.all(
queries.map(async ({ ref, args }) => {
const response = await fetch(
Expand All @@ -26,36 +116,30 @@ export const loader = createLoader({
});
```

### Resolve nodes from runtime data
To understand what's happening here you need to know how [`DataLoader`](https://github.com/graphql/dataloader)
works. Basically, it batches all requests to the same data source and then resolves
them in one go. That's why `ExampleCom` function accepts an array of queries.
Each query has two fields

In following example we have a `Component` type that has a `ownerId` field.
We tell HydraphQL to take a value of `ownerId` field and resolve it to a `Owner` node
using the same data source used for resolving `Component`.

```graphql
type Component {
owner: Owner @resolve(at: "ownerId")
```typescript
interface NodeQuery {
ref?: string;
args?: Record<string, unknown>;
}
```

If for resolving `Owner` node we have to use a different data source, we can specify it
using `from` argument:
- `ref` contains a value from runtime data of a field specified in `at` argument of
`@resolve` directive (in our case from above it's a value at `spec.projectId`)
- `args` contains arguments passed to a GraphQL

```graphql
type Component {
owner: Owner @resolve(at: "ownerId", from: "OwnersAPI")
}
```

How to add a new data source to your application you can read in [Data Loaders](#data-loaders) section.
### Quering data without any references

1. To achieve that first of all you need to create a DataLoader with `createLoader` function


2. Then you can use the `@resolve` directive with specifying the loader name of your API:
We can go futher and use `@resolve` directive omitting `at` argument. It opens
a possibility for combinning multiple APIs under one GraphQL endpoint.

```graphql
type Project {
tasks: [Task!] @resolve(at: "spec.projectId", from: "ExampleCom")
type Query {
projects(filter: ProjectsFilter): Connection @resolve(from: "ProjectAPI", nodeType: "Project")
tasks(filter: TasksFilter): Connection @resolve(from: "TaskAPI", nodeType: "Task")
}
```
Loading

0 comments on commit fd512f8

Please sign in to comment.