Late last year as we were preparing Orchestrate for its public launch, I decided to build some Orchestrate powered apps. The goal was to understand better how users would consume our API. Having heard many good things about Go and having an interest in programming languages generally, I decided to write those apps in Go. The first step was to write a simple client library. Thus the first iteration of the Orchestrate golang client was born.

As Orchestrate matured over the following months, the Go client didn’t receive the love it deserved. No longer! Over the past few weeks, Brady Catherman, our ops guru, and I have worked to make the Go client a fully featured and powerful tool. Today we’d like to announce a major new release of the Orchestrate Go client, which henceforth will be known as gorc.

In addition to the rebranding, we’ve made the following improvements which bring gorc to parity with Orchestrate’s API:

  • Put methods support both interface{} types and raw io.Reader’s
  • Put methods return an Orchestrate path, enabling 1IfUnmodified1 semantics on updates and deletes
  • Get methods return Result types, containing an Orchestrate path and the raw binary value
  • Conditional puts and deletes
    • PutIfAbsent
    • PutIfUnmodified
    • DeleteIfUnmodified
  • Event puts with timestamps
  • Event gets with time ranges
  • Helper methods are provided for marshaling Result types RawValue into interface{} types
  • Key/Value Listing with pagination helpers
  • Search pagination helpers
  • Graph relation deletes
  • Lower memory overhead when serializing JSON
  • Improved defaults for the underlying HTTP client
  • Better documentation
  • And tons of minor improvements

So that’s the rundown. Let’s now take a look at how to take advantage of these features.

Getting Started

First, you’ll want to import the client. To do so, simply:

import "github.com/orchestrate-io/gorc"

Make sure to run go get to actually pulldown the client locally. Next, you should create a client object:

c := gorc.NewClient("YOUR_API_KEY")

It’s worth noting that client objects can and should be shared across goroutines. The client is wrapper for the standard library’s http.Client which will maintain a connection pool transparently, meaning that connections will be reused and multiple API calls can be made in parallel.

Key/Value Operations

Key/value operations power the core CRUD-style requests within Orchestrate.The following are some key/value basics:

// Get an object
result, err := c.Get("collection", "key")
if err != nil {
    // handle error
}

// Marshal the value into a domain type
domainObject := new(DomainObject)
if err := c.Value(domainObject); err != nil {
    // handle error
}

// Put a domain type to a collection/key pair
path, err := c.Put("collection", "key", domainObject)
if err != nil {
    // handle error
}

// Put if unmodified
path, err := c.PutIfUnmodified(path, domainObject)
if err != nil {
    if orcErr, ok := err.(*gorc.OrchestrateError); ok && orcErr.StatusCode == 412 {
        // handle version conflict
    } else {
        // handle error
    }
}

// Put if absent
path, err := c.PutIfAbsent("collection", "key", domainObject)
if err != nil {
    if orcErr, ok := err.(*gorc.OrchestrateError); ok && orcErr.StatusCode == 412 {
        // handle pre-existing key conflict
    } else {
        // handle error
    }
}

// Get a specific version of a colletion/key pair
result, err := c.GetPath(path)
if err != nil {
    // handle error
}

// Delete a collection/key pair
if err := c.Delete("collection", "key"); err != nil {
    // handle error
}

// Delete a collection/key pair if it's not been modified
if err := c.DeleteIfUnmodified(path); err != nil {
    if orcErr, ok := err.(*gorc.OrchestrateError); ok && orcErr.StatusCode == 412 {
        // handle version conflict
    } else {
        // handle error
    }
}

Key/Value Listing

Key/value listing allows for a collection to be listed in sorted key order. Here are some examples:

// List a collection, 100 items at a time
results, err := c.List("collection", 100)
if err != nil {
    // handle error
}

// Marshal the values into an array of domain objects
values := make([]DomainObject{}, len(results.Results))
for i, result := range results.Results {
    result.Value(&values[i])
}

// Get the next page of results
if results.HasNext() {
    nextResults, err := c.ListGetNext(results)
    if err != nil {
        // handle error
    }
}

// List the contents of a collection that come after "someKey"
results, err := c.ListAfter("collection", "someKey", 100)

// List the contents of a collection starting with "someKey"
results, err := c.ListStart("collection", "someKey", 100)

Search

Search is a powerful way to query objects based on their values. The queries use Lucene Query Parser Syntax, which we reviewed in a previous post: Querying the Enron Email Trove with Lucene. Here are some examples of how to use gorc’s search functionality:

// Perform a search getting 100 items at a time and starting from the first page
results, err := c.Search("collection", "a lucene query", 100, 0)
if err != nil {
    // handle error
}

// Marshal the values into an array of domain objects
values := make([]DomainObject{}, len(results.Results))
for i, result := range results.Results {
    result.Value(&values[i])
}

// Get the next page of search results
if results.HasNext() {
    nextResults, err := c.SearchGetNext(results)
    if err != nil {
        // handle error
    }
}

// Get the previous page of search results
if results.HasPrev() {
    nextResults, err := c.SearchGetPrev(results)
    if err != nil {
        // handle error
    }
}

Events

Orchestrate’s event functionality allows you to associate time-ordered data. Let’s see a few examples of how to create and retrieve events:

// Create an event
if err := c.PutEvent("collection", "key", "kind", domainObject); err != nil {
    // handle error
}

// Get latest events
events, err := c.GetEvents("collection", "key", "kind")
if err != nil {
    // handle error
}

// Marshal the values into an array of domain objects
values := make([]DomainObject{}, len(results.Results))
for i, event := range events.Results {
    event.Value(&values[i])
}

// Get events in time range
events, err := c.GetEventsInRange("collection", "key", "kind", startTime, endTime)
if err != nil {
    // handle error
}

Graph

Orchestrate’s graph functionality allows you to describe relations between collection/key pairs. The following examples show how to create, query, and delete relations:

// Create a relation
if err := c.PutRelation("sourceCollection", "sourceKey", "type", "destCollection", "destKey"); err != nil {
    // handle error
}

// Query relations
relations, err:= c.GetRelations("collection", "key", []string{"kind1", "kind2"})
if err != nil {
    // handle error
}

// Marshal the values into an array of domain objects
values := make([]DomainObject{}, len(results.Results))
for i, relation := range relations.Results {
    relation.Value(&values[i])
}

// Delete a relation
if err := c.DeleteRelation("sourceCollection", "sourceKey", "type", "destCollection", "destKey"); err != nil {
    // handle error
}

Closing Words

You can view the full go docs here. If you uncover any issues or want to contribute, head to our Github repo.

Go forth and Orchestrate!

Orchestrate gives developers access to multiple NoSQL databases through a single, RESTful API, eliminating the need to run databases in production. Learn more.