«GraphQL — это язык запросов для API и среда выполнения для выполнения этих запросов с вашими существующими данными. GraphQL предоставляет полное и понятное описание данных в вашем API, дает клиентам возможность запрашивать именно то, что им нужно, и ничего более, упрощает разработку API со временем и предоставляет мощные инструменты для разработчиков ».
Любой, кто создал службы REST , которые используются несколькими потребителями, например другие службы, веб-сайты или мобильные устройства, будет знать, что очень трудно создать идеальную конечную точку, которая удовлетворяет всем потребностям. Вы, как правило, в конечном итоге с вариантами одного и того же сервиса, для всех этих особых случаев 🙂
Теперь мы все знаем, что мы должны просто использовать HATEOAS … и это было в моем списке TODO (обещаю!), Пока я не наткнулся на GraphQL .
Итак, в этом сообщении в блоге я объясню, как вы можете без особых усилий добавить GraphQL в существующее приложение JAX-RS .
Пример проекта
Пример проекта доступен на Github , и его очень легко начать
|
1
2
3
|
git clone https://github.com/phillip-kruger/membership.gitcd membershipmvn clean install |
Это запустит фиджара-демона-роя с примером приложения http: // localhost: 8080 / members /
Высокий уровень
Примером является базовая служба членства, где вы можете получить всех участников или конкретного участника. Вы можете добавлять, редактировать и удалять участников.
Приложение является типичным JAX-RS, CDI, EJB, JPA, приложением Java EE для проверки Bean-компонентов, и мы добавляем новую конечную точку GraphQL.
Часть GraphQL использует следующие библиотеки:
Единственные java-классы, которые я добавил, чтобы представить мой существующий JAX-RS как GraphQL:
- MembershipGraphQLListener — зарегистрировать прослушиватель сервлета «/ graphql».
- MembershipGraphQLApi — конечная точка GraphQL. Просто обернуть существующий сервис
@Stateless. - MembershipErrorHandler — для обработки исключений.
Используя аннотации из graphQL-spqr , класс MembershipGraphQLApi действительно просто описывает и оборачивает существующий сервис @Stateless :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
@RequestScoped public class MembershipGraphQLApi { @Inject private MembershipService membershipService; // ... @GraphQLQuery(name = "memberships") public List<Membership> getAllMemberships(Optional<MembershipFilter> filter, @GraphQLArgument(name = "skip") Optional<Integer> skip, @GraphQLArgument(name = "first") Optional<Integer> first) { return membershipService.getAllMemberships(filter, skip, first); } // ... } |
Я надеюсь — у нас скоро будет JAX-QL (или что-то еще) как часть Java EE (или Jakarta EE, или MicroProfile), чтобы сделать это еще проще !!
Сначала немного ОТДЫХА
Я использую MicroProfile OpenAPI и Swagger UI для создания определений Open API для конечной точки REST.
Вы можете протестировать некоторые запросы, используя http: // localhost: 8080 / members / rest / openapi-ui /
Пример — Получение всех членств:
GET http://localhost:8080/membership/rest
Это вернет:
|
01
02
03
04
05
06
07
08
09
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
|
[ { "membershipId": 1, "owner": { "id": 1, "names": [ "Natus", "Phillip" ], "surname": "Kruger" }, "type": "FULL" }, { "membershipId": 2, "owner": { "id": 2, "names": [ "Charmaine", "Juliet" ], "surname": "Kruger" }, "type": "FULL" }, { "membershipId": 3, "owner": { "id": 3, "names": [ "Koos" ], "surname": "van der Merwe" }, "type": "FULL" }, { "membershipId": 4, "owner": { "id": 4, "names": [ "Minki" ], "surname": "van der Westhuizen" }, "type": "FREE" } ] |
Пример — получение определенного членства (1):
GET http://localhost:8080/membership/rest/1
Это вернет:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
{ "membershipId": 1, "owner": { "id": 1, "names": [ "Natus", "Phillip" ], "surname": "Kruger" }, "type": "FULL" } |
Теперь давайте посмотрим на GraphQL
Приложение включает в себя пользовательский интерфейс GraphiQL (как веб-jar), который позволяет легко тестировать некоторые запросы GraphQL
Вы можете протестировать некоторые запросы, используя http: // localhost: 8080 / members / graph / graphiql /
Итак, давайте посмотрим, выполнил ли GraphQL обещание «Нет больше перегрузок и недопоставок».
Получить все членство и все поля (так же, как REST получить все)
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
query Memberships { memberships{ ...fullMembership } } fragment fullMembership on Membership { membershipId owner{ ...owner } type } fragment owner on Person { id names surname } |
Это вернет все значения, однако теперь легко определить, какие поля должны быть включены …
Получить все членство, но только включить поле id
|
1
2
3
4
5
6
7
8
9
|
query Memberships { memberships{ ...membershipIdentifiers } } fragment membershipIdentifiers on Membership { membershipId } |
Полученная полезная нагрузка теперь намного меньше:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
{ "data": { "memberships": [ { "membershipId": 1 }, { "membershipId": 2 }, { "membershipId": 3 }, { "membershipId": 4 } ] } } |
Теперь давайте получим только определенные типы членства (так что получайте все БЕСПЛАТНЫЕ членства)
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
query FilteredMemberships { memberships(filter:{ type:FREE }){ ...fullMembership } } fragment fullMembership on Membership { membershipId owner{ ...owner } type } fragment owner on Person { id names surname } |
Это вернет только бесплатное членство. Здорово !
Или, что еще лучше, все фамилии участников начинаются с «Kru»
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
query FilteredMemberships { memberships(filter:{ surnameContains: "Kru" }){ ...fullMembership } } fragment fullMembership on Membership { membershipId owner{ ...owner } type } fragment owner on Person { id names surname } |
Большой !! Мы нашли двух человек:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
{ "data": { "memberships": [ { "membershipId": 1, "owner": { "id": 1, "names": [ "Natus", "Phillip" ], "surname": "Kruger" }, "type": "FULL" }, { "membershipId": 2, "owner": { "id": 2, "names": [ "Charmaine", "Juliet" ], "surname": "Kruger" }, "type": "FULL" } ] } } |
Получение определенного членства, используя переменную на клиенте:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
query Membership($id:Int!) { membership(membershipId:$id){ ...fullMembership } } fragment fullMembership on Membership { membershipId owner{ ...owner } type } fragment owner on Person { id names surname } |
Переменная:
|
1
|
{"id":1} |
Включить поля при определенном условии:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
query Membership($id:Int!,$withOwner: Boolean!) { membership(membershipId:$id){ ...fullMembership } } fragment fullMembership on Membership { membershipId owner @include(if: $withOwner){ ...owner } type } fragment owner on Person { id names surname } |
Переменная:
|
1
|
{"id":1,"withOwner": false} |
это исключит владельца (верно включить):
|
1
2
3
4
5
6
7
8
|
{ "data": { "membership": { "membershipId": 1, "type": "FULL" } } } |
пагинация
Давайте используем запрос get all, но разбиваем на страницы.
|
01
02
03
04
05
06
07
08
09
10
11
12
|
query Memberships($itemsPerPage:Int!,$pageNumber:Int!) { memberships( first:$itemsPerPage, skip:$pageNumber) { membershipId owner{ names surname } type } } |
Переменная:
|
1
|
{"itemsPerPage": 2,"pageNumber": 1} |
Это вернет первые 2 результата, а затем вы можете выполнить пейджинг, увеличив значение «pageNumber».
Мутации
Создайте
|
1
2
3
4
5
|
mutation CreateMember { createMembership(membership: {type:FULL,owner: {names: "James",surname:"Small"}}) { membershipId } } |
Это создаст новое членство и вернет идентификатор.
Обновить
|
1
2
3
4
5
|
mutation EditMember($membership: MembershipInput!) { createMembership(membership:$membership) { membershipId } } |
Переменная:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
{ "membership": { "membershipId": 2, "owner": { "names": [ "Charmaine", "Juliet" ], "surname": "Krüger" }, "type": "FULL" } } |
(добавлен умляут на Крюгера, теперь это должен быть Крюгер)
Удалить
|
1
2
3
4
5
|
mutation DeleteMembership($id:Int!){ deleteMembership(membershipId:$id){ membershipId } } |
Переменная:
|
1
|
{"id":1} |
Это удалит членство 1.
Исключение.
MembershipErrorHandler преобразует исключение ConstraintViolationException (которое выдается при сбое проверки компонента) и создает приятное сообщение об ошибке для GraphQL.
Итак, давайте попробуем создать участника с фамилией всего в одну букву.
|
1
2
3
4
5
|
mutation CreateMember($membership: MembershipInput!) { createMembership(membership:$membership) { membershipId } } |
Переменная:
|
1
2
3
4
5
6
7
8
9
|
{ "membership": { "owner": { "names": "Christina", "surname": "S" }, "type": "FULL" } } |
Это вернет сообщение об ошибке проверки бина:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
{ "data": { "createMembership": null }, "errors": [ { "message": "Surname 'S' is too short, minimum 2 characters", "path": null, "extensions": null } ] } |
Если вы посмотрите на человека POJO:
|
1
2
3
|
@NotNull(message = "Surname can not be empty") @Size(min=2, message = "Surname '${validatedValue}' is too short, minimum {min} characters") private String surname; |
интроспекция
Еще одна приятная вещь в GraphQL состоит в том, что у него есть система схем и типов, которую вы можете запросить:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
{ __schema { queryType { name fields { name } } mutationType{ name fields{ name } } subscriptionType { name fields{ name } } } } |
Выше будут описаны запросы и мутации, доступные в этой конечной точке.
Вы также можете описать свои модели:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
{ __type(name: "Membership") { name kind fields { name args { name } } } } |
Резюме
В этом примере мы не удалили REST, а просто добавили GraphQL как альтернативный вариант для потребителя.
К настоящему времени должно быть ясно, что у клиента гораздо больше возможностей фильтровать и запрашивать данные именно так, как им это нужно. Все это без необходимости выполнять дополнительную работу на сервере. Это учитывает быстрые итерации продукта на стороне клиента.
Полезная нагрузка по проводам оптимизирована, и мы экономим пропускную способность!
Опять же, я надеюсь — у нас скоро будет JAX-QL (или что-то еще) как часть Java EE (или Jakarta EE, или MicroProfile), чтобы сделать это еще проще!
| Опубликовано на Java Code Geeks с разрешения Филиппа Крюгера, партнера нашей программы JCG . Смотреть оригинальную статью здесь: GraphQL о рою Wildfly
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |


