[S2GRAPH-172]: Suggest to implement GraphQL as standard web interface for S2Graph.
JIRA: [S2GRAPH-172] https://issues.apache.org/jira/browse/S2GRAPH-172 Pull Request: Closes #131 Author daewon <dae...@apache.org> Project: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/commit/6a7e58a4 Tree: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/tree/6a7e58a4 Diff: http://git-wip-us.apache.org/repos/asf/incubator-s2graph/diff/6a7e58a4 Branch: refs/heads/master Commit: 6a7e58a463d110f438c478962ad6fc1df44d8e8f Parents: 8f9214e 1f1dee3 Author: DO YUNG YOON <steams...@apache.org> Authored: Tue Feb 13 18:23:30 2018 +0900 Committer: DO YUNG YOON <steams...@apache.org> Committed: Tue Feb 13 18:25:29 2018 +0900 ---------------------------------------------------------------------- CHANGES | 1 + build.sbt | 8 +- project/plugins.sbt | 3 +- .../org/apache/s2graph/core/Management.scala | 4 +- .../org/apache/s2graph/core/mysqls/Label.scala | 3 + .../apache/s2graph/core/mysqls/LabelMeta.scala | 8 + .../apache/s2graph/core/mysqls/Service.scala | 1 + s2graphql/README.md | 550 +++++++++++++++++++ s2graphql/build.sbt | 40 ++ s2graphql/project/build.properties | 18 + s2graphql/project/plugins.sbt | 19 + s2graphql/src/main/resources/application.conf | 25 + s2graphql/src/main/resources/log4j.properties | 26 + s2graphql/src/main/scala/GraphQLServer.scala | 110 ++++ s2graphql/src/main/scala/GraphRepository.scala | 164 ++++++ s2graphql/src/main/scala/HttpServer.scala | 61 ++ s2graphql/src/main/scala/S2Type.scala | 519 +++++++++++++++++ .../main/scala/SangriaPlayJsonScalarType.scala | 76 +++ s2graphql/src/main/scala/SchemaDef.scala | 36 ++ s2graphql/src/test/resources/application.conf | 18 + .../rest/play/controllers/AdminController.scala | 8 +- 21 files changed, 1688 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/6a7e58a4/CHANGES ---------------------------------------------------------------------- diff --cc CHANGES index 2dcdfcf,8384bdc..0a46c3b --- a/CHANGES +++ b/CHANGES @@@ -55,6 -55,7 +55,7 @@@ Release Notes - S2Graph - Version 0.2. * [S2GRAPH-125] - Add options field on Label model for controlling advanced options. * [S2GRAPH-166] - Provide embeddable storage backend using RocksDB. * [S2GRAPH-175] - Provide Elastic Search Index Provider. - * [S2GRAPH-172] - Suggest to implement GraphQL as standard web interface for S2Graph. ++ * [S2GRAPH-172] - Suggest to implement GraphQL as standard web interface for S2Graph. ** Task * [S2GRAPH-162] - Update year in the NOTICE file. http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/6a7e58a4/s2core/src/main/scala/org/apache/s2graph/core/mysqls/LabelMeta.scala ---------------------------------------------------------------------- diff --cc s2core/src/main/scala/org/apache/s2graph/core/mysqls/LabelMeta.scala index 920b432,c715c47..5018285 --- a/s2core/src/main/scala/org/apache/s2graph/core/mysqls/LabelMeta.scala +++ b/s2core/src/main/scala/org/apache/s2graph/core/mysqls/LabelMeta.scala @@@ -187,13 -187,15 +187,21 @@@ object LabelMeta extends Model[LabelMet val cacheKey = s"labelId=${labelId}" cacheKey -> ls }.toList) + + ls + } + + def updateStoreInGlobalIndex(id: Int, storeInGlobalIndex: Boolean)(implicit session: DBSession = AutoSession): Try[Long] = Try { + sql""" + update label_metas set store_in_global_index = ${storeInGlobalIndex} where id = ${id} + """.updateAndReturnGeneratedKey.apply() } + + def updateStoreInGlobalIndex(id: Int, storeInGlobalIndex: Boolean)(implicit session: DBSession = AutoSession): Try[Long] = Try { + sql""" + update label_metas set store_in_global_index = ${storeInGlobalIndex} where id = ${id} + """.updateAndReturnGeneratedKey.apply() + } } case class LabelMeta(id: Option[Int], http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/6a7e58a4/s2graphql/README.md ---------------------------------------------------------------------- diff --cc s2graphql/README.md index 0000000,4146e8e..56d9645 mode 000000,100644..100644 --- a/s2graphql/README.md +++ b/s2graphql/README.md @@@ -1,0 -1,530 +1,550 @@@ ++<!--- ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++---> + # Suggest to implement GraphQL as standard web interface for S2Graph. + + - To support GraphQL i used [Akka HTTP](https://github.com/akka/akka-http) and [Sangria](https://github.com/sangria-graphql). each is an HTTP Server and GraphQL Scala implementation. + - I also used [GraphiQL](https://github.com/graphql/graphiql) as a tool for GraphQL queries. + + ## Wroking example + + ![mutation](https://user-images.githubusercontent.com/1182522/35611013-f551f2b6-06a6-11e8-8f48-e39e667a8849.gif) + + ![query](https://user-images.githubusercontent.com/1182522/35611725-599e1e5a-06a9-11e8-9a52-9e5fd3542c2e.gif) + + + ## Overview + + The reason I started this work is because the `Label` used by S2Graph has a strong type system, so I think it will work well with the `schema` provided by GraphQL. + + To do this, we converted S2Graph Model (Label, Service ...) into GraphLQL schema whenever added (changed). + + ## Setup + Assume that hbase is running on localhost. + If the hbase environment is not set up, you can run it with the following command + + ```bash + sbt package + target/apache-s2graph-0.2.1-SNAPSHOT-incubating-bin/bin/hbase-standalone.sh start + ``` + + If hbase is running well, run the following command after cloning the project locally. + + `GraphiQL` is not directly related to the `GraphQL` implementation, but is recommended for convenient queries. + Because of the license problem, you should download the file through the following command. + + ```bash + cd s2graphql/src/main/resources + wget https://raw.githubusercontent.com/sangria-graphql/sangria-akka-http-example/master/src/main/resources/graphiql.html + ``` + + You can see that the `graphiql.html` file is added to the `s2graphql/src/main/resources` folder as shown below. + + ``` + $ls + application.conf graphiql.html log4j.properties + ``` + + And let's run http server. + + ```bash + sbt -DschemaCacheTTL=-1 -Dhttp.port=8000 'project s2graphql' '~re-start' + ``` + + When the server is running, connect to `http://localhost:8000`. If it works normally, you can see the following screen. + + ![2018-01-31 4 39 25](https://user-images.githubusercontent.com/1182522/35610627-5ddd1cd6-06a5-11e8-8f02-446b28df54cb.png) + + ## API List + - createService + - createLabel + - addEdges + - addEdge + - query (You can recursively browse the linked labels from the service and any other labels that are linked from that label) + + ## Your First Grpah (GraphQL version) + + [S2Graph tutorial](https://github.com/apache/incubator-s2graph#your-first-graph) + I have ported the contents of `Your first graph` provided by S2Graph based on GraphQL. + + ### Start by connecting to `http://localhost:8000`. + + The environment for this example is Mac OS and Chrome. + You can get help with schema-based `Autocompletion` using the `ctrl + space` key. + + If you add a `label` or `service`, you will need to `refresh` (`cmd + r`) your browser because the schema will change dynamically. + + 1. First, we need a name for the new service. + + The following POST query will create a service named "KakaoFavorites". + + Request + ```graphql + mutation { + createService( + name: "KakaoFavorites", + compressionAlgorithm: gz + ) { + isSuccess + message + created { + id + } + } + } + ``` + + Response + ```json + { + "data": { + "createService": { + "isSuccess": true, + "message": "Created successful", + "created": { + "id": 1 + } + } + } + } + ``` + + To make sure the service is created correctly, check out the following. + + > Since the schema has changed, GraphiQL must recognize the changed schema. To do this, refresh the browser several times. + + Request + ```graphql + query { + Services(name: KakaoFavorites) { + id + name + } + } + ``` + + Response + ```json + { + "data": { + "Services": [ + { + "id": 1, + "name": "KakaoFavorites" + } + ] + } + } + ``` + + 2. Next, we will need some friends. + + In S2Graph, relationships are organized as labels. Create a label called friends using the following createLabel API call: + + Request + + ```graphql + mutation { + createLabel( + name: "friends", + sourceService: { + name: KakaoFavorites, + columnName: "userName", + dataType: string + }, + targetService: { + name: KakaoFavorites, + columnName: "userName", + dataType: string + } + consistencyLevel: strong + ){ + isSuccess + message + created { + id + name + } + } + } + ``` + + Response + ```json + { + "data": { + "createLabel": { + "isSuccess": true, + "message": "Created successful", + "created": { + "id": 1, + "name": "friends" + } + } + } + } + ``` + + Check if the label has been created correctly + > Since the schema has changed, GraphiQL must recognize the changed schema. To do this, refresh the browser several times. + + Request + ```graphql + query { + Labels(name: friends) { + id + name + srcColumnName + tgtColumnName + } + } + ``` + + Response + ```json + { + "data": { + "Labels": [ + { + "id": 1, + "name": "friends", + "srcColumnName": "userName", + "tgtColumnName": "userName" + } + ] + } + } + ``` + + Now that the label friends is ready, we can store the friendship data. + Entries of a label are called edges, and you can add edges with edges/insert API: + + > Since the schema has changed, GraphiQL must recognize the changed schema. To do this, refresh the browser several times. + + Request + ```graphql + mutation { + addEdges( + friends: [ + {from: "Elmo", to: "Big Bird"}, + {from: "Elmo", to: "Ernie"}, + {from: "Elmo", to: "Bert"}, + {from: "Cookie Monster", to: "Grover"}, + {from: "Cookie Monster", to: "Kermit"}, + {from: "Cookie Monster", to: "Oscar"}, + ] + ) { + isSuccess + } + } + ``` + + Response + ```json + { + "data": { + "addEdges": [ + { + "isSuccess": true + }, + { + "isSuccess": true + }, + { + "isSuccess": true + }, + { + "isSuccess": true + }, + { + "isSuccess": true + }, + { + "isSuccess": true + } + ] + } + } + ``` + + Query friends of Elmo with getEdges API: + + Request + + ```graphql + query { + KakaoFavorites(id: "Elmo") { + friends { + to + } + } + } + ``` + + Response + + ```json + { + "data": { + "KakaoFavorites": [ + { + "friends": [ + { + "to": "Bert" + }, + { + "to": "Ernie" + }, + { + "to": "Big Bird" + } + ] + } + ] + } + } + ``` + + Now query friends of Cookie Monster: + + ```graphql + query { + KakaoFavorites(id: "Cookie Monster") { + friends { + to + } + } + } + ``` + + 3. Users of Kakao Favorites will be able to post URLs of their favorite websites. + + Request + + ```graphql + mutation { + createLabel( + name: "post", + sourceService: { + name: KakaoFavorites, + columnName: "userName", + dataType: string + }, + targetService: { + name: KakaoFavorites, + columnName: "url", + dataType: string, + } + consistencyLevel: strong + ) { + isSuccess + message + created { + id + name + } + } + } + ``` + + Response + + ```json + { + "data": { + "createLabel": { + "isSuccess": true, + "message": "Created successful", + "created": { + "id": 2, + "name": "post" + } + } + } + } + ``` + + Now, insert some posts of the users: + + > Since the schema has changed, GraphiQL must recognize the changed schema. To do this, refresh the browser several times. + + + Request + + ```graphql + mutation { + addEdges( + post: [ + { from: "Big Bird", to: "www.kakaocorp.com/en/main" }, + { from: "Big Bird", to: "github.com/kakao/s2graph" }, + { from: "Ernie", to: "groups.google.com/forum/#!forum/s2graph" }, + { from: "Grover", to: "hbase.apache.org/forum/#!forum/s2graph" }, + { from: "Kermit", to: "www.playframework.com"}, + { from: "Oscar", to: "www.scala-lang.org"} + ] + ) { + isSuccess + } + } + ``` + + Response + ```json + { + "data": { + "addEdges": [ + { + "isSuccess": true + }, + { + "isSuccess": true + }, + { + "isSuccess": true + }, + { + "isSuccess": true + }, + { + "isSuccess": true + }, + { + "isSuccess": true + } + ] + } + } + ``` + + 4. So far, we have designed a label schema for the labels friends and post, and stored some edges to them.+ + + This should be enough for creating the timeline feature! The following two-step query will return the URLs for Elmo's timeline, which are the posts of Elmo's friends: + + Request + + ```graphql + query { + KakaoFavorites(id: "Elmo") { + friends { + post { + from + to + } + } + } + } + ``` + + Response + ```json + { + "data": { + "KakaoFavorites": [ + { + "friends": [ + { + "post": [] + }, + { + "post": [ + { + "from": "Ernie", + "to": "groups.google.com/forum/#!forum/s2graph" + } + ] + }, + { + "post": [ + { + "from": "Big Bird", + "to": "www.kakaocorp.com/en/main" + }, + { + "from": "Big Bird", + "to": "github.com/kakao/s2graph" + } + ] + } + ] + } + ] + } + } + ``` + + Also try Cookie Monster's timeline: + + Request + ```graphql + query { + KakaoFavorites(id: "Cookie Monster") { + friends { + post { + from + to + } + } + } + } + ``` + + Response + ```json + { + "data": { + "KakaoFavorites": [ + { + "friends": [ + { + "post": [ + { + "from": "Oscar", + "to": "www.scala-lang.org" + } + ] + }, + { + "post": [ + { + "from": "Kermit", + "to": "www.playframework.com" + } + ] + }, + { + "post": [ + { + "from": "Grover", + "to": "hbase.apache.org/forum/#!forum/s2graph" + } + ] + } + ] + } + ] + } + } + ``` + + + ![2018-01-31 5 18 46](https://user-images.githubusercontent.com/1182522/35612101-db97e160-06aa-11e8-9286-0dd1ffa15c82.png) + + The example above is by no means a full blown social network timeline, but it gives you an idea of how to represent, store and query graph data with S2Graph. + http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/6a7e58a4/s2graphql/build.sbt ---------------------------------------------------------------------- diff --cc s2graphql/build.sbt index 0000000,d344892..22cde81 mode 000000,100644..100644 --- a/s2graphql/build.sbt +++ b/s2graphql/build.sbt @@@ -1,0 -1,21 +1,40 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++ + name := "s2graphql" + + version := "0.1" + + description := "GraphQL server with akka-http and sangria and s2graph" + + scalacOptions ++= Seq("-deprecation", "-feature") + + libraryDependencies ++= Seq( + "org.sangria-graphql" %% "sangria" % "1.3.3", + "org.sangria-graphql" %% "sangria-spray-json" % "1.0.0", + + "com.typesafe.akka" %% "akka-http" % "10.0.10", + "com.typesafe.akka" %% "akka-http-spray-json" % "10.0.10", + + "com.typesafe.akka" %% "akka-slf4j" % "2.4.6", + + "org.scalatest" %% "scalatest" % "3.0.4" % Test + ) + + Revolver.settings http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/6a7e58a4/s2graphql/project/plugins.sbt ---------------------------------------------------------------------- diff --cc s2graphql/project/plugins.sbt index 0000000,4f78e51..7dab336 mode 000000,100644..100644 --- a/s2graphql/project/plugins.sbt +++ b/s2graphql/project/plugins.sbt @@@ -1,0 -1,1 +1,19 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ + addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/6a7e58a4/s2graphql/src/main/resources/application.conf ---------------------------------------------------------------------- diff --cc s2graphql/src/main/resources/application.conf index 0000000,71e8a99..45eb1b7 mode 000000,100644..100644 --- a/s2graphql/src/main/resources/application.conf +++ b/s2graphql/src/main/resources/application.conf @@@ -1,0 -1,7 +1,25 @@@ ++# ++# Licensed to the Apache Software Foundation (ASF) under one ++# or more contributor license agreements. See the NOTICE file ++# distributed with this work for additional information ++# regarding copyright ownership. The ASF licenses this file ++# to you under the Apache License, Version 2.0 (the ++# "License"); you may not use this file except in compliance ++# with the License. You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, ++# software distributed under the License is distributed on an ++# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++# KIND, either express or implied. See the License for the ++# specific language governing permissions and limitations ++# under the License. ++# + akka { + loggers = ["akka.event.slf4j.Slf4jLogger"] + event-handlers = ["akka.event.slf4j.Slf4jEventHandler"] + loglevel = "INFO" + } + + http://git-wip-us.apache.org/repos/asf/incubator-s2graph/blob/6a7e58a4/s2graphql/src/test/resources/application.conf ---------------------------------------------------------------------- diff --cc s2graphql/src/test/resources/application.conf index 0000000,e69de29..5fb2552 mode 000000,100644..100644 --- a/s2graphql/src/test/resources/application.conf +++ b/s2graphql/src/test/resources/application.conf @@@ -1,0 -1,0 +1,18 @@@ ++# ++# Licensed to the Apache Software Foundation (ASF) under one ++# or more contributor license agreements. See the NOTICE file ++# distributed with this work for additional information ++# regarding copyright ownership. The ASF licenses this file ++# to you under the Apache License, Version 2.0 (the ++# "License"); you may not use this file except in compliance ++# with the License. You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, ++# software distributed under the License is distributed on an ++# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++# KIND, either express or implied. See the License for the ++# specific language governing permissions and limitations ++# under the License. ++#