Skip to content

Document Models (ORM)

Andrew Watson edited this page Nov 23, 2013 · 13 revisions

Document Models, commonly referred to as ORM (Object-Relational Mapping) in other database drivers, maps Go structs to an object in Riak and supports links between objects. This is done by parsing the JSON data from an object in Riak and mapping it to a struct's fields.

The library allows for easy integration of a Go application into a project that also uses Ruby (on Rails) with the "ripple" gem (https://github.com/basho/ripple). To enable easy integration with Ruby/ripple projects the struct "tag" feature of Go is used to get around the naming convention differences between Go and Ruby (Uppercase starting letter required for export versus Uppercase being constants and typically CamelCase versus snake_case). Also it stores the model/struct name as _type in Riak just like ripple does.

For example the following Ruby/Ripple class:

    class Device
      include Ripple::Document
      property :ip, String
      property :description, String
      property :download_enabled, Boolean
    end

can be mapped to the following Go struct:

    type Device struct {
        DownloadEnabled  bool    `riak:"download_enabled"`
        Ip               string  `riak:"ip"`
        Description      string  `riak:"description"`
        riak.Model       `riak:"devices"`
    }

Note that it is required to have an (anonymous) riak.Model field. If the riak.Model field is an anonymous field this has the benefit that the functions like "Save" or "SaveAs" can be called directly as in the example below.

This example initializes a document model, sets the fields and saves it to Riak:

err := riak.ConnectClient("127.0.0.1:8087")
var dev Device 
err = riak.NewModel("", &dev)
dev.DownloadEnabled = true
dev.Ip = "192.168.1.10"
dev.Description = "Some device"
err = dev.Save()
fmt.Printf("Device saves as: %v\n", dev.Key())

This example loads an existing model, changes a field and saves it under a different key:

var dev Device 
err = riak.LoadModel("key", &dev)
dev.Description = "Some other device"
err = dev.SaveAs("other_key")

Models can also be created in, or loaded from, another bucket (other then defined in the Model's struct definition):

var dev Device
err = riak.NewModelIn("other_devices", "", &dev)
dev.Description = "Some device"
err = dev.Save()
fmt.Printf("Device saves as: %v\n", dev.Key())

err = riak.LoadModelFrom("other_devices", "some_key", &dev)

Relationships using links

A model can link to another model or to many other models using "riak.One" and "riak.Many" links. The links can retrieve the linked information and map it to a model.

    type Device struct {
        DownloadEnabled  bool      `riak:"download_enabled"`
        Ip               string    `riak:"ip"`
        Description      string    `riak:"description"`
        LinkedDevice     riak.One  `riak:"device"`
        Records          riak.Many `riak:"records"`
        riak.Model       `riak:"devices"`
    }

...
    var device Device
...
    var linkedDev Device
    err = device.LinkedDevice.Get(&linkedDev)
...
    var rec Record
    for _, v := range device.Records {
        err = v.Get(&rec)
        ...
    }

Resolving conflicts (siblings)

If document models are stored in a bucket that allows multiple values or siblings a "Resolve" function must be implemented to resolve those conflicts. It is up to the application to merge the siblings in a model. If a model is loaded that has multiple siblings and no Resolve function is overriding the (*Model)Resolve function an error will be returned. See below for the definition of the Resolver interface and the Resolve function that should be overridden:

type Resolver interface {
	Resolve(int) error
}

func (*Model) Resolve(count int) (err error) {
	return errors.New("Resolve not implemented")
}

The function receives the number of siblings that are present that have data and a typical function should first create a slice for those siblings and get them populated. See the example below that solves the conflict for the Device struct used in earlier examples:

func (d *Device) Resolve(count int) (err error) {
	// Get siblings populated
	siblings := make([]Device, count)
	err = d.GetSiblings(siblings)
	// Resolve the conflict (in some arbitrary way, in this example the
	// longest Description is choosen, the first non-empty Ip and
	// DownloadEnabled is set to true if any of the siblings have it
	// set to true).
	d.DownloadEnabled = false
	d.Ip = ""
	for _, s := range siblings {
		if len(s.Description) > len(d.Description) {
			d.Description = s.Description
		}
		if d.Ip == "" {
			d.Ip = s.Ip
		}
		if s.DownloadEnabled {
			d.DownloadEnabled = true
		}
	}
}