Skip to main content
Version: 3.10

Getting Started with ScalarDB Cluster GraphQL

This tutorial describes how to use ScalarDB Cluster GraphQL.

Prerequisites

Sample application

This tutorial illustrates the process of creating an electronic money application, where money can be transferred between accounts.

The following diagram shows the system architecture of the sample application:

                              +----------------------------------------------------------------------------------------------------------------------------------------+
| [Kubernetes Cluster] |
| |
| [Pod] [Pod] [Pod] |
| |
| +-------+ |
| +---> | Envoy | ---+ |
| | +-------+ | |
| | | |
+------------------------+ | +---------+ | +-------+ | +--------------------+ |
| Schema Loader | --+-> | Service | ---+---> | Envoy | ---+---------> | Service | ---+ |
| (indirect client mode) | | | (Envoy) | | +-------+ | | (ScalarDB Cluster) | | |
+------------------------+ | +---------+ | | +--------------------+ | +-----------------------+ |
| | +-------+ | | +---> | ScalarDB Cluster Node | ---+ |
| +---> | Envoy | ---+ | | +-----------------------+ | |
| +-------+ | | | |
| | | +-----------------------+ | +------------+ |
| +---+---> | ScalarDB Cluster Node | ---+---> | PostgreSQL | |
| | | +-----------------------+ | +------------+ |
| | | | |
| | | +-----------------------+ | |
| | +---> | ScalarDB Cluster Node | ---+ |
| | +-----------------------+ |
+------------+ | +----------------------------+ | |
| Browser | ------+---------------------------------------> | Service | ---+ |
| (GraphiQL) | | | (ScalarDB Cluster GraphQL) | |
+------------+ | +----------------------------+ |
| |
+----------------------------------------------------------------------------------------------------------------------------------------+

Step 1. Create schema.json

The following is a simple example schema.

Create schema.json, and add the following to the file:

{
"emoney.account": {
"transaction": true,
"partition-key": [
"id"
],
"clustering-key": [],
"columns": {
"id": "TEXT",
"balance": "INT"
}
}
}

Step 2. Create database.properties

You need to create database.properties for the Schema Loader for ScalarDB Cluster. But first, you need to get the EXTERNAL-IP address of the service resource of Envoy (scalardb-cluster-envoy).

To see the EXTERNAL-IP address, run the following command:

kubectl get svc scalardb-cluster-envoy
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
scalardb-cluster-envoy LoadBalancer 10.105.121.51 localhost 60053:30641/TCP 16h

In this case, the EXTERNAL-IP address is localhost.

Then, create database.properties, and add the following to the file:

scalar.db.transaction_manager=cluster
scalar.db.contact_points=indirect:localhost

To connect to ScalarDB Cluster, you need to specify cluster for the scalar.db.transaction_manager property. In addition, you will use the indirect client mode and connect to the service resource of Envoy in this tutorial. For details about the client modes, see Developer Guide for ScalarDB Cluster with the Java API.

Step 3. Load a schema

To load a schema via ScalarDB Cluster, you need to use the dedicated Schema Loader for ScalarDB Cluster (Schema Loader for Cluster). Using the Schema Loader for Cluster is basically the same as using the Schema Loader for ScalarDB except the name of the JAR file is different. You can download the Schema Loader for Cluster from ScalarDB Releases. After downloading the JAR file, you can run the Schema Loader for Cluster with the following command:

java -jar scalardb-cluster-schema-loader-3.10.6-all.jar --config database.properties -f schema.json --coordinator

Step 4. Run operations from GraphiQL

In ScalarDB Cluster, if the scalar.db.graphql.graphiql property is set to true (true is the default value), the GraphiQL IDE will be available.

To get the EXTERNAL-IP address of the service resource of ScalarDB Cluster GraphQL (scalardb-cluster-graphql), run the following command:

kubectl get svc scalardb-cluster-graphql
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
scalardb-cluster-graphql LoadBalancer 10.105.74.214 localhost 8080:30514/TCP 16h

In this case, the EXTERNAL-IP address is localhost, and the endpoint URL of GraphiQL IDE is http://localhost:8080/graphql. Opening that URL with your web browser will take you to the GraphiQL screen.

Let's insert the first record. In the left pane, paste the following mutation, then push the triangle-shaped Execute Query button at the top of the window.

mutation PutUser1 {
account_put(put: {key: {id: "user1"}, values: {balance: 1000}})
}

ScalarDB GraphQL always runs queries with transactions. The above query starts a new transaction, executes a ScalarDB Put command, and commits the transaction at the end of the execution.

The following response from the GraphQL server will appear in the right pane:

{
"data": {
"account_put": true
}
}

The "data" field contains the result of the execution. This response shows the account_put field of the mutation was successful. The result type of mutations is Boolean!, which indicates whether the operation succeeded or not.

