«GraphQL — это язык запросов для API и среда выполнения для выполнения этих запросов с вашими существующими данными. GraphQL предоставляет полное и понятное описание данных в вашем API, дает клиентам возможность запрашивать именно то, что им нужно, и ничего более, упрощает разработку API со временем и предоставляет мощные инструменты для разработчиков ».
Любой, кто создал службы REST , которые используются несколькими потребителями, например другие службы, веб-сайты или мобильные устройства, будет знать, что очень трудно создать идеальную конечную точку, которая удовлетворяет всем потребностям. Вы, как правило, в конечном итоге с вариантами одного и того же сервиса, для всех этих особых случаев 🙂
Теперь мы все знаем, что мы должны просто использовать HATEOAS … и это было в моем списке TODO (обещаю!), Пока я не наткнулся на GraphQL .
Итак, в этом сообщении в блоге я объясню, как вы можете без особых усилий добавить GraphQL в существующее приложение JAX-RS .
Пример проекта
Пример проекта доступен на Github , и его очень легко начать
1
2
3
|
git clone https: //github.com/phillip-kruger/membership.git cd membership mvn 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, являются их собственными. |