The Sub-Document API – go

You've probably heard about the sub-document (subdoc) API available in couchbase 4.5. Mark Nunberg, one of the architects of the new API, has a great blog on the motivation and impetus behind extending the Memcached (key-value) api to support Sub-Document operations. Matthew Revell also put together a great sample walkthrough using Java and Python. If you're anything like me, you want to see any new feature expressed in your preferred language(s). For me, that means go or nodejs. Let's look at an example and how the API works in go.

Edit: This blog post has been edited with updates from the 4.5 Beta

For other features of Couchbase 4.5, see Don Pinto's blog posts about the Developer Preview and the Beta.

Let's start with a simple json structure, with three fields

Create a document using the structure we defined and “upsert” it into couchbase

Now, let's add an array to the document we created to a new field, and then perform some additional operations on the array. Through the magic of the subdoc API, we can do all of this without ever having to retrieve or update the entire document. This saves time and bandwidth, and dramatically improves performance.

What just happened? I need a builder!

The go api for sub-document operations adds two new methods to the Bucket type: LookupIn() and MutateIn(). These bucket level operations are consistent across the couchbase SDKs. If you're using go, nodejs, Java, .NET, C or Python they all work the same way. This is a nice convenience for code portability, as we seldom see a production environment with only one language through the stack. Let's take a look at what these two new methods on the Bucket type do under the covers:

MutateIn

Let's look at the MutateInBuilder, used to combine one or more mutation operations scoped to a single document: func (b *Bucket) MutateIn(key string, cas Cas, expiry uint32) *MutateInBuilder. This function includes a method receiver for the Bucket type, and it returns a reference to the MutateInBuilder

The MutateInBuilder has ten methods:

  • AddUnique(): func (set *MutateInBuilder) AddUnique(path string, value interface{}, createParents bool) *MutateInBuilder  This method adds a unique value to an existing array field. It checks if the value exists first, and the updates. It returns a reference to a MutateInBuilder
  • ArrayInsert(): func (set *MutateInBuilder) ArrayInsert(path string, value interface{}) *MutateInBuilder  This method inserts an array value to an array field of a document. Note, in our example above we pass in a string that represents the array and index: “fourthItem[2]”. It returns a reference to a MutateInBuilder
  • Counter(): func (set *MutateInBuilder) Counter(path string, delta int64, createParents bool) *MutateInBuilder  This method performs an atomic counter operation on a field within a document. It returns a reference to a MutateInBuilder
  • Insert(): func (set *MutateInBuilder) Insert(path string, value interface{}, createParents bool) *MutateInBuilder  This method inserts a new value to a specific location in a document. It returns a reference to a MutateInBuilder
  • PushBack(): func (set *MutateInBuilder) PushBack(path string, value interface{}, createParents bool) *MutateInBuilder  This method adds a new value to the end of an array field within a document. It returns a reference to a MutateInBuilder
  • PushFront(): func (set *MutateInBuilder) PushFront(path string, value interface{}, createParents bool) *MutateInBuilder  This method adds a new value to the beginning of an array field within a document. It returns a reference to a MutateInBuilder
  • Remove(): func (set *MutateInBuilder) Remove(path string) *MutateInBuilder  This method removes a value from a specific field of a document. It returns a reference to a MutateInBuilder
  • Replace(): func (set *MutateInBuilder) Replace(path string, value interface{}) *MutateInBuilder  This method replaces a value within a field of a document. It returns a reference to a MutateInBuilder
  • Upsert(): func (set *MutateInBuilder) Upsert(path string, value interface{}, createParents bool) *MutateInBuilder  This method adds or replaces a field within a document. It returns a reference to a MutateInBuilder
  • Execute(): func (set *MutateInBuilder) Execute() (*DocumentFragment, error)  This method submits the chained operations to the server and returns a DocumentFragment containing their results.

 

The logic flow for MutateIn() looks like this

 

LookupIn

Let's look at the LookupInBuilder, which allows us to declare one or more retrieval operations scoped to a single document: func (b *Bucket) LookupIn(key string) *LookupInBuilder. This function includes a method receiver for the Bucket type, and it returns a reference to the LookupInBuilder.

The LookupInBuilder has three methods:

  • Get(): func (set *LookupInBuilder) Get(path string) *LookupInBuilder  This method requests that the path contents be retrieved. Returns a reference to a LookupInBuilder
  • Exists(): func (set *LookupInBuilder) Exists(path string) *LookupInBuilder   Checks if the provided path exists. Returns a reference to a LookupInBuilder
  • Execute(): func (set *LookupInBuilder) Execute() (*DocumentFragment, error)  This method sends the chained commands to the server and returns a reference to a DocumentFragment type (containing the results) and an error if one is encountered.

 

The logic flow for LookupIn()looks like this

Next Steps

Why not try it yourself? The example above along with several other go examples can be found in our developer-guide repository on github A great way to get started and try out couchbase 4.5 (in beta at the time of publishing) is with docker. The couchbase 4.5 docker image can be loaded if you have docker installed with the following command:

docker run -d --name=CB45DP1 -p 8091-8093:8091-8094 -p 11207-11210:11207-11210 -p 18091-18092:18091-18092 couchbase/server:enterprise-4.5.0-Beta

We thrive on feedback–give it a try and let us know what you think.

Posted by Todd Greenstein

As a Solution Architect at Couchbase. I'm currently specializing in NOSQL design, architecture, data modeling, nodejs and golang development, API design and proof of concepts.

Leave a reply