OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // Package gae provides a fakable wrapped interface for the appengine SDK's | 5 // Package gae provides a fakable wrapped interface for the appengine SDK's |
6 // APIs. This means that it's possible to mock all of the supported appengine | 6 // APIs. This means that it's possible to mock all of the supported appengine |
7 // APIs for testing (or potentially implement a different backend for them). | 7 // APIs for testing (or potentially implement a different backend for them). |
8 // | 8 // |
| 9 // Features |
| 10 // |
9 // gae currently provides interfaces for: | 11 // gae currently provides interfaces for: |
10 // * RawDatastore (a less reflection-magic version of Datastore) | 12 // - RawDatastore (a less reflection-magic version of Datastore) |
11 // * Memcache | 13 // - Memcache |
12 // * TaskQueue | 14 // - TaskQueue |
13 // * GlobalInfo (e.g. Namespace, AppID, etc.) | 15 // - GlobalInfo (e.g. Namespace, AppID, etc.) |
14 // | 16 // |
15 // A package which implements the gae is expected to provide the following: | 17 // In addition, it provides a 'Datastore' service which adds all the reflection |
16 // * A package function `Use(c context.Context, ...) context.Context` | 18 // magic of the original SDK's datastore package on top of RawDatastore. |
17 // This function is expected to add any information to c which is necessary | |
18 // for the rest of its implementations to work. This may be something like | |
19 // an `appengine.Context` or some connection information for an external | |
20 // server. The `...` in the function signature may be any additional data | |
21 // needed (or it may be empty). | |
22 // | 19 // |
23 // * Partially-implemented interfaces should embed one of the dummy | 20 // Additional features include: |
24 // implementations in the `dummy` subpackage which will panic with | 21 // - true service interfaces (not package-level functions) |
25 // an appropriate error for unimplemented methods. | 22 // - methods don't need explicit context passed to them, increasing readabilit
y. |
| 23 // - service filters allow for composition of functionality. For example: |
| 24 // - transparent memcaching of datastore access |
| 25 // - transparent transaction buffering |
| 26 // - statistics-gathering shims |
| 27 // - deterministic and probabalistic API failure simulation |
| 28 // - transparent retries |
| 29 // - truly parallel in-memory testing implementation. No more need for |
| 30 // dev_appserver.py subprocesses :). |
26 // | 31 // |
27 // see "infra/gae/libs/gae/prod" for an appengine-backed implementation. | 32 // Package Organization |
28 // see "infra/gae/libs/gae/memory" for an in-memory implementation. | 33 // |
| 34 // The gae library is organized into several subpackages: |
| 35 // - service/* supported service definitions |
| 36 // - impl/* implementations of the services |
| 37 // - filter/* extra filter functionality for the services, agnostic to the |
| 38 // underlying implementation. |
| 39 // |
| 40 // TLDR |
| 41 // |
| 42 // In production, do: |
| 43 // |
| 44 // import ( |
| 45 // "fmt" |
| 46 // "net/http" |
| 47 // |
| 48 // "github.com/luci/gae/impl/prod" |
| 49 // "github.com/luci/gae/service/rawdatastore" |
| 50 // "golang.org/x/net/context" |
| 51 // ) |
| 52 // |
| 53 // func handler(w http.ResponseWriter, r *http.Request) { |
| 54 // c := prod.UseRequest(r) |
| 55 // // add production filters, etc. here |
| 56 // innerHandler(c, w) |
| 57 // } |
| 58 // |
| 59 // func innerHandler(c context.Context, w http.ResponseWriter) { |
| 60 // rds := rawdatastore.Get(c) |
| 61 // data := rawdatastore.PropertyMap{ |
| 62 // "Value": {rawdatastore.MkProperty("hello")}, |
| 63 // } |
| 64 // newKey, err := rds.Put(rds.NewKey("Kind", "", 0, nil), data) |
| 65 // if err != nil { |
| 66 // http.Error(w, err.String(), http.StatusInternalServerError) |
| 67 // } |
| 68 // fmt.Fprintf(w, "I wrote: %s", newKey) |
| 69 // } |
| 70 // |
| 71 // And in your test do: |
| 72 // |
| 73 // import ( |
| 74 // "testing" |
| 75 // "fmt" |
| 76 // "net/http" |
| 77 // |
| 78 // "github.com/luci/gae/impl/memory" |
| 79 // "github.com/luci/gae/service/rawdatastore" |
| 80 // "golang.org/x/net/context" |
| 81 // ) |
| 82 // |
| 83 // func TestHandler(t *testing.T) { |
| 84 // t.Parallel() |
| 85 // c := memory.Use(context.Background()) |
| 86 // // use rawdatastore here to monkey with the database, install |
| 87 // // testing filters like featureBreaker to test error conditions in |
| 88 // // innerHandler, etc. |
| 89 // innerHandler(c, ...) |
| 90 // } |
| 91 // |
| 92 // Service Definitions |
| 93 // |
| 94 // A service defintion lives under the `service` subfolder, and defines the |
| 95 // user-facing interface for a service. Each service has a few common types and |
| 96 // functions. Common types are: |
| 97 // |
| 98 // service.Interface - the main service interface |
| 99 // service.Testable - any additional methods that a 'testing' implementation |
| 100 // should provide. It's expected that tests will cast |
| 101 // the Interface from Get() to Testable in order to |
| 102 // access these methods. |
| 103 // service.Factory - a function returning an Interface |
| 104 // service.Filter - a function returning a new Interface based on the |
| 105 // previous filtered interface. |
| 106 // |
| 107 // And common functions are: |
| 108 // service.Get - Retrieve the current, filtered Interface |
| 109 // implementation from the context. This is the most |
| 110 // frequently used service function by far. |
| 111 // service.AddFilters - adds one or more Filters to the context. |
| 112 // service.SetFactory - adds a Factory to the context |
| 113 // service.Set - adds an implementation of Interface to the context |
| 114 // (shorthand for SetFactory, useful for testing) |
| 115 // |
| 116 // Implementations |
| 117 // |
| 118 // The impl subdirectory contains a couple different service implementations, |
| 119 // depending on your needs. |
| 120 // |
| 121 // 'prod' is the production (e.g. real appengine-backed) implementation. It |
| 122 // calls through to the original appengine SDK. |
| 123 // |
| 124 // 'memory' is a truly parallel in-memory testing implementation. It should |
| 125 // be functionally the same as the production appengine services, implementing |
| 126 // many of the real-world quirks of the actual services. It also implements |
| 127 // the services' Testable interface, for those services which define those |
| 128 // interfaces. |
| 129 // |
| 130 // Usage |
29 // | 131 // |
30 // You will typically access one of the service interfaces in your code like: | 132 // You will typically access one of the service interfaces in your code like: |
31 // // This is the 'production' code | 133 // // This is the 'production' code |
32 // func HTTPHandler(r *http.Request) { | 134 // func HTTPHandler(r *http.Request) { |
33 // c := prod.Use(appengine.NewContext(r)) | 135 // c := prod.Use(appengine.NewContext(r)) |
34 // CoolFunc(c) | 136 // CoolFunc(c) |
35 // } | 137 // } |
36 // | 138 // |
37 // // This is the 'testing' code | 139 // // This is the 'testing' code |
38 // func TestCoolFunc(t *testing.T) { | 140 // func TestCoolFunc(t *testing.T) { |
39 // c := memory.Use(context.Background()) | 141 // c := memory.Use(context.Background()) |
40 // CoolFunc(c) | 142 // CoolFunc(c) |
41 // } | 143 // } |
42 // | 144 // |
43 // func CoolFunc(c context.Context, ...) { | 145 // func CoolFunc(c context.Context, ...) { |
44 // rds := gae.GetRDS(c) // returns a RawDatastore object | 146 // rds := gae.GetRDS(c) // returns a RawDatastore object |
45 // mc := gae.GetMC(c) // returns a Memcache object | 147 // mc := gae.GetMC(c) // returns a Memcache object |
46 // // use them here | 148 // // use them here |
47 // | 149 // |
48 // // don't pass rds/mc/etc. directly, pass the context instead. | 150 // // don't pass rds/mc/etc. directly, pass the context instead. |
49 // SomeOtherFunction(c, ...) | 151 // SomeOtherFunction(c, ...) |
50 // | 152 // |
51 // // because you might need to: | 153 // // because you might need to: |
52 // rds.RunInTransaction(func (c context.Context) error { | 154 // rds.RunInTransaction(func (c context.Context) error { |
53 // SomeOtherFunction(c, ...) // c contains transaction versions of everyt
hing | 155 // SomeOtherFunction(c, ...) // c contains transaction versions of everyt
hing |
54 // }, nil) | 156 // }, nil) |
55 // } | 157 // } |
56 // | 158 // |
57 // RawDatastore struct serialization is provided by the `helper` subpackage. All | 159 // RawDatastore struct serialization is provided by the `rawdatastore` |
58 // supported struct types and interfaces are provided in this package, however. | 160 // subpackage. All supported struct types and interfaces are provided in this |
59 // You can operate without any struct serizialization/reflection by exclusively | 161 // package, however. You can operate without any struct |
60 // using DSPropertyMap. A goon-style Datastore interface is also provided in the | 162 // serizialization/reflection by exclusively using PropertyMap. A goon-style |
61 // `helper` subpackage. | 163 // Datastore interface is also provided in the `datastore` service package. |
| 164 // |
| 165 // Filters |
62 // | 166 // |
63 // Each service also supports "filters". Filters are proxy objects which have | 167 // Each service also supports "filters". Filters are proxy objects which have |
64 // the same interface as the service they're filtering, and pass data through to | 168 // the same interface as the service they're filtering, and pass data through to |
65 // the previous filter in the stack. Conceptually, a filtered version of, for | 169 // the previous filter in the stack. Conceptually, a filtered version of, for |
66 // example, the RawDatastore, could look like: | 170 // example, the RawDatastore, could look like: |
67 // User | 171 // User code |
68 // <mcache filter (attempts to use memcache as a cache for datastore)> | 172 // <count filter (counts how many times each API is called by the user)> |
69 // <count filter (keeps a conter for how many times each API is used)> | 173 // <mcache filter (attempts to use memcache as a cache for rawdatastore)> |
70 // Memory RawDatastore (contains actual data) | 174 // <count filter (counts how many times each API is actually hit)> |
| 175 // memory RawDatastore implementation |
71 // | 176 // |
72 // So GetRDS would return the full stack, and GetRDSUnfiltered would only | 177 // So rawdatastore.Get would return the full stack. In code, this would look |
73 // return the bottom layer. In code, this would look like: | 178 // like: |
74 // func HTTPHandler(r *http.Request) { | 179 // func HTTPHandler(r *http.Request) { |
75 // c := prod.Use(appengine.NewContext(r)) // production datastore | 180 // c := prod.Use(appengine.NewContext(r)) // production datastore |
76 // c, rawCount := count.FilterRDS() // add count filter | 181 // c, rawCount := count.FilterRDS() // add count filter |
77 // c = mcache.FilterRDS(c) // add mcache filter | 182 // c = mcache.FilterRDS(c) // add mcache filter |
78 // c, userCount := count.FilterRDS() // add another count filter | 183 // c, userCount := count.FilterRDS() // add another count filter |
79 // } | 184 // } |
80 // | 185 // |
81 // Filters may or may not have state, it's up to the filter itself. In the case | 186 // Filters may or may not have state, it's up to the filter itself. In the case |
82 // of the count filter, it returns its state from the Filter<Service> method, | 187 // of the count filter, it returns its state from the Filter<Service> method, |
83 // and the state can be observed to see how many times each API was invoked. | 188 // and the state can be observed to see how many times each API was invoked. |
84 // Since filters stack, we can compare counts from rawCount versus userCount to | 189 // Since filters stack, we can compare counts from rawCount versus userCount to |
85 // see how many calls to the actual real datastore went through, v. how many | 190 // see how many calls to the actual real datastore went through, vs. how many |
86 // went to memcache, for example. | 191 // went to memcache, for example. |
87 package gae | 192 package gae |
OLD | NEW |