Not too long ago I wrote about working with parts, or fragments, of documents in Couchbase

using the Node.js SDK. Being able to work with

parts of documents is made possible using

Couchbase Server 4.5 and higher and the sub-document API.

This is huge because when working with NoSQL documents you may find yourself with very large documents due to all the embedded JSON data. As you probably

know, making requests on large documents is slow, and in the modern web age, everything needs to be fast. Instead, it is more efficient to only

work with what you need and not everything all at once.

This time around we’re going to look at doing the same NoSQL document manipulations we saw in Node.js, but this time with the Go programming language. Let’s

come up with a data story for this example. It will be the same data story as the previous example, but let’s assume we have the following JSON document:

{ firstName: "Nic", lastName: "Raboy", socialNetworking: { twitter: "nraboy" } } 1 2 3 4 5 6 7 8 9 { firstName : "Nic" , lastName : "Raboy" , socialNetworking : { twitter : "nraboy" } }

The above data will be a basic user profile store with social media information. All of our manipulations will be around the social media information, not

the parent data that surrounds it.

To keep things simple for this guide, we are going to work with a fresh project. At this point we’ll assume you have both Couchbase Server 4.5+ and

GoLang installed and configured on your machine. If you’ve not already downloaded the GoLang SDK for Couchbase, execute the following from your Command

Prompt or Terminal:

go get github.com/couchbase/gocb 1 2 3 go get github . com / couchbase / gocb

Our entire project for this example will reside in a single file. We’re going to refer to this file as main.go and it can reside in any

Go project directory you want as long as it meets the requirements of the Go programming language.

To start things off, let’s create a main function inside our project:

func main() { fmt.Println("Starting the app...") cluster, _ := gocb.Connect("couchbase://localhost") bucket, _ = cluster.OpenBucket("default", "") person := Person{FirstName: "Nic", LastName: "Raboy", SocialNetworking: &SocialNetworking{Twitter: "nraboy"}} createDocument("nraboy", &person) } 1 2 3 4 5 6 7 8 9 func main ( ) { fmt . Println ( "Starting the app..." ) cluster , _ : = gocb . Connect ( "couchbase://localhost" ) bucket , _ = cluster . OpenBucket ( "default" , "" ) person : = Person { FirstName : "Nic" , LastName : "Raboy" , SocialNetworking : & SocialNetworking { Twitter : "nraboy" } } createDocument ( "nraboy" , & person ) }

There are a few things to note in the above. First we are establishing a connection to a locally running Couchbase cluster. When the connection has

been established we open the default bucket. Notice that we are only assigning a value to the bucket and not defining it. This

is because we are going to use this variable globally and must define it outside the main function. With the bucket open, we are going to

create our initial data structure. This data structure Person is defined below:

type Person struct { FirstName string `json:"firstname,omitempty"` LastName string `json:"lastname,omitempty"` SocialNetworking *SocialNetworking `json:"socialNetworking,omitempty"` } type SocialNetworking struct { Twitter string `json:"twitter,omitempty"` Website string `json:"website,omitempty"` } 1 2 3 4 5 6 7 8 9 10 11 12 type Person struct { FirstName string ` json : "firstname,omitempty" ` LastName string ` json : "lastname,omitempty" ` SocialNetworking * SocialNetworking ` json : "socialNetworking,omitempty" ` } type SocialNetworking struct { Twitter string ` json : "twitter,omitempty" ` Website string ` json : "website,omitempty" ` }

The Person structure will have the basic user information and reference another structure called SocialNetworking . Both

structures are tagged with JSON property names that are to be excluded from print if they are blank.

Going back to the main function. Notice that our new person object is missing the website. We’re going to be adding this later. The first

function we call from the main function is called createDocument and it will add our object to the database. This function

is defined as follows:

func createDocument(documentId string, person *Person) { fmt.Println("Upserting a full document...") _, error := bucket.Upsert(documentId, person, 0) if error != nil { fmt.Println(error.Error()) return } getDocument(documentId) getSubDocument(documentId) } 1 2 3 4 5 6 7 8 9 10 11 12 func createDocument ( documentId string , person * Person ) { fmt . Println ( "Upserting a full document..." ) _ , error : = bucket . Upsert ( documentId , person , 0 ) if error ! = nil { fmt . Println ( error . Error ( ) ) return } getDocument ( documentId ) getSubDocument ( documentId ) }

