|
| 1 | +# Getting Started with ScalarDB GraphQL |
| 2 | + |
| 3 | +ScalarDB GraphQL is an interface layer that allows client applications to communicate with a [ScalarDB](https://github.com/scalar-labs/scalardb) database with GraphQL. |
| 4 | + |
| 5 | +In this Getting Started guide, you will run a GraphQL server on your local machine. |
| 6 | + |
| 7 | +## Prerequisites |
| 8 | + |
| 9 | +We assume you have already installed Docker and have access to a ScalarDB-supported database such as Cassandra. Please configure them first by following [Getting Started with ScalarDB](../getting-started-with-scalardb.mdx) if you have not set them up yet. |
| 10 | + |
| 11 | +You need a Personal Access Token (PAT) to access the Docker image of ScalarDB GraphQL in GitHub Container registry since the image is private. Ask a person in charge to get your account ready. Please read [the official document](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) for more detail. |
| 12 | + |
| 13 | +## Set up a database schema |
| 14 | + |
| 15 | +We use the following simple example schema. |
| 16 | + |
| 17 | +`emoney.json` |
| 18 | + |
| 19 | +```json |
| 20 | +{ |
| 21 | + "emoney.account": { |
| 22 | + "transaction": true, |
| 23 | + "partition-key": [ |
| 24 | + "id" |
| 25 | + ], |
| 26 | + "clustering-key": [], |
| 27 | + "columns": { |
| 28 | + "id": "TEXT", |
| 29 | + "balance": "INT" |
| 30 | + } |
| 31 | + } |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +To apply the schema to your database, download the Schema Loader that matches the version you use from [scalardb releases](https://github.com/scalar-labs/scalardb/releases), and run the following command to load the schema. |
| 36 | + |
| 37 | +```console |
| 38 | +java -jar scalardb-schema-loader-<version>.jar --config /path/to/database.properties -f emoney.json --coordinator |
| 39 | +``` |
| 40 | + |
| 41 | +## Docker login |
| 42 | + |
| 43 | +`docker login` is required to start the ScalarDB GraphQL Docker image as described in the Prerequisites section. |
| 44 | + |
| 45 | +```console |
| 46 | +# read:packages scope needs to be selected in a personal access token to login |
| 47 | +export CR_PAT=YOUR_PERSONAL_ACCESS_TOKEN |
| 48 | +echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin |
| 49 | +``` |
| 50 | + |
| 51 | +## Configure the GraphQL Server |
| 52 | + |
| 53 | +Add the following properties to your `database.properties` file. You can change the values as needed. Especially, make sure to set `namespaces` property. The tables in the specified namespaces (it can be a comma-separated list) will be exposed by the GraphQL server. |
| 54 | + |
| 55 | +```properties |
| 56 | +scalar.db.graphql.port=8080 |
| 57 | +scalar.db.graphql.path=/graphql |
| 58 | +scalar.db.graphql.namespaces=emoney |
| 59 | +scalar.db.graphql.graphiql=true |
| 60 | +``` |
| 61 | + |
| 62 | +## Start up the GraphQL Server |
| 63 | + |
| 64 | +The following command starts up the ScalarDB GraphQL server. The first time you run the command, it will download the Docker image from GitHub Container Registry. |
| 65 | + |
| 66 | +```console |
| 67 | +docker run -v /path/to/database.properties:/scalardb-graphql/database.properties.tmpl -p 8080:8080 ghcr.io/scalar-labs/scalardb-graphql:<version> |
| 68 | +``` |
| 69 | + |
| 70 | +At this point, the server reads the tables in the specified namespaces and generates a GraphQL schema to perform CRUD operations on the them. |
| 71 | + |
| 72 | +## Run operations from GraphiQL |
| 73 | + |
| 74 | +If the server is configured with a property `scalar.db.graphql.graphiql=true` (true by default), GraphiQL IDE will be available. When the above example properties are used, 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. |
| 75 | + |
| 76 | +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. |
| 77 | + |
| 78 | +```graphql |
| 79 | +mutation PutUser1 { |
| 80 | + account_put(put: {key: {id: "user1"}, values: {balance: 1000}}) |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +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. |
| 85 | + |
| 86 | +The following response from the GraphQL server will appear in the right pane. |
| 87 | + |
| 88 | +```json |
| 89 | +{ |
| 90 | + "data": { |
| 91 | + "account_put": true |
| 92 | + } |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +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. |
| 97 | + |
| 98 | +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 will appear below the button, and you can choose which operation should be executed. Choose `GetUser1`. |
| 99 | + |
| 100 | +```graphql |
| 101 | +query GetUser1 { |
| 102 | + account_get(get: {key: {id: "user1"}}) { |
| 103 | + account { |
| 104 | + id |
| 105 | + balance |
| 106 | + } |
| 107 | + } |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +You should get the following result in the right pane. |
| 112 | + |
| 113 | +```json |
| 114 | +{ |
| 115 | + "data": { |
| 116 | + "account_get": { |
| 117 | + "account": { |
| 118 | + "id": "user1", |
| 119 | + "balance": 1000 |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +## Mappings between GraphQL API and ScalarDB Java API |
| 127 | + |
| 128 | +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 target namespaces. These operations are designed to match the ScalarDB APIs defined in the [`DistributedTransaction`](https://javadoc.io/doc/com.scalar-labs/scalardb/3.13.2/com/scalar/db/api/DistributedTransaction.html) interface. |
| 129 | + |
| 130 | +Assuming you have an `account` table in a namespace, the following queries and mutations will be generated. |
| 131 | + |
| 132 | +| ScalarDB API | GraphQL root type | GraphQL field | |
| 133 | +|--------------------------------------------------------|-------------------|------------------------------------------------------------------------------------| |
| 134 | +| `get(Get get)` | `Query` | `account_get(get: account_GetInput!): account_GetPayload` | |
| 135 | +| `scan(Scan scan)` | `Query` | `account_scan(scan: account_ScanInput!): account_ScanPayload` | |
| 136 | +| `put(Put put)` | `Mutation` | `account_put(put: account_PutInput!): Boolean!` | |
| 137 | +| `put(java.util.List<Put> puts)` | `Mutation` | `account_bulkPut(put: [account_PutInput!]!): Boolean!` | |
| 138 | +| `delete(Delete delete)` | `Mutation` | `account_delete(delete: account_DeleteInput!): Boolean!` | |
| 139 | +| `delete(java.util.List<Delete> deletes)` | `Mutation` | `account_bulkDelete(delete: [account_DeleteInput!]!): Boolean!` | |
| 140 | +| `mutate(java.util.List<? extends Mutation> mutations)` | `Mutation` | `account_mutate(put: [account_PutInput!]delete: [account_DeleteInput!]): Boolean!` | |
| 141 | + |
| 142 | +Note that the `scan` field is not generated for a table with no clustering key. This is the reason why `account_scan` field is not available in our `emoney` example in this document. |
| 143 | + |
| 144 | +You can see all generated GraphQL types in GraphiQL's Documentation Explorer (the `< Docs` link at the top-right corner). |
| 145 | + |
| 146 | +## Transaction across multiple requests |
| 147 | + |
| 148 | +This section describes how to run a transaction that spans multiple GraphQL requests. |
| 149 | + |
| 150 | +The generated schema provides the `@transaction` directive that allows you to identify transactions. This directive can be used with both queries and mutations. |
| 151 | + |
| 152 | +### Start a transaction before running an operation |
| 153 | + |
| 154 | +Adding a `@transaction` directive with no arguments to a query or a mutation directs the execution to start a new transaction. |
| 155 | + |
| 156 | +```graphql |
| 157 | +query GetAccounts @transaction { |
| 158 | + user1: account_get(get: {key: {id: "user1"}}) { |
| 159 | + account { balance } |
| 160 | + } |
| 161 | + user2: account_get(get: {key: {id: "user2"}}) { |
| 162 | + account { balance } |
| 163 | + } |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +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, this is the new ID of the transaction just started by the request. |
| 168 | + |
| 169 | +```json |
| 170 | +{ |
| 171 | + "data": { |
| 172 | + "user1": { |
| 173 | + "account": { |
| 174 | + "balance": 1000 |
| 175 | + } |
| 176 | + }, |
| 177 | + "user2": { |
| 178 | + "account": { |
| 179 | + "balance": 1000 |
| 180 | + } |
| 181 | + } |
| 182 | + }, |
| 183 | + "extensions": { |
| 184 | + "transaction": { |
| 185 | + "id": "c88da8a6-a13f-4857-82fe-45f1ab4150f9" |
| 186 | + } |
| 187 | + } |
| 188 | +} |
| 189 | +``` |
| 190 | + |
| 191 | +### Run an operation in a continued transaction |
| 192 | + |
| 193 | +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 will update two accounts you got in the previous example in the same transaction. This represents a transfer of balance from user1's account to user2's account. |
| 194 | + |
| 195 | +```graphql |
| 196 | +mutation Transfer @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9") { |
| 197 | + user1: account_put(put: {key: {id: "user1"}, values: {balance: 750}}) |
| 198 | + user2: account_put(put: {key: {id: "user2"}, values: {balance: 1250}}) |
| 199 | +} |
| 200 | +``` |
| 201 | + |
| 202 | +Note that a transaction started with GraphQL has a timeout of 1 minute and will be aborted automatically when it exceeds the timeout. |
| 203 | + |
| 204 | +### Commit a transaction |
| 205 | + |
| 206 | +To commit the continued transaction, specify both the `id` and the `commit: true` flag as arguments of the `@transaction` directive. |
| 207 | + |
| 208 | +```graphql |
| 209 | +query GetAndCommit @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9", commit: true) { |
| 210 | + user1: account_get(get: {key: {id: "user1"}}) { |
| 211 | + account { balance } |
| 212 | + } |
| 213 | + user2: account_get(get: {key: {id: "user2"}}) { |
| 214 | + account { balance } |
| 215 | + } |
| 216 | +} |
| 217 | +``` |
| 218 | + |
| 219 | +Note: If you specify a `commit: true` flag without an `id` argument like `@transaction(commit: true)`, a new transaction is started and committed just for one operation. This 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 it is `@transaction(commit: true)`. |
| 220 | + |
| 221 | +### Abort/Rollback a transaction |
| 222 | + |
| 223 | +When you need to abort/rollback 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 it with any other operations, so you must specify it alone. |
| 224 | + |
| 225 | +```graphql |
| 226 | +mutation AbortTx @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9") { |
| 227 | + abort |
| 228 | +} |
| 229 | +``` |
| 230 | +or |
| 231 | +```graphql |
| 232 | +mutation RollbackTx @transaction(id: "c88da8a6-a13f-4857-82fe-45f1ab4150f9") { |
| 233 | + rollback |
| 234 | +} |
| 235 | +``` |
0 commit comments