-
Notifications
You must be signed in to change notification settings - Fork 0
Add article about graphql #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
igorlima
wants to merge
2
commits into
master
Choose a base branch
from
graphql-poc
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
320 changes: 320 additions & 0 deletions
320
Authors/Igor-Lima/01-graphql-proof-of-concept/readme.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,320 @@ | ||
# **Proof of Concept** for two different implementations of GraghQL server | ||
|
||
As we already know GraphQL is an alternative to REST endpoints for handling queries and database updates. And in this tech article I'll try to figure out which programming language, among [Node](https://github.com/igorlima/expressjs-graphql-server) and [Java](https://github.com/igorlima/spark-graphql-server), is way faster to handle GraphQL queries. Also, I will use different tools to measure the perfomance and document the process. | ||
|
||
Let's start comparing some code. The idea here is to make sure both implementations handle all requests using the same approach. For instance, the amount of calling to the database and so on. Bellow is the list of the source for each language: | ||
|
||
* NodeJS: https://github.com/igorlima/expressjs-graphql-server | ||
* JAVA: https://github.com/igorlima/spark-graphql-server | ||
|
||
We tried to keep both the implementations straightforward. | ||
|
||
## Server side code | ||
|
||
Let's take a quick look at on how things are implemented in each language. Notice that both languages follow the same approach. I mean the way it creates a GraphQL schema and so forth. | ||
|
||
#### __*JAVA*__ | ||
|
||
To create a GraphQL server in Java, firstly we need to define a [GraphQLObjectType](https://github.com/andimarek/graphql-java/blob/master/src/main/java/graphql/schema/GraphQLObjectType.java). A type of object that will be served through user requests. Here, we have a type `todo` with the following attributes: `id`, `title` and `completed`. | ||
|
||
```java | ||
// https://github.com/igorlima/spark-graphql-server/blob/master/src/main/java/com/mycompany/app/TodoSchema.java#L15 | ||
public static GraphQLObjectType todoType = newObject() | ||
.name("todoType") | ||
.field(newFieldDefinition() | ||
.type(GraphQLString) | ||
.name("id") | ||
.description("Todo id") | ||
.build()) | ||
.field(newFieldDefinition() | ||
.type(GraphQLString) | ||
.name("title") | ||
.description("Task title") | ||
.build()) | ||
.field(newFieldDefinition() | ||
.type(GraphQLBoolean) | ||
.name("completed") | ||
.description("Flag to mark if the task is completed") | ||
.build()) | ||
.build(); | ||
``` | ||
|
||
In addition, we have to define a *query type* by using the same class [GraphQLObjectType](https://github.com/andimarek/graphql-java/blob/master/src/main/java/graphql/schema/GraphQLObjectType.java). The query type below defines a simple type to resolve a list of todos. | ||
|
||
```java | ||
// https://github.com/igorlima/spark-graphql-server/blob/master/src/main/java/com/mycompany/app/TodoSchema.java | ||
// lines 34 and 42 | ||
static GraphQLObjectType queryType = newObject() | ||
.name("Todo") | ||
.field(newFieldDefinition() | ||
.type(new GraphQLList(todoType)) | ||
.name("todos") | ||
.description("todo tasks") | ||
.dataFetcher(TodoData.todoFetcher) | ||
.build()) | ||
.build(); | ||
``` | ||
|
||
Of course we need to fetch data, and it's done by `TodoData.todoFetcher`, what is an instance of [DataFetcher](https://github.com/andimarek/graphql-java/blob/master/src/main/java/graphql/schema/DataFetcher.java). | ||
|
||
```java | ||
// https://github.com/igorlima/spark-graphql-server/blob/master/src/main/java/com/mycompany/app/TodoData.java | ||
// lines 36 and 49 | ||
static DataFetcher todoFetcher = new DataFetcher() { | ||
@Override | ||
public Object get(DataFetchingEnvironment environment) { | ||
List<Map<String, Object>> todos = new ArrayList<Map<String, Object>>(); | ||
FindIterable<Document> iterable = collection.find(); | ||
iterable.forEach(new Block<Document>() { | ||
@Override | ||
public void apply(final Document document) { | ||
todos.add(converter(document)); | ||
} | ||
}); | ||
return todos; | ||
} | ||
}; | ||
``` | ||
|
||
Note that the data comes from `collection.find()`. A MongoDB collection. To create a [MongoDB connection in Java](https://docs.mongodb.org/getting-started/java/client/), we need to have a [MongoClientURI](https://api.mongodb.org/java/3.2/com/mongodb/MongoClientURI.html) to instantiate a [MongoClient](https://api.mongodb.org/java/3.2/com/mongodb/MongoClient.html). With that instance of MongoClient, we are ready to get a [MongoDatabase](https://api.mongodb.org/java/3.2/com/mongodb/client/MongoDatabase.html) and finally a [MongoCollection](https://api.mongodb.org/java/3.2/com/mongodb/client/MongoCollection.html). | ||
|
||
> **NOTE:** I’m sharing my credentials here. Feel free to use it while you’re learning. After that, create and use your own credential. Thanks. | ||
|
||
```java | ||
// https://github.com/igorlima/spark-graphql-server/blob/master/src/main/java/com/mycompany/app/TodoData.java#L23 | ||
MongoClientURI uri = new MongoClientURI("mongodb://example:example@candidate.54.mongolayer.com:10775,candidate.57.mongolayer.com:10128/spark-server-with-mongo?replicaSet=set-5647f7c9cd9e2855e00007fb"); | ||
|
||
MongoClient mongoClient = new MongoClient(uri); | ||
MongoDatabase db = mongoClient.getDatabase("spark-server-with-mongo"); | ||
MongoCollection<Document> collection = db.getCollection("todos"); | ||
``` | ||
|
||
Now, we have in place a connection, a query type, and the type of object `todo`. All of that allows us to define our GraphQL Schema in Java: | ||
|
||
```java | ||
// https://github.com/igorlima/spark-graphql-server/blob/master/src/main/java/com/mycompany/app/TodoSchema.java#L121 | ||
public static GraphQLSchema schema = GraphQLSchema.newSchema() | ||
.query(queryType) | ||
.build(); | ||
``` | ||
|
||
#### __*NodeJS*__ | ||
|
||
In the other side in Node, to create a GraphQL server, firstly we need to define a [GraphQLObjectType](https://github.com/graphql/graphql-js/blob/master/src/type/definition.js#L289). A type of object that will be served through user requests. Here, we have a type `todo` with the following attributes: `id`, `title` and `completed`. | ||
|
||
```js | ||
var TodoType = new GraphQLObjectType({ | ||
name: 'todo', | ||
fields: () => ({ | ||
id: { | ||
type: GraphQLID, | ||
description: 'Todo id' | ||
}, | ||
title: { | ||
type: GraphQLString, | ||
description: 'Task title' | ||
}, | ||
completed: { | ||
type: GraphQLBoolean, | ||
description: 'Flag to mark if the task is completed' | ||
} | ||
}) | ||
}) | ||
``` | ||
|
||
In addition, we have to define a *query type* by using the same object [GraphQLObjectType](https://github.com/andimarek/graphql-java/blob/master/src/main/java/graphql/schema/GraphQLObjectType.java). The query type below defines a simple type to resolve a list of todos. | ||
|
||
```js | ||
// https://github.com/igorlima/expressjs-graphql-server/blob/master/schema.js | ||
// lines 81 and 91 | ||
var QueryType = new GraphQLObjectType({ | ||
name: 'Query', | ||
fields: () => ({ | ||
todos: { | ||
type: new GraphQLList(TodoType), | ||
resolve: () => { | ||
return promiseListAll() | ||
} | ||
} | ||
}) | ||
}) | ||
``` | ||
|
||
Of course we need to fetch data, and it's done by `promiseListAll()`. A promise to get a list of data from `TODO.find(...)`, what is a MongoDB schema. | ||
|
||
```js | ||
// https://github.com/igorlima/expressjs-graphql-server/blob/master/schema.js | ||
// lines 72 and 79 | ||
var promiseListAll = () => { | ||
return new Promise((resolve, reject) => { | ||
TODO.find((err, todos) => { | ||
if (err) reject(err) | ||
else resolve(todos) | ||
}) | ||
}) | ||
} | ||
``` | ||
|
||
In order to create a [MongoDB connection in Node](http://mongoosejs.com/index.html), we need to define previously a [Schema](http://mongoosejs.com/docs/guide.html). Once defining a schema, it will get a collection with the given name in *lower case* and *plural*. In this case, the schema name is `'Todo'`, so the collection should be `'todos'`. Then we just need to connect to the database by utilizing the [`mongoose.connect()`](http://mongoosejs.com/docs/connections.html) method. | ||
|
||
> **NOTE:** I’m sharing my credentials here. Feel free to use it while you’re learning. After that, create and use your own credential. Thanks. | ||
|
||
```js | ||
// https://github.com/igorlima/expressjs-graphql-server/blob/master/schema.js#L12 | ||
// Mongoose Schema definition | ||
var TodoSchema = new Schema({ | ||
title: String, | ||
completed: Boolean | ||
}) | ||
var TODO = mongoose.model('Todo', TodoSchema) | ||
|
||
// https://github.com/igorlima/expressjs-graphql-server/blob/master/schema.js#L47 | ||
var COMPOSE_URI_DEFAULT = 'mongodb://example:example@candidate.54.mongolayer.com:10775,candidate.57.mongolayer.com:10128/spark-server-with-mongo?replicaSet=set-5647f7c9cd9e2855e00007fb' | ||
|
||
mongoose.connect(process.env.COMPOSE_URI || COMPOSE_URI_DEFAULT, function (error) { | ||
if (error) console.error(error) | ||
else console.log('mongo connected') | ||
}) | ||
``` | ||
|
||
Now, everything is in place to define our GraphQL Schema in NodeJS. A connection, a query type, and the type of object `todo`. | ||
|
||
```js | ||
// https://github.com/igorlima/expressjs-graphql-server/blob/master/schema.js#L279 | ||
module.exports = new GraphQLSchema({ | ||
query: QueryType | ||
}) | ||
``` | ||
|
||
## Client side code | ||
|
||
Our client side is a [Todo List](http://todomvc.com/examples/react/#/) written in React. You can choose among [many JS frameworks over there](http://todomvc.com/). As I'm working in a React project these days, so I chose React as the front-end framework. However, feel free to choose any other JavaScript framework you're comfortable with. See how's this Todo List looks like: | ||
|
||
 | ||
|
||
* Source code | ||
* JAVA: https://github.com/igorlima/spark-graphql-server/tree/gh-pages | ||
* NodeJS: https://github.com/igorlima/expressjs-graphql-server/tree/gh-pages | ||
* Live demo | ||
* JAVA: http://igorlima.github.io/spark-graphql-server/#/ | ||
* NodeJS: http://igorlima.github.io/expressjs-graphql-server/#/ | ||
|
||
The client-side code is exactly the same for both servers. Also, both GraphQL servers, in JAVA and Node, implement a type of query to fetch all `todos` as well as six different mutations: `add`, `toggle`, `toggleAll`, `destroy`, `clearCompleted` and `save`. All kind of queries needed by the client side example. | ||
|
||
|
||
## Metrics | ||
|
||
|
||
### Using the tool [webpagetest](https://www.npmjs.com/package/webpagetest) | ||
|
||
The time for rendering the assets (*html*, *css* and *js* files) can be disregarded. Because we have the same asset files hosted by [github page](https://pages.github.com/). So let's compare the time spent by calling the GraphQL server. | ||
|
||
NodeJS server | JAVA server | ||
------------- | ------------ | ||
 |  | ||
|
||
On the left side, there is the NodeJS server spending *311ms* plus *63ms* with a total of **374ms**. Against JAVA server with *316ms* plus *144ms*, a total of **460ms**. | ||
|
||
### Using the tool [loadtest](https://www.npmjs.com/package/loadtest) | ||
|
||
To run the *[loadtest](https://www.npmjs.com/package/loadtest)* we need to specify the amount of max request and the concurrency level. Respectively, these arguments are `-n` and `-c`. First test uses the following query to get all *todos*: | ||
|
||
```js | ||
query Todo { | ||
todos { | ||
id, | ||
title, | ||
completed | ||
} | ||
} | ||
``` | ||
|
||
The command line used was: | ||
|
||
```sh | ||
# For JAVA | ||
loadtest -c 10 -n 1000 -T application/graphql -P 'query Todo { todos {id, title, completed} }' -m POST 'http://spark-graphql-server.herokuapp.com/graphql' | ||
``` | ||
|
||
and | ||
|
||
```sh | ||
# For Node | ||
loadtest -c 10 -n 1000 -T application/graphql -P 'query Todo { todos {id, title, completed} }' -m POST 'http://expressjs-graphql-server.herokuapp.com/graphql' | ||
``` | ||
|
||
Here is the result of the first test. | ||
|
||
Metric | ExpressJS (NodeJS) | Spark (JAVA) | ||
-------|--------------------|------- | ||
Completed requests|1000|1000 | ||
Concurrency level|10|10 | ||
Total errors|0|0 | ||
Requests/second|29|29 | ||
|
||
Percentage of the requests served within a certain time | ExpressJS (NodeJS) | Spark (JAVA) | ||
----|--------------------|------------- | ||
50% | 342 ms | 333 ms | ||
90% | 363 ms | 360 ms | ||
95% | 370 ms | 383 ms | ||
99% | 495 ms | 908 ms | ||
100% (longest request)| 526 ms | 1028 ms | ||
|
||
----- | ||
|
||
The second test uses the following query to *toggle* a specific *todo*: | ||
|
||
```js | ||
mutation Todo { | ||
toggle(id: "569245602404f9e4145b2e02") { | ||
id, | ||
title, | ||
completed | ||
} | ||
} | ||
``` | ||
|
||
The command line used was: | ||
|
||
```sh | ||
# For JAVA | ||
loadtest -c 10 -n 1000 -T application/graphql -P 'mutation Todo { toggle(id: "569245602404f9e4145b2e02") { id, title, completed }}' -m POST 'http://spark-graphql-server.herokuapp.com/graphql' | ||
``` | ||
|
||
and | ||
|
||
```sh | ||
# For NodeJS | ||
loadtest -c 10 -n 1000 -T application/graphql -P 'mutation Todo { toggle(id: "569245602404f9e4145b2e02") { id, title, completed }}' -m POST 'http://expressjs-graphql-server.herokuapp.com/graphql' | ||
``` | ||
|
||
Here is the result of the second test. | ||
|
||
Metric | ExpressJS (NodeJS) | Spark (JAVA) | ||
-------|--------------------|------- | ||
Completed requests|1000|1000 | ||
Concurrency level|10|10 | ||
Total errors|0|0 | ||
Requests/second|28|29 | ||
|
||
Percentage of the requests served within a certain time | ExpressJS (NodeJS) | Spark (JAVA) | ||
----|--------------------|------------- | ||
50% | 349 ms | 339 ms | ||
90% | 367 ms | 365 ms | ||
95% | 380 ms | 378 ms | ||
99% | 698 ms | 909 ms | ||
100% (longest request)| 736 ms | 937 ms | ||
|
||
### Using the Chrome browser | ||
|
||
In the live demo is noticeable the slowness of JAVA implementation. When we try to *toggle* a todo or even *delete* one, we can see an icon of loading... Again, we are using (*exactly*) the same client side code. Below is an image with a comparison of this *toggling*: | ||
|
||
NodeJS server | JAVA server | ||
------------- | ------------ | ||
 |  | ||
|
||
As seen, Java needs **2157ms** for this operation. On the other side, Node needs less than a second: **695ms**. | ||
|
||
## Conclusion | ||
|
||
At the end of the day, in terms of perfomance, I ended up in a conclusion that the GraphQL in Node is way better in comparison to JAVA implementation. Please feel free to use the code and comment on the analysis we have done. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove "And"
... and database updates. In this tech article...