In the above function we are not yet working with fragments of a document. We need to start the example with fresh data first. We’re going to upsert

the intitial document and provided there are no errors we’re going to call getDocument to validate it was created and then

getSubDocument to get a certain part of the document. The getDocument function will be used twice in this application and

it looks like the following:

func getDocument(documentId string) { fmt.Println("Getting the full document by id...") var person Person _, error := bucket.Get(documentId, &person) if error != nil { fmt.Println(error.Error()) return } jsonPerson, _ := json.Marshal(&person) fmt.Println(string(jsonPerson)) } 1 2 3 4 5 6 7 8 9 10 11 12 13 func getDocument ( documentId string ) { fmt . Println ( "Getting the full document by id..." ) var person Person _ , error : = bucket . Get ( documentId , & person ) if error ! = nil { fmt . Println ( error . Error ( ) ) return } jsonPerson , _ : = json . Marshal ( & person ) fmt . Println ( string ( jsonPerson ) ) }

In the above getDocument function we are getting the entire document based on id, marshalling it into JSON, and then printing it out. This

brings us to the getSubDocument function as seen below:

func getSubDocument(documentId string) { fmt.Println("Getting part of a document by id...") fragment, error := bucket.LookupIn(documentId).Get("socialNetworking").Execute() if error != nil { fmt.Println(error.Error()) return } var socialNetworking SocialNetworking fragment.Content("socialNetworking", &socialNetworking) jsonSocialNetworking, _ := json.Marshal(&socialNetworking) fmt.Println(string(jsonSocialNetworking)) upsertSubDocument(documentId, "thepolyglotdeveloper.com") } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func getSubDocument ( documentId string ) { fmt . Println ( "Getting part of a document by id..." ) fragment , error : = bucket . LookupIn ( documentId ) . Get ( "socialNetworking" ) . Execute ( ) if error ! = nil { fmt . Println ( error . Error ( ) ) return } var socialNetworking SocialNetworking fragment . Content ( "socialNetworking" , & socialNetworking ) jsonSocialNetworking , _ : = json . Marshal ( & socialNetworking ) fmt . Println ( string ( jsonSocialNetworking ) ) upsertSubDocument ( documentId , "thepolyglotdeveloper.com" ) }

In the above getSubDocument function we are doing a lookup within a document for a specific property. This is where we start working with

the sub-document API. The lookup we’re performing is a lookup for the socialNetworking property. Notice that I am referring to the JSON, not

the struct name. When we have the fragment we can marshal it into JSON and then print it out. The result should look like this:

{ "twitter": "nraboy" } 1 2 3 4 5 { "twitter" : "nraboy" }

At the end of the getSubDocument function we make a call to a soon to be created upsertSubDocument function. This is where

we are going to modify part of a document without first obtaining the entire document. This function can be seen as follows:

func upsertSubDocument(documentId string, website string) { fmt.Println("Upserting part of a document...") _, error := bucket.MutateIn(documentId, 0, 0).Upsert("socialNetworking.website", website, true).Execute() if error != nil { fmt.Println(error.Error()) return } getDocument(documentId) } 1 2 3 4 5 6 7 8 9 10 11 func upsertSubDocument ( documentId string , website string ) { fmt . Println ( "Upserting part of a document..." ) _ , error : = bucket . MutateIn ( documentId , 0 , 0 ) . Upsert ( "socialNetworking.website" , website , true ) . Execute ( ) if error ! = nil { fmt . Println ( error . Error ( ) ) return } getDocument ( documentId ) }

In the above function we first specify which document we want to manipulate based on the document id. Then we say we want to perform an upsert on a

certain path or property of document. In this example we are saying we want to upsert a website property found in the

socialNetworking parent. Note that this entire process happens without actually obtaining the document.

When we’re done we do a full document lookup again to see what the document looks like as a whole. In case you need this to be put into perspective a

little better, the full code to this project can be seen below:

