Hello World | Couchbase Docs (original) (raw)

Couchbase documents are organized into buckets, scopes, and collections.CRUD operations — Create, Read, Update, Delete — can be performed upon documents in a collection.

JSON

We’ll create a snippet of JSON to work with, using the client’s own JSON library, but you can read about the Scala SDK’s support for other JSON libraries on theJSON Libraries page.

val json = JsonObject("status" -> "awesome")

Insert (Create) and Upsert

insert and upsert will both create a new document. The difference between the two is that if a document with that key already exists, the insert operation will fail, while the upsert operation will succeed, replacing the content.

We need to provide a unique ID as the key, and we’ll use a UUID here:

Creating a new document

val docId = UUID.randomUUID().toString
collection.upsert(docId, json) match {
  case Success(result)    =>
  case Failure(exception) => println("Error: " + exception)
}

Get (Read)

The get method reads a document from a collection.

As mentioned above, the Scala SDK will not throw exceptions. Instead, methods that can error — such as the upsert above — will return a Scala Try result, which can either be a Success containing the result, or a Failure containing a Throwable exception. The easiest way to handle a single operation is with pattern matching, as shown above.

Now let’s get the data back (this example will look a little messy due the nested handling of multiple Try results, but we’ll see how to clean it up shortly):

// Get a document
collection.get(docId) match {
  case Success(result) =>
    // Convert the content to a JsonObjectSafe
    result.contentAs[JsonObjectSafe] match {
      case Success(json) =>
        // Pull out the JSON's status field, if it exists
        json.str("status") match {
          case Success(hello) => println(s"Couchbase is $hello")
          case _              => println("Field 'status' did not exist")
        }
      case Failure(err) => println("Error decoding result: " + err)
    }
  case Failure(err) => println("Error getting document: " + err)
}

Here we’re fetching the value for the key docId, converting that value to a JsonObjectSafe(a simple wrapper around JsonObject that returns Try results — seeJsonObjectSafe for details), and then accessing the value of the status key as a String.

Better Error Handling

All three of these operations could fail, so there’s quite a lot of error handling code here to do something quite simple. One way to improve on this is by using flatMap, like this:

val result: Try[String] = collection
  .get(docId)
  .flatMap(_.contentAs[JsonObjectSafe])
  .flatMap(_.str("status"))

result match {
  case Success(status) => println(s"Couchbase is $status")
  case Failure(err)    => println("Error: " + err)
}

Alternatively, you can use a for-comprehension, like so:

val result: Try[String] = for {
  result <- collection.get(docId)
  json <- result.contentAs[JsonObjectSafe]
  status <- json.str("status")
} yield status

result match {
  case Success(status) => println(s"Couchbase is $status")
  case Failure(err)    => println("Error: " + err)
}

Either of these methods will stop on the first failed operation. So the final returned Try contains either a) Success and the result of the final operation, indicating that everything was successful, or b) Failure with the error returned by the first failing operation.

Replace (Update) and Overloads

You’ll notice that most operations in the Scala SDK have two overloads. One will take an Options builder, which provides all possible options that operation takes. For instance:

The replace method updates the value of an existing document

collection.replace(
  docId,
  json,
  ReplaceOptions()
    .expiry(10.seconds)
    .durability(Durability.Majority)
) match {
  case Success(status) =>
  case Failure(err)    => println("Error: " + err)
}

These options blocks are implemented as Scala case classes: they are immutable data objects that return a copy of themselves on each change.

The other overload is provided purely for convenience. It takes named arguments instead of an Options object, and provides only the most commonly used options:

collection.replace(docId, json, durability = Durability.Majority) match {
  case Success(status) =>
  case Failure(err)    => println("Error: " + err)
}

| | When you replace a document, it’s usually good practice to use optimistic locking. Otherwise, changes might get lost if two people change the same document at the same time. | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Remove (Delete)

The remove method deletes a document from a collection:

collection.remove("document-key") match {
  case Success(result) => println("Document removed successfully")
  case Failure(err: DocumentNotFoundException) =>
    println("The document does not exist")
  case Failure(err) => println("Error: " + err)
}