func (g *Goon) RunInTransaction(f func(tg *Goon) error, opts *datastore.TransactionOptions) error {
	var ng *Goon
	err := datastore.RunInTransaction(g.Context, func(tc context.Context) error {
		ng = &Goon{
			Context:          tc,
			inTransaction:    true,
			toDelete:         make(map[string]struct{}),
			toDeleteMC:       make(map[string]struct{}),
			KindNameResolver: g.KindNameResolver,
		}
		return f(ng)
	}, opts)
	if err == nil {
		ng.txnCacheLock.Lock()
		defer ng.txnCacheLock.Unlock()
		if len(ng.toDeleteMC) > 0 {
			var memkeys []string
			for k := range ng.toDeleteMC {
				memkeys = append(memkeys, k)
			}
			g.memcacheDeleteError(memcache.DeleteMulti(g.Context, memkeys))
		}
		for k := range ng.toDelete {
			g.cache.Delete(k)
		}
	} else {
		g.error(err)
	}
	return err
}
RunInTransaction runs f in a transaction. It calls f with a transaction
context tg that f should use for all App Engine operations. Neither cache nor
memcache are used or set during a transaction.

Otherwise similar to appengine/datastore.RunInTransaction:
https://developers.google.com/appengine/docs/go/datastore/reference#RunInTransaction
package goon
import (
	"context"
	"encoding/ascii85"
	"fmt"
	"net/http"
	"path/filepath"
	"reflect"
	"runtime"
	"sync"
	"time"
	"golang.org/x/crypto/blake2b"
	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/memcache"
)
var (
	LogErrors = true
	LogTimeoutErrors = false
	MemcachePutTimeoutThreshold = 20 * 1024  
	MemcachePutTimeoutSmall = 5 * time.Millisecond
	MemcachePutTimeoutLarge = 1 * time.Millisecond
	MemcacheGetTimeout = 31250 * time.Microsecond  
	IgnoreFieldMismatch = true
)
var (
	propagateMemcachePutError = false
)
type Goon struct {
	Context       context.Context
	cache         *cache
	inTransaction bool
	txnCacheLock  sync.Mutex  
	toDelete      map[string]struct{}
	toDeleteMC    map[string]struct{}
	KindNameResolver KindNameResolver
}
var MemcacheKey = func(k *datastore.Key) string {
	return k.Encode()
}
var cacheKeyPrefix = fmt.Sprintf("g%X:", serializationFormatVersion)
func cacheKey(k *datastore.Key) string {
	key := cacheKeyPrefix + MemcacheKey(k)
	if len(key) > memcacheMaxKeySize {
		h, err := blake2b.New(24, nil)
		if err != nil {
			panic(fmt.Sprintf("Unexpected error initializing blake2b: %v", err))
		}
		h.Write([]byte(key))
		hash := h.Sum(make([]byte, 0, 24))
		encoded := make([]byte, 30, 30)
		ascii85.Encode(encoded, hash)
		key = string(encoded)
	}
	return key
}
func memcacheGetTimeout(keyCount int) time.Duration {
	if keyCount > memcacheMaxRPCSize/memcacheMaxItemSize {
		keyCount = memcacheMaxRPCSize / memcacheMaxItemSize
	}
	return time.Duration(keyCount) * MemcacheGetTimeout
}
func memcachePutTimeout(payloadSize int) time.Duration {
	return MemcachePutTimeoutSmall + time.Duration(payloadSize/MemcachePutTimeoutThreshold)*MemcachePutTimeoutLarge
}
const (
	memcacheOverhead     = 76
	memcacheMaxKeySize   = 250
	memcacheMaxItemSize  = 1 << 20  
	memcacheMaxValueSize = memcacheMaxItemSize - memcacheMaxKeySize - memcacheOverhead
	memcacheMaxRPCSize   = 32 << 20  
)
const (
	datastoreGetMultiMaxItems    = 1000
	datastorePutMultiMaxItems    = 500
	datastoreDeleteMultiMaxItems = 500
	datastoreGetMultiMaxRPCSize = 50 << 20  
)
func NewGoon(r *http.Request) *Goon {
	return FromContext(appengine.NewContext(r))
}
func FromContext(c context.Context) *Goon {
	return &Goon{
		Context:          c,
		cache:            newCache(defaultCacheLimit),
		KindNameResolver: DefaultKindName,
	}
}
func (g *Goon) error(err error) {
	if !LogErrors {
		return
	}
	_, filename, line, ok := runtime.Caller(1)
	if ok {
		log.Errorf(g.Context, "goon - %s:%d - %v", filepath.Base(filename), line, err)
	} else {
		log.Errorf(g.Context, "goon - %v", err)
	}
}
func (g *Goon) timeoutError(err error) {
	if LogTimeoutErrors {
		log.Warningf(g.Context, "goon memcache timeout: %v", err)
	}
}
func (g *Goon) memcacheDeleteError(err error) {
	if err == nil || !LogErrors {
		return
	}
	if me, ok := err.(appengine.MultiError); ok {
		for i := range me {
			if me[i] != nil && me[i] != memcache.ErrCacheMiss {
				err = me[i]
				goto reportError
			}
		}
		return
	}
reportError:
	_, filename, line, ok := runtime.Caller(1)
	if ok {
		log.Errorf(g.Context, "goon - %s:%d - memcache.DeleteMulti failed: %v - the goon cache may be out of sync now!", filepath.Base(filename), line, err)
	} else {
		log.Errorf(g.Context, "goon - memcache.DeleteMulti failed: %v - the goon cache may be out of sync now!", err)
	}
}
func (g *Goon) extractKeys(src interface{}, putRequest bool) ([]*datastore.Key, error) {
	v := reflect.Indirect(reflect.ValueOf(src))
	if v.Kind() != reflect.Slice {
		return nil, fmt.Errorf("goon: value must be a slice or pointer-to-slice")
	}
	l := v.Len()
	keys := make([]*datastore.Key, l)
	for i := 0; i < l; i++ {
		vi := v.Index(i)
		key, hasStringId, err := g.getStructKey(vi.Interface())
		if err != nil {
			return nil, err
		}
		if !putRequest && key.Incomplete() {
			return nil, fmt.Errorf("goon: cannot find a key for struct - %v", vi.Interface())
		} else if putRequest && key.Incomplete() && hasStringId {
			return nil, fmt.Errorf("goon: empty string id on put")
		}
		keys[i] = key
	}
	return keys, nil
}
func (g *Goon) Key(src interface{}) *datastore.Key {
	if k, err := g.KeyError(src); err == nil {
		return k
	}
	return nil
}
func (g *Goon) Kind(src interface{}) string {
	if k, err := g.KeyError(src); err == nil {
		return k.Kind()
	}
	return ""
}
func (g *Goon) KeyError(src interface{}) (*datastore.Key, error) {
	key, _, err := g.getStructKey(src)
	return key, err
}