package main import ( "encoding/json" "fmt" "github.com/couchbase/gocb" ) var bucket *gocb.Bucket type Person struct { FirstName string `json:"firstname,omitempty"` LastName string `json:"lastname,omitempty"` SocialNetworking *SocialNetworking `json:"socialNetworking,omitempty"` } type SocialNetworking struct { Twitter string `json:"twitter,omitempty"` Website string `json:"website,omitempty"` } func getDocument(documentId string) { fmt.Println("Getting the full document by id...") var person Person _, error := bucket.Get(documentId, &person) if error != nil { fmt.Println(error.Error()) return } jsonPerson, _ := json.Marshal(&person) fmt.Println(string(jsonPerson)) } func createDocument(documentId string, person *Person) { fmt.Println("Upserting a full document...") _, error := bucket.Upsert(documentId, person, 0) if error != nil { fmt.Println(error.Error()) return } getDocument(documentId) getSubDocument(documentId) } func getSubDocument(documentId string) { fmt.Println("Getting part of a document by id...") fragment, error := bucket.LookupIn(documentId).Get("socialNetworking").Execute() if error != nil { fmt.Println(error.Error()) return } var socialNetworking SocialNetworking fragment.Content("socialNetworking", &socialNetworking) jsonSocialNetworking, _ := json.Marshal(&socialNetworking) fmt.Println(string(jsonSocialNetworking)) upsertSubDocument(documentId, "thepolyglotdeveloper.com") } func upsertSubDocument(documentId string, website string) { fmt.Println("Upserting part of a document...") _, error := bucket.MutateIn(documentId, 0, 0).Upsert("socialNetworking.website", website, true).Execute() if error != nil { fmt.Println(error.Error()) return } getDocument(documentId) } func main() { fmt.Println("Starting the app...") cluster, _ := gocb.Connect("couchbase://localhost") bucket, _ = cluster.OpenBucket("default", "") person := Person{FirstName: "Nic", LastName: "Raboy", SocialNetworking: &SocialNetworking{Twitter: "nraboy"}} createDocument("nraboy", &person) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package main import ( "encoding/json" "fmt" "github.com/couchbase/gocb" ) var bucket * gocb . Bucket type Person struct { FirstName string ` json : "firstname,omitempty" ` LastName string ` json : "lastname,omitempty" ` SocialNetworking * SocialNetworking ` json : "socialNetworking,omitempty" ` } type SocialNetworking struct { Twitter string ` json : "twitter,omitempty" ` Website string ` json : "website,omitempty" ` } func getDocument ( documentId string ) { fmt . Println ( "Getting the full document by id..." ) var person Person _ , error : = bucket . Get ( documentId , & person ) if error ! = nil { fmt . Println ( error . Error ( ) ) return } jsonPerson , _ : = json . Marshal ( & person ) fmt . Println ( string ( jsonPerson ) ) } func createDocument ( documentId string , person * Person ) { fmt . Println ( "Upserting a full document..." ) _ , error : = bucket . Upsert ( documentId , person , 0 ) if error ! = nil { fmt . Println ( error . Error ( ) ) return } getDocument ( documentId ) getSubDocument ( documentId ) } func getSubDocument ( documentId string ) { fmt . Println ( "Getting part of a document by id..." ) fragment , error : = bucket . LookupIn ( documentId ) . Get ( "socialNetworking" ) . Execute ( ) if error ! = nil { fmt . Println ( error . Error ( ) ) return } var socialNetworking SocialNetworking fragment . Content ( "socialNetworking" , & socialNetworking ) jsonSocialNetworking , _ : = json . Marshal ( & socialNetworking ) fmt . Println ( string ( jsonSocialNetworking ) ) upsertSubDocument ( documentId , "thepolyglotdeveloper.com" ) } func upsertSubDocument ( documentId string , website string ) { fmt . Println ( "Upserting part of a document..." ) _ , error : = bucket . MutateIn ( documentId , 0 , 0 ) . Upsert ( "socialNetworking.website" , website , true ) . Execute ( ) if error ! = nil { fmt . Println ( error . Error ( ) ) return } getDocument ( documentId ) } func main ( ) { fmt . Println ( "Starting the app..." ) cluster , _ : = gocb . Connect ( "couchbase://localhost" ) bucket , _ = cluster . OpenBucket ( "default" , "" ) person : = Person { FirstName : "Nic" , LastName : "Raboy" , SocialNetworking : & SocialNetworking { Twitter : "nraboy" } } createDocument ( "nraboy" , & person ) }

Take this project for a test drive to see how wonderful the sub-document API is.

Conclusion

You just saw how to use the Couchbase Server Sub-Document API in a GoLang application using the Couchbase Go SDK. No longer will you have to worry about

passing around your potentially huge NoSQL documents, ruining your application response times. If you know your documents are large or you only need

bits and pieces, you can make use of this API.

For more information, visit the Couchbase Developer Portal.