| Index: doc.go
|
| diff --git a/doc.go b/doc.go
|
| index 2704e4c15b83786e22c7f224e9696500b9e5f8b4..f33019758deb1629e2728c96313290071c1b96f3 100644
|
| --- a/doc.go
|
| +++ b/doc.go
|
| @@ -6,26 +6,128 @@
|
| // APIs. This means that it's possible to mock all of the supported appengine
|
| // APIs for testing (or potentially implement a different backend for them).
|
| //
|
| +// Features
|
| +//
|
| // gae currently provides interfaces for:
|
| -// * RawDatastore (a less reflection-magic version of Datastore)
|
| -// * Memcache
|
| -// * TaskQueue
|
| -// * GlobalInfo (e.g. Namespace, AppID, etc.)
|
| -//
|
| -// A package which implements the gae is expected to provide the following:
|
| -// * A package function `Use(c context.Context, ...) context.Context`
|
| -// This function is expected to add any information to c which is necessary
|
| -// for the rest of its implementations to work. This may be something like
|
| -// an `appengine.Context` or some connection information for an external
|
| -// server. The `...` in the function signature may be any additional data
|
| -// needed (or it may be empty).
|
| -//
|
| -// * Partially-implemented interfaces should embed one of the dummy
|
| -// implementations in the `dummy` subpackage which will panic with
|
| -// an appropriate error for unimplemented methods.
|
| -//
|
| -// see "infra/gae/libs/gae/prod" for an appengine-backed implementation.
|
| -// see "infra/gae/libs/gae/memory" for an in-memory implementation.
|
| +// - RawDatastore (a less reflection-magic version of Datastore)
|
| +// - Memcache
|
| +// - TaskQueue
|
| +// - GlobalInfo (e.g. Namespace, AppID, etc.)
|
| +//
|
| +// In addition, it provides a 'Datastore' service which adds all the reflection
|
| +// magic of the original SDK's datastore package on top of RawDatastore.
|
| +//
|
| +// Additional features include:
|
| +// - true service interfaces (not package-level functions)
|
| +// - methods don't need explicit context passed to them, increasing readability.
|
| +// - service filters allow for composition of functionality. For example:
|
| +// - transparent memcaching of datastore access
|
| +// - transparent transaction buffering
|
| +// - statistics-gathering shims
|
| +// - deterministic and probabalistic API failure simulation
|
| +// - transparent retries
|
| +// - truly parallel in-memory testing implementation. No more need for
|
| +// dev_appserver.py subprocesses :).
|
| +//
|
| +// Package Organization
|
| +//
|
| +// The gae library is organized into several subpackages:
|
| +// - service/* supported service definitions
|
| +// - impl/* implementations of the services
|
| +// - filter/* extra filter functionality for the services, agnostic to the
|
| +// underlying implementation.
|
| +//
|
| +// TLDR
|
| +//
|
| +// In production, do:
|
| +//
|
| +// import (
|
| +// "fmt"
|
| +// "net/http"
|
| +//
|
| +// "github.com/luci/gae/impl/prod"
|
| +// "github.com/luci/gae/service/rawdatastore"
|
| +// "golang.org/x/net/context"
|
| +// )
|
| +//
|
| +// func handler(w http.ResponseWriter, r *http.Request) {
|
| +// c := prod.UseRequest(r)
|
| +// // add production filters, etc. here
|
| +// innerHandler(c, w)
|
| +// }
|
| +//
|
| +// func innerHandler(c context.Context, w http.ResponseWriter) {
|
| +// rds := rawdatastore.Get(c)
|
| +// data := rawdatastore.PropertyMap{
|
| +// "Value": {rawdatastore.MkProperty("hello")},
|
| +// }
|
| +// newKey, err := rds.Put(rds.NewKey("Kind", "", 0, nil), data)
|
| +// if err != nil {
|
| +// http.Error(w, err.String(), http.StatusInternalServerError)
|
| +// }
|
| +// fmt.Fprintf(w, "I wrote: %s", newKey)
|
| +// }
|
| +//
|
| +// And in your test do:
|
| +//
|
| +// import (
|
| +// "testing"
|
| +// "fmt"
|
| +// "net/http"
|
| +//
|
| +// "github.com/luci/gae/impl/memory"
|
| +// "github.com/luci/gae/service/rawdatastore"
|
| +// "golang.org/x/net/context"
|
| +// )
|
| +//
|
| +// func TestHandler(t *testing.T) {
|
| +// t.Parallel()
|
| +// c := memory.Use(context.Background())
|
| +// // use rawdatastore here to monkey with the database, install
|
| +// // testing filters like featureBreaker to test error conditions in
|
| +// // innerHandler, etc.
|
| +// innerHandler(c, ...)
|
| +// }
|
| +//
|
| +// Service Definitions
|
| +//
|
| +// A service defintion lives under the `service` subfolder, and defines the
|
| +// user-facing interface for a service. Each service has a few common types and
|
| +// functions. Common types are:
|
| +//
|
| +// service.Interface - the main service interface
|
| +// service.Testable - any additional methods that a 'testing' implementation
|
| +// should provide. It's expected that tests will cast
|
| +// the Interface from Get() to Testable in order to
|
| +// access these methods.
|
| +// service.Factory - a function returning an Interface
|
| +// service.Filter - a function returning a new Interface based on the
|
| +// previous filtered interface.
|
| +//
|
| +// And common functions are:
|
| +// service.Get - Retrieve the current, filtered Interface
|
| +// implementation from the context. This is the most
|
| +// frequently used service function by far.
|
| +// service.AddFilters - adds one or more Filters to the context.
|
| +// service.SetFactory - adds a Factory to the context
|
| +// service.Set - adds an implementation of Interface to the context
|
| +// (shorthand for SetFactory, useful for testing)
|
| +//
|
| +// Implementations
|
| +//
|
| +// The impl subdirectory contains a couple different service implementations,
|
| +// depending on your needs.
|
| +//
|
| +// 'prod' is the production (e.g. real appengine-backed) implementation. It
|
| +// calls through to the original appengine SDK.
|
| +//
|
| +// 'memory' is a truly parallel in-memory testing implementation. It should
|
| +// be functionally the same as the production appengine services, implementing
|
| +// many of the real-world quirks of the actual services. It also implements
|
| +// the services' Testable interface, for those services which define those
|
| +// interfaces.
|
| +//
|
| +// Usage
|
| //
|
| // You will typically access one of the service interfaces in your code like:
|
| // // This is the 'production' code
|
| @@ -54,23 +156,26 @@
|
| // }, nil)
|
| // }
|
| //
|
| -// RawDatastore struct serialization is provided by the `helper` subpackage. All
|
| -// supported struct types and interfaces are provided in this package, however.
|
| -// You can operate without any struct serizialization/reflection by exclusively
|
| -// using DSPropertyMap. A goon-style Datastore interface is also provided in the
|
| -// `helper` subpackage.
|
| +// RawDatastore struct serialization is provided by the `rawdatastore`
|
| +// subpackage. All supported struct types and interfaces are provided in this
|
| +// package, however. You can operate without any struct
|
| +// serizialization/reflection by exclusively using PropertyMap. A goon-style
|
| +// Datastore interface is also provided in the `datastore` service package.
|
| +//
|
| +// Filters
|
| //
|
| // Each service also supports "filters". Filters are proxy objects which have
|
| // the same interface as the service they're filtering, and pass data through to
|
| // the previous filter in the stack. Conceptually, a filtered version of, for
|
| // example, the RawDatastore, could look like:
|
| -// User
|
| -// <mcache filter (attempts to use memcache as a cache for datastore)>
|
| -// <count filter (keeps a conter for how many times each API is used)>
|
| -// Memory RawDatastore (contains actual data)
|
| +// User code
|
| +// <count filter (counts how many times each API is called by the user)>
|
| +// <mcache filter (attempts to use memcache as a cache for rawdatastore)>
|
| +// <count filter (counts how many times each API is actually hit)>
|
| +// memory RawDatastore implementation
|
| //
|
| -// So GetRDS would return the full stack, and GetRDSUnfiltered would only
|
| -// return the bottom layer. In code, this would look like:
|
| +// So rawdatastore.Get would return the full stack. In code, this would look
|
| +// like:
|
| // func HTTPHandler(r *http.Request) {
|
| // c := prod.Use(appengine.NewContext(r)) // production datastore
|
| // c, rawCount := count.FilterRDS() // add count filter
|
| @@ -82,6 +187,6 @@
|
| // of the count filter, it returns its state from the Filter<Service> method,
|
| // and the state can be observed to see how many times each API was invoked.
|
| // Since filters stack, we can compare counts from rawCount versus userCount to
|
| -// see how many calls to the actual real datastore went through, v. how many
|
| +// see how many calls to the actual real datastore went through, vs. how many
|
| // went to memcache, for example.
|
| package gae
|
|
|