Una API REST no ofrece introspección y por tanto hay que recurrir a un sistema de documentación que puede estar desactualizado y hay que mantener para conocer como usar la API y cuales son sus tipos y parámetros. Por el contrario GraphQL incorpora un sistema de introspección que permite conocer sus tipos y campos, a través del editor GraphiQL o si fuese necesario de forma automatizada con código.
Una de las cosas que me gustan de GraphQL sobre REST es que la API de un servicio se define en un esquema. Tanto las operaciones de consulta, de modificación con sus nombres de parámetros, tipos y si son requeridos o no. Esta información es básica para hacer un buen uso de esa API y conocer cual es su contrato. Además con la herramienta GraphiQL se pueden crear y realizar consultas con un pequeño IDE con asistencia de código. GraphQL genera los metadatos e ofrece la instrospección a partir únicamente de la definición del esquema del servicio sin ningún esfuerzo adicional por parte del creador del servicio.
En el ejemplo de esta serie de artículos sobre GraphQL he usado el siguiente esquema que utiliza como modelo de datos el de una librería.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
scalar Long
scalar LocalDate
type Book {
id: Long
title: String
author: Author
isbn: String
date: LocalDate
comments(after: String, limit: Long): CommentsConnection
batchedIsbn: String
batchedComments(after: String, limit: Long): CommentsConnection
}
type Magazine {
id: Long
name: String
pages: Long
}
type Comment {
id: Long
text: String
}
type Author {
id: Long
name: String
}
input BookFilter {
title: String
}
type CommentsConnection {
edges: [CommentEdge]
pageInfo: PageInfo
}
type CommentEdge {
node: Comment
cursor: String
}
type PageInfo {
startCursor: String
endCursor: String
hasNextPage: Boolean
}
union Publication = Book | Magazine
type Query {
books(filter: BookFilter): [Book]!
publications: [Publication]!
book(id: Long): Book!
authors: [Author]!
author(id: Long): Author!
}
type Mutation {
addBook(title: String, author: Long): Book
}
schema {
query: Query
mutation: Mutation
}
|
library.graphqls
Si no se conoce el esquema qué operaciones, tipos y nombres ofrece la API GraphQL permite introspección y con únicamente el endpoint se puede averiguar esta información.
Por ejemplo, con la siguiente consulta se puede conocer qué tipos contiene el esquema de una API. Los que comienzan con dos barras bajas o __ son tipos parte del sistema de introspección. Entre los que están el que representa un libro y autor pero también está Query que es un punto de acceso a la API.
1
2
3
4
5
6
7
|
{
__schema {
types {
name
}
}
}
|
types.query
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
{
"data": {
"__schema": {
"types": [
{
"name": "Author"
},
{
"name": "Book"
},
{
"name": "BookFilter"
},
{
"name": "Boolean"
},
{
"name": "Comment"
},
{
"name": "CommentEdge"
},
{
"name": "CommentsConnection"
},
{
"name": "LocalDate"
},
{
"name": "Long"
},
{
"name": "Magazine"
},
{
"name": "Mutation"
},
{
"name": "PageInfo"
},
{
"name": "Publication"
},
{
"name": "Query"
},
{
"name": "String"
},
{
"name": "__Directive"
},
{
"name": "__DirectiveLocation"
},
{
"name": "__EnumValue"
},
{
"name": "__Field"
},
{
"name": "__InputValue"
},
{
"name": "__Schema"
},
{
"name": "__Type"
},
{
"name": "__TypeKind"
}
]
}
}
}
|
types.out
Conocer el tipo de las consultas de lectura y que consultas se pueden realizar inspeccionando el tipo Query.
1
2
3
4
5
6
7
|
{
__schema {
queryType {
name
}
}
}
|
query-type.query
1
2
3
4
5
6
7
8
|
{
__type(name: "Query") {
name
fields {
name
}
}
}
|
query-type-info.query
1
2
3
4
5
6
7
8
9
|
{
"data": {
"__schema": {
"queryType": {
"name": "Query"
}
}
}
}
|
query-type.out
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
{
"data": {
"__type": {
"name": "Query",
"fields": [
{
"name": "author"
},
{
"name": "authors"
},
{
"name": "book"
},
{
"name": "books"
},
{
"name": "publications"
}
]
}
}
}
|
query-type-info.out
Por la propiedad de GraphQL de que se pueden realizar varias consultas en una única petición se pueden obtener ambos resultados a la vez.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{
__schema {
queryType {
name
}
}
__type(name: "Query") {
name
fields {
name
}
}
}
|
queries.query
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
{
"data": {
"__schema": {
"queryType": {
"name": "Query"
}
},
"__type": {
"name": "Query",
"fields": [
{
"name": "author"
},
{
"name": "authors"
},
{
"name": "book"
},
{
"name": "books"
},
{
"name": "publications"
}
]
}
}
}
|
queries.out
Se puede obtener más en detalle los campos que contiene un tipo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
{
book: __type(name: "Book") {
name
fields {
name
type {
name
kind
}
}
}
author: __type(name: "Author") {
name
fields {
name
type {
name
kind
}
}
}
}
|
types-info.query
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
{
"data": {
"book": {
"name": "Book",
"fields": [
{
"name": "author",
"type": {
"name": "Author",
"kind": "OBJECT"
}
},
{
"name": "batchedComments",
"type": {
"name": "CommentsConnection",
"kind": "OBJECT"
}
},
{
"name": "batchedIsbn",
"type": {
"name": "String",
"kind": "SCALAR"
}
},
{
"name": "comments",
"type": {
"name": "CommentsConnection",
"kind": "OBJECT"
}
},
{
"name": "date",
"type": {
"name": "LocalDate",
"kind": "SCALAR"
}
},
{
"name": "id",
"type": {
"name": "Long",
"kind": "SCALAR"
}
},
{
"name": "isbn",
"type": {
"name": "String",
"kind": "SCALAR"
}
},
{
"name": "title",
"type": {
"name": "String",
"kind": "SCALAR"
}
}
]
},
"author": {
"name": "Author",
"fields": [
{
"name": "id",
"type": {
"name": "Long",
"kind": "SCALAR"
}
},
{
"name": "name",
"type": {
"name": "String",
"kind": "SCALAR"
}
}
]
}
}
}
|
types-info.out
Incluso se puede inspeccionar los tipos del sistema de instrospección. Con las descripciones de los campos o parámetros de entrada si los tuviese.
1
2
3
4
5
6
7
8
9
10
11
12
|
{
__type(name: "__Type") {
name
fields {
name
type {
name
kind
}
}
}
}
|
type-type.query
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
{
"data": {
"__type": {
"name": "__Type",
"fields": [
{
"name": "description",
"type": {
"name": "String",
"kind": "SCALAR"
}
},
{
"name": "enumValues",
"type": {
"name": null,
"kind": "LIST"
}
},
{
"name": "fields",
"type": {
"name": null,
"kind": "LIST"
}
},
{
"name": "inputFields",
"type": {
"name": null,
"kind": "LIST"
}
},
{
"name": "interfaces",
"type": {
"name": null,
"kind": "LIST"
}
},
{
"name": "kind",
"type": {
"name": null,
"kind": "NON_NULL"
}
},
{
"name": "name",
"type": {
"name": "String",
"kind": "SCALAR"
}
},
{
"name": "ofType",
"type": {
"name": "__Type",
"kind": "OBJECT"
}
},
{
"name": "possibleTypes",
"type": {
"name": null,
"kind": "LIST"
}
}
]
}
}
}
|
type-type.out
Conocer cuales son los campos de un tipo puede utilizarse para validar una API, comprobando que no se han eliminado campos necesarios. Es útil en el caso de querer automatizar esta validación de una API de GraphQL que se consuma ayudando a detectar de forma temprana problemas de compatibilidad al publicarse una nueva versión que no está bajo propiedad del que la usa.
El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run