Next, let's get the record you just inserted. Paste the following query next to the previous mutation in the left pane, and click the Execute Query button. Since you don't delete the mutation PutUser1 above, a pull-down menu will appear below the button, and you can choose which operation should be executed. Choose GetUser1, as shown below:

query GetUser1 {
account_get(get: {key: {id: "user1"}}) {
account {
id
balance
}
}
}

You should get the following result in the right pane:

{
"data": {
"account_get": {
"account": {
"id": "user1",
"balance": 1000
}
}
}
}

Mappings between GraphQL API and ScalarDB Java API

The automatically generated GraphQL schema defines queries, mutations, and object types for input/output to allow you to run CRUD operations for all the tables in the specified namespaces. These operations are designed to match the ScalarDB APIs defined in the DistributedTransaction interface.

Assuming you have an account table in a namespace, the following queries and mutations will be generated:

ScalarDB APIGraphQL root typeGraphQL field
get(Get get)Queryaccount_get(get: account_GetInput!): account_GetPayload
scan(Scan scan)Queryaccount_scan(scan: account_ScanInput!): account_ScanPayload
put(Put put)Mutationaccount_put(put: account_PutInput!): Boolean!
put(java.util.List<Put> puts)Mutationaccount_bulkPut(put: [account_PutInput!]!): Boolean!
delete(Delete delete)Mutationaccount_delete(delete: account_DeleteInput!): Boolean!
delete(java.util.List<Delete> deletes)Mutationaccount_bulkDelete(delete: [account_DeleteInput!]!): Boolean!
mutate(java.util.List<? extends Mutation> mutations)Mutationaccount_mutate(put: [account_PutInput!]delete: [account_DeleteInput!]): Boolean!

Note that the scan field is not generated for a table with no clustering key. This is the reason why the account_scan field is not available in this electronic money sample application.

You can see all generated GraphQL types in GraphiQL's Documentation Explorer (the < Docs link at the top-left corner).

Step 5. Run a transaction across multiple requests from GraphiQL

Let's run a transaction that spans multiple GraphQL requests.

The generated schema provides the @transaction directive that allows you to identify transactions. You can use this directive with both queries and mutations.

Before starting a transaction, you need to insert the necessary record with the following mutation:

mutation PutUser2 {
account_put(put: {key: {id: "user2"}, values: {balance: 1000}})
}

Start a transaction before running an operation

Running the following to add a @transaction directive with no arguments to a query or mutation directs the execution to start a new transaction:

query GetAccounts @transaction {
user1: account_get(get: {key: {id: "user1"}}) {
account { balance }
}
user2: account_get(get: {key: {id: "user2"}}) {
account { balance }
}
}

After running the above command, you will get a result with a transaction ID in the extensions field. The id value in the extensions is the transaction ID in which the operation in the request was run. In this case, the following is the new ID of the transaction just started by the request:

{
"data": {
"user1": {
"account": {
"balance": 1000
}
},
"user2": {
"account": {
"balance": 1000
}
}
},
"extensions": {
"transaction": {
"id": "c88da8a6-a13f-4857-82fe-45f1ab4150f9"
}
}
}

Run an operation in a continued transaction

To run the next queries or mutations in the transaction you started, specify the transaction ID as the id argument of the @transaction. The following example updates two accounts you retrieved in the previous example by transferring a balance from user1's account to user2's account in the same transaction:

mutation Transfer @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9") {
user1: account_put(put: {key: {id: "user1"}, values: {balance: 750}})
user2: account_put(put: {key: {id: "user2"}, values: {balance: 1250}})
}

Note that a transaction started with GraphQL has a timeout of 1 minute (by default) and will be aborted automatically if it exceeds the timeout.

Commit a transaction

To commit the continued transaction, specify both the id and the commit: true flag as arguments of the @transaction directive:

query GetAndCommit @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9", commit: true) {
user1: account_get(get: {key: {id: "user1"}}) {
account { balance }
}
user2: account_get(get: {key: {id: "user2"}}) {
account { balance }
}
}

Note: If you specify a commit: true flag without an id argument like @transaction(commit: true), a new transaction will start and be committed just for one operation. This behavior is exactly the same as not specifying the @transaction directive, as seen in the above examples using GraphiQL. In other words, you can omit the directive itself when @transaction(commit: true) is specified.

Abort or roll back a transaction

If you need to abort or roll back a transaction explicitly, you can use the abort or rollback mutation fields interchangeably (both have the same effect and usage). Note that you cannot mix these fields with any other operations, so you must specify only the abort or rollback mutation field as follows:

mutation AbortTx @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9") {
abort
}

Or:

mutation RollbackTx @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9") {
rollback
}

See also

For other ScalarDB Cluster tutorials, see the following:

For details about developing applications that use ScalarDB Cluster with the Java API, refer to the following:

For details about the ScalarDB Cluster gRPC API, refer to the following: