There’s really not that much documentation on how to write some Go code to work properly with Google Datastore or how to structure it the right way.

Yes, there are some resources, but they really aren’t that great:

Building your typical model

This part is pretty basic, we’re just gonna create a new file where you’ll be interacting with a data structure.

package models

import (
  "appengine"
  "appengine/datastore"
  "encoding/json"
  "io"
)

type Category struct {
  Id   int64  `json:"id" datastore:"-"`
  Name string `json:"name"`
}

It’s important to understand what’s going on here.

  • datastore:"-" tells the datastore module not to save this field into the database.
  • json:"id" renames and maps it to that name when outputting or reading a JSON structure.

Methods

Keys

In datastore, everything is referenced by Keys and not by your Id field. In order to update a model or create a new one, you need to generate the key onto your model’s instance.

func (category *Category) key(c appengine.Context) *datastore.Key {
  // if there is no Id, we want to generate an "incomplete"
  // one and let datastore determine the key/Id for us
  if category.Id == 0 {
    return datastore.NewIncompleteKey(c, "Category", nil)
  }

  // if Id is already set, we'll just build the Key based
  // on the one provided.
  return datastore.NewKey(c, "Category", "", category.Id, nil)
}

Save

func (category *Category) save(c appengine.Context) error {
  // reference the key function and generate it
  // accordingly basically its isNew true/false
  k, err := datastore.Put(c, category.key(c), category)
  if err != nil {
    return err
  }

  // The Id on the model is not prepopulated so we'll have
  // to append manually
  category.Id = k.IntID()
  return nil
}

Get All

func GetCategories(c appengine.Context) ([]Category, error) {
  q := datastore.NewQuery("Category").Order("Name")

  var categories []Category
  keys, err := q.GetAll(c, &categories)
  if err != nil {
    return nil, err
  }

  // you'll see this a lot because instances
  // do not have this by default
  for i := 0; i < len(categories); i++ {
    categories[i].Id = keys[i].IntID()
  }

  return categories, nil
}

Get By Id

func GetCategory(c appengine.Context, id int64) (*Category, error) {
  var category Category
  category.Id = id

  k := category.key(c)
  err := datastore.Get(c, k, &category)
  if err != nil {
    return nil, err
  }

  category.Id = k.IntID()

  return &category, nil
}

Get By Ids

Since there really isn’t a “Id IN” ability in datastore, we have to do it through an alternative method. More info on Stack Overflow.

func GetCategoriesByIds(c appengine.Context, ids []int64) ([]Category, error) {
  var keys []*datastore.Key

  for _, id := range ids {
    keys = append(keys, datastore.NewKey(c, "Category", "", id, nil))
  }

  categories := make([]Category, len(keys))
  err := datastore.GetMulti(c, keys, categories)
  if err != nil {
    return nil, err
  }

  for i := 0; i < len(categories); i++ {
    categories[i].Id = keys[i].IntID()
  }

  return categories, nil
}

Create

func NewCategory(c appengine.Context, r io.ReadCloser) (*Category, error) {
  var category Category

  // if you're using net/http, r = r.Body
  err := json.NewDecoder(r).Decode(&category)
  if err != nil {
    return nil, err
  }

  err = category.save(c)
  if err != nil {
    return nil, err
  }

  return &category, nil
}

Delete

func RemoveCategory(c appengine.Context, id int64) (*Category, error) {
  category, err := GetCategory(c, id)
  if err != nil {
    return nil, err
  }

  err = datastore.Delete(c, category.key(c))
  if err != nil {
    return nil, err
  }

  return category, nil
}

Update

func UpdateCategory(c appengine.Context, id int64, r io.ReadCloser) (*Category, error) {
  var category Category
  category.Id = id

  // gets the actual instance that is built currently
  // into our datastore
  k := category.key(c)
  err := datastore.Get(c, k, &category)
  if err != nil {
    return nil, err
  }

  // this is a temporary instance that is built
  // from `r.Body`
  var cat Category
  err = json.NewDecoder(r).Decode(&cat)
  if err != nil {
    return nil, err
  }

  // we only want specific fields to be updated
  category.Name = cat.Name

  err = category.save(c)
  if err != nil {
    return nil, err
  }

  return &category, nil
}