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 aMutateInBuilder
- 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 aMutateInBuilder
- 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 aMutateInBuilder
- 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 aMutateInBuilder
- 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 aMutateInBuilder
- 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 aMutateInBuilder
- 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 aMutateInBuilder
- 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 aMutateInBuilder
- 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 aMutateInBuilder
- Execute():
func (set *MutateInBuilder) Execute() (*DocumentFragment, error)
This method submits the chained operations to the server and returns aDocumentFragment
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 aLookupInBuilder
- Exists():
func (set *LookupInBuilder) Exists(path string) *LookupInBuilder
Checks if the provided path exists. Returns a reference to aLookupInBuilder
- Execute():
func (set *LookupInBuilder) Execute() (*DocumentFragment, error)
This method sends the chained commands to the server and returns a reference to aDocumentFragment
type (containing the results) and anerror
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.