Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/vladimir-dejanovic/graphql-to-rescue
graphql-to-rescue
https://github.com/vladimir-dejanovic/graphql-to-rescue
graphql-api graphql-server java rest spring-boot
Last synced: 13 days ago
JSON representation
graphql-to-rescue
- Host: GitHub
- URL: https://github.com/vladimir-dejanovic/graphql-to-rescue
- Owner: vladimir-dejanovic
- License: gpl-3.0
- Created: 2018-12-01T19:21:28.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2022-09-16T21:07:13.000Z (over 2 years ago)
- Last Synced: 2024-10-27T16:23:14.512Z (2 months ago)
- Topics: graphql-api, graphql-server, java, rest, spring-boot
- Language: Java
- Size: 43.9 KB
- Stars: 1
- Watchers: 2
- Forks: 0
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# GraphQL to rescue
this is code example for my talk *What limitations and problems of REST API can be solved by GraphQL*
## Environment on my machine :)
- **Apache Maven 3.5.2**
- **Java version: 11.0.2, vendor: AdoptOpenJDK**## Issue 1 - No spec & documentation not up to date
Let us open code in init directory
We have no spec or documentation, so only thing we have is code.
Let us investigate what we have here.
### Open pom
We have here Spring Boot app with dependencies
- Web
- JPA/H2### Find controller
- here we see all end points that we expose over REST API and how response look like if we follow code
- also we can see the full structure of code, since code isn't that big.In case Code base is much bigger it wouldn't be so easy or fun to go this way.
### Solution to Issue 1
- add dependencies for graphql (uncomment them from pom.xml)
```
com.graphql-java
graphql-java
11.0com.graphql-java
graphql-java-tools
5.2.4com.graphql-java
graphql-spring-boot-starter
5.0.2com.graphql-java
graphiql-spring-boot-starter
5.0.2```
Add schema to *schema.graphqls*```
type Attendee {
id: ID!
name: String!
}type Speaker {
id: ID!
name: String!
twitter: String
}type Talk {
id: ID!
# this is comment for title
title: String!
description: String
}type Query {
allTalks: [Talk]
allSpeakers: [Speaker]
allAttendees: [Attendee]
allTalksForSpeaker(speakerId: Int) : [Talk]
}schema {
query: Query
}
```Add Query resolver
```
package xyz.itshark.conf.talk.graphqltorescue.graphql;import com.coxautodev.graphql.tools.GraphQLQueryResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import xyz.itshark.conf.talk.graphqltorescue.pojo.Attendee;
import xyz.itshark.conf.talk.graphqltorescue.pojo.Speaker;
import xyz.itshark.conf.talk.graphqltorescue.pojo.Talk;
import xyz.itshark.conf.talk.graphqltorescue.service.AttendeeService;
import xyz.itshark.conf.talk.graphqltorescue.service.SpeakerService;
import xyz.itshark.conf.talk.graphqltorescue.service.TalkService;import java.util.List;
@Component
public class Query implements GraphQLQueryResolver {@Autowired
TalkService talkService;@Autowired
SpeakerService speakerService;@Autowired
AttendeeService attendeeService;public List allTalks() {
return talkService.findAll();
}public List allSpeakers() {
return speakerService.findAll();
}public List allAttendees() {
return attendeeService.findAll();
}public List allTalksForSpeaker(Long speakerId) {
return talkService.findAllTalksBySpeakerId(speakerId);
}}
```
Let us build the code and check resultIn browser hit http://localhost:8080/graphiql
request all talks
```
query {
allTalks {
id
title
description
}
}
```request all speakers
```
query {
allSpeakers {
id
name
}
}
```request all attendees
```
query {
allAttendees {
id
name
}
}
```request all talks for speaker
```
query {
allTalksForSpeaker(speakerId:1) {
id
title
description
}
}
```- There is validation happening of all input queries and responses, they need to match schema, so there is no wayt that code and schema are out of sync.
- Schema is mandatory so it has to be present.
- Schema is in the same time documentation, so it is also always present.End result of code can be found in **graphql-0.1** directory.
## Issue 2 - client & server not in sync
In case of GraphQL this isn't the issue due to specification.
On init connection client will get spec from server and will not event sent incorrect requests according to specification.## Issue 3 - Over fetching fetching
Solved by default by GraphQL
```
query {
allTalks {
id
title
}
}
```## Issue 4 - Under fetching
In case we want to show Speaker details and also details about speaker talks, we need to make multiple calls to backend or we need to add new entry point that would return this specific combination. With GraphQL we can solve this in an easy way.
### Update graphql schema
```
type Speaker {
id: ID!
name: String!
twitter: String
talks: [Talk]
}
```### update java code
Let us add new resolver
```
package xyz.itshark.conf.talk.graphqltorescue.graphql;import com.coxautodev.graphql.tools.GraphQLResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import xyz.itshark.conf.talk.graphqltorescue.pojo.Speaker;
import xyz.itshark.conf.talk.graphqltorescue.pojo.Talk;
import xyz.itshark.conf.talk.graphqltorescue.service.TalkService;import java.util.List;
@Component
public class SpeakerResolver implements GraphQLResolver {@Autowired
TalkService talkService;public List talks(Speaker speaker) {
return talkService.findAllTalksBySpeaker(speaker);
}
}
```and now we can have query like
```
query {
allSpeakers {
name
talks {
title
description
}
}
}
```End result of code can be found in **graphql-0.2** directory.
## Issue 5 - naming conventions
Automatically solved by GraphQL
```
query {
speakers:allSpeakers {
speaker_name:name
talks {
title
desc:description
}
}
}
```## Issue 6 - different type of data in same request
### update GraphQL Schema
```
union Any = Speaker | Talktype Query {
allTalks: [Talk]
allSpeakers: [Speaker]
allAttendees: [Attendee]
allTalksForSpeaker(speakerId: Int) : [Talk]
allAny: [Any]
}
```### Update java
```
@Component
public class Query implements GraphQLQueryResolver {....
public List allAny() {
List list = speakerService.findAll();
List list2 = talkService.findAll();list.addAll(list2);
return list;
}}
```### Test solution
```
query {
allAny {
... on Speaker {
name
}
... on Talk {
title
}
}
}
```we can also use fragments in query
```
query {
allAny {
... on Speaker {
name
}
...testFragment
}
}fragment testFragment on Talk {
talk_title:title
}
```if we need info about type we can get it in this way
```
query {
allAny {
__typename
... on Speaker {
name
}
...testFragment
}
}fragment testFragment on Talk {
talk_title:title
}
```End result of code can be found in **graphql-0.3** directory.
## Issue 7 - sharing of similar data between resources
### update GraphQL Schema
```
interface Human {
name: String!
}type Attendee implements Human {
id: ID!
name: String!
}type Speaker implements Human {
id: ID!
name: String!
twitter: String
talks: [Talk]
}type Query {
allTalks: [Talk]
allSpeakers: [Speaker]
allAttendees: [Attendee]
allTalksForSpeaker(speakerId: Int) : [Talk]
allAny: [Any]
allHumans: [Human]
}```
### Update Java
We can add interface if we want to
```
package xyz.itshark.conf.talk.graphqltorescue.pojo;public interface Human {
public String getName();
}
``````
public class Attendee implements Human{
...
}
``````
public class Speaker implements Human {
...
}
``````
@Component
public class Query implements GraphQLQueryResolver {...
public List allHumans() {
List list1 = speakerService.findAll();
List list2 = attendeeService.findAll();list1.addAll(list2);
return list1;
}}
```### Test solution
```
query {
allHumans {
__typename
name
... on Speaker {
}
... on Attendee {
id
}
}
}
```End result of code can be found in **graphql-0.4** directory.
## Issue 8 - Status Code/Error
Solved by specification
## Issue 9 - Streaming data
### update GraphQL Schema
```
type Score {
talk: String
score: Int
}type Subscription {
scoreForTalk(talk: String): Score
}schema {
query: Query
subscription: Subscription
}
```### Update Java Code
update pom.xml with new dependency
```
io.reactivex.rxjava2
rxjava
2.2.3
```Add pojo for Score
```
import lombok.Data;@Data
public class Score {private String talk;
private int score;
}
```Add subscription resolver
```
package xyz.itshark.conf.talk.graphqltorescue.graphql;import com.coxautodev.graphql.tools.GraphQLSubscriptionResolver;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Observable;
import io.reactivex.observables.ConnectableObservable;
import org.reactivestreams.Publisher;
import org.springframework.stereotype.Component;
import xyz.itshark.conf.talk.graphqltorescue.pojo.Score;import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;@Component
public class Subscription implements GraphQLSubscriptionResolver {public Publisher scoreForTalk(String talk) {
Observable observable = Observable.create( e -> {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleAtFixedRate(() -> {
Score score = new Score();
score.setTalk(talk);
score.setScore((int) Math.floor(Math.random()*10));
e.onNext(score);
}, 0, 2, TimeUnit.SECONDS);
});ConnectableObservable connectableObservable = observable.share().publish();
connectableObservable.connect();
return connectableObservable.toFlowable(BackpressureStrategy.BUFFER);
}}
```### Test Solution
hit http://localhost:8080/graphiql
```
subscription {
scoreForTalk(talk: "my talk") {
talk
score
}
}
```End result of code can be found in **graphql-0.5** directory.