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 | 9 // Features |
10 // | 10 // |
11 // gae currently provides interfaces for: | 11 // gae currently provides interfaces for: |
12 // - RawDatastore (a less reflection-magic version of Datastore) | 12 // - Datastore |
13 // - Memcache | 13 // - Memcache |
14 // - TaskQueue | 14 // - TaskQueue |
15 // - GlobalInfo (e.g. Namespace, AppID, etc.) | 15 // - Info (e.g. Namespace, AppID, etc.) |
16 // | |
17 // In addition, it provides a 'Datastore' service which adds all the reflection | |
18 // magic of the original SDK's datastore package on top of RawDatastore. | |
19 // | 16 // |
20 // Additional features include: | 17 // Additional features include: |
21 // - true service interfaces (not package-level functions) | 18 // - true service interfaces (not package-level functions) |
22 // - methods don't need explicit context passed to them, increasing readabilit
y. | 19 // - methods don't need explicit context passed to them, increasing readabilit
y. |
23 // - service filters allow for composition of functionality. For example: | 20 // - service filters allow for composition of functionality. For example: |
24 // - transparent memcaching of datastore access | 21 // - transparent memcaching of datastore access |
25 // - transparent transaction buffering | 22 // - transparent transaction buffering |
26 // - statistics-gathering shims | 23 // - statistics-gathering shims |
27 // - deterministic and probabalistic API failure simulation | 24 // - deterministic and probabalistic API failure simulation |
28 // - transparent retries | 25 // - transparent retries |
29 // - truly parallel in-memory testing implementation. No more need for | 26 // - truly parallel in-memory testing implementation. No more need for |
30 // dev_appserver.py subprocesses :). | 27 // dev_appserver.py subprocesses :). |
| 28 // - Separate service and user-facing interfaces |
| 29 // - Allows easier filter and service implementation while retaining the |
| 30 // benefits of a user-friendly interface. |
31 // | 31 // |
32 // Package Organization | 32 // Package Organization |
33 // | 33 // |
34 // The gae library is organized into several subpackages: | 34 // The gae library is organized into several subpackages: |
35 // - service/* supported service definitions | 35 // - service/* supported service definitions |
36 // - impl/* implementations of the services | 36 // - impl/* implementations of the services |
37 // - filter/* extra filter functionality for the services, agnostic to the | 37 // - filter/* extra filter functionality for the services, agnostic to the |
38 // underlying implementation. | 38 // underlying implementation. |
39 // | 39 // |
40 // TLDR | 40 // TLDR |
41 // | 41 // |
42 // In production, do: | 42 // In production, do: |
43 // | 43 // |
44 // import ( | 44 // import ( |
45 // "fmt" | 45 // "fmt" |
46 // "net/http" | 46 // "net/http" |
47 // | 47 // |
48 // "github.com/luci/gae/impl/prod" | 48 // "github.com/luci/gae/impl/prod" |
49 // "github.com/luci/gae/service/rawdatastore" | 49 // "github.com/luci/gae/service/datastore" |
50 // "golang.org/x/net/context" | 50 // "golang.org/x/net/context" |
51 // ) | 51 // ) |
52 // | 52 // |
53 // func handler(w http.ResponseWriter, r *http.Request) { | 53 // func handler(w http.ResponseWriter, r *http.Request) { |
54 // c := prod.UseRequest(r) | 54 // c := prod.UseRequest(r) |
55 // // add production filters, etc. here | 55 // // add production filters, etc. here |
56 // innerHandler(c, w) | 56 // innerHandler(c, w) |
57 // } | 57 // } |
58 // | 58 // |
| 59 // type CoolStruct struct { |
| 60 // ID `gae:"$id"` |
| 61 // |
| 62 // Value string |
| 63 // } |
| 64 // |
59 // func innerHandler(c context.Context, w http.ResponseWriter) { | 65 // func innerHandler(c context.Context, w http.ResponseWriter) { |
60 // rds := rawdatastore.Get(c) | 66 // rds := datastore.Get(c) |
61 // data := rawdatastore.PropertyMap{ | 67 // obj := &CoolStruct{Value: "hello"} |
62 // "Value": {rawdatastore.MkProperty("hello")}, | 68 // if err := rds.Put(obj); err != nil { |
63 // } | |
64 // newKey, err := rds.Put(rds.NewKey("Kind", "", 0, nil), data) | |
65 // if err != nil { | |
66 // http.Error(w, err.String(), http.StatusInternalServerError) | 69 // http.Error(w, err.String(), http.StatusInternalServerError) |
67 // } | 70 // } |
68 // fmt.Fprintf(w, "I wrote: %s", newKey) | 71 // fmt.Fprintf(w, "I wrote: %s", ds.KeyForObj(obj)) |
69 // } | 72 // } |
70 // | 73 // |
71 // And in your test do: | 74 // And in your test do: |
72 // | 75 // |
73 // import ( | 76 // import ( |
74 // "testing" | 77 // "testing" |
75 // "fmt" | 78 // "fmt" |
76 // "net/http" | 79 // "net/http" |
77 // | 80 // |
78 // "github.com/luci/gae/impl/memory" | 81 // "github.com/luci/gae/impl/memory" |
79 // "github.com/luci/gae/service/rawdatastore" | 82 // "github.com/luci/gae/service/datastore" |
80 // "golang.org/x/net/context" | 83 // "golang.org/x/net/context" |
81 // ) | 84 // ) |
82 // | 85 // |
83 // func TestHandler(t *testing.T) { | 86 // func TestHandler(t *testing.T) { |
84 // t.Parallel() | 87 // t.Parallel() |
85 // c := memory.Use(context.Background()) | 88 // c := memory.Use(context.Background()) |
86 // // use rawdatastore here to monkey with the database, install | 89 // // use datastore here to monkey with the database, install testing |
87 // // testing filters like featureBreaker to test error conditions in | 90 // // filters like featureBreaker to test error conditions in innerHandler, |
88 // // innerHandler, etc. | 91 // // etc. |
89 // innerHandler(c, ...) | 92 // innerHandler(c, ...) |
90 // } | 93 // } |
91 // | 94 // |
92 // Service Definitions | 95 // Service Definitions |
93 // | 96 // |
94 // A service defintion lives under the `service` subfolder, and defines the | 97 // 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 | 98 // user-facing interface for a service. Each service has a few common types and |
96 // functions. Common types are: | 99 // functions. Common types are: |
97 // | 100 // |
98 // service.Interface - the main service interface | 101 // service.Interface - the main user-friendly service interface. |
99 // service.Testable - any additional methods that a 'testing' implementation | 102 // |
100 // should provide. It's expected that tests will cast | 103 // service.RawInterface - the internal service interface used by service |
101 // the Interface from Get() to Testable in order to | 104 // and filter implementations. Note that some services |
102 // access these methods. | 105 // like Info don't distinguish between the service |
103 // service.Factory - a function returning an Interface | 106 // interface and the user interface. |
104 // service.Filter - a function returning a new Interface based on the | 107 // |
105 // previous filtered interface. | 108 // service.Testable - any additional methods that a 'testing' |
| 109 // implementation should provide. It's expected that |
| 110 // tests will cast the RawInterface from GetRaw() to |
| 111 // Testable in order to access these methods. |
| 112 // |
| 113 // service.RawFactory - a function returning a RawInterface |
| 114 // |
| 115 // service.RawFilter - a function returning a new RawInterface based on |
| 116 // the previous filtered interface. Filters chain |
| 117 // together to allow behavioral service features |
| 118 // without needing to agument the underlying service |
| 119 // implementations directly. |
106 // | 120 // |
107 // And common functions are: | 121 // And common functions are: |
108 // service.Get - Retrieve the current, filtered Interface | 122 // service.Get - Retrieve the current, filtered Interface |
109 // implementation from the context. This is the most | 123 // implementation from the context. This is the most |
110 // frequently used service function by far. | 124 // frequently used service function by far. |
111 // service.AddFilters - adds one or more Filters to the context. | 125 // |
112 // service.SetFactory - adds a Factory to the context | 126 // service.GetRaw - Retrieve the current, filtered RawInterface |
113 // service.Set - adds an implementation of Interface to the context | 127 // implementation from the context. This is less |
114 // (shorthand for SetFactory, useful for testing) | 128 // frequently used, but can be useful if you want to |
| 129 // avoid some of the overhead of the user-friendly |
| 130 // Interface, which can do sometimes-unnecessary amou
nts |
| 131 // of reflection or allocation. The RawInterface and |
| 132 // Interface for a service are fully interchangable a
nd |
| 133 // usage of them can be freely mixed in an applicatio
n. |
| 134 // |
| 135 // service.AddRawFilters - adds one or more Filters to the context. |
| 136 // |
| 137 // service.SetRawFactory - adds a Factory to the context |
| 138 // |
| 139 // service.SetRaw - adds an implementation of RawInterface to the cont
ext |
| 140 // (shorthand for SetRawFactory, useful for testing) |
115 // | 141 // |
116 // Implementations | 142 // Implementations |
117 // | 143 // |
118 // The impl subdirectory contains a couple different service implementations, | 144 // The impl subdirectory contains a couple different service implementations, |
119 // depending on your needs. | 145 // depending on your needs. |
120 // | 146 // |
121 // 'prod' is the production (e.g. real appengine-backed) implementation. It | 147 // 'prod' is the production (e.g. real appengine-backed) implementation. It |
122 // calls through to the original appengine SDK. | 148 // calls through to the original appengine SDK. |
123 // | 149 // |
124 // 'memory' is a truly parallel in-memory testing implementation. It should | 150 // 'memory' is a truly parallel in-memory testing implementation. It should |
125 // be functionally the same as the production appengine services, implementing | 151 // be functionally the same as the production appengine services, implementing |
126 // many of the real-world quirks of the actual services. It also implements | 152 // many of the real-world quirks of the actual services. It also implements |
127 // the services' Testable interface, for those services which define those | 153 // the services' Testable interface, for those services which define those |
128 // interfaces. | 154 // interfaces. |
129 // | 155 // |
| 156 // 'dummy' provides a bunch of implementations of the various RawInterfaces. |
| 157 // These implementations just panic with an appropriate message, depending on |
| 158 // which API method was called. They're useful to embed in filter or service |
| 159 // implementations as stubs while you're implementing the filter. |
| 160 // |
130 // Usage | 161 // Usage |
131 // | 162 // |
132 // You will typically access one of the service interfaces in your code like: | 163 // You will typically access one of the service interfaces in your code like: |
133 // // This is the 'production' code | 164 // // This is the 'production' code |
134 // func HTTPHandler(r *http.Request) { | 165 // func HTTPHandler(r *http.Request) { |
135 // c := prod.Use(appengine.NewContext(r)) | 166 // c := prod.Use(appengine.NewContext(r)) |
136 // CoolFunc(c) | 167 // CoolFunc(c) |
137 // } | 168 // } |
138 // | 169 // |
139 // // This is the 'testing' code | 170 // // This is the 'testing' code |
140 // func TestCoolFunc(t *testing.T) { | 171 // func TestCoolFunc(t *testing.T) { |
141 // c := memory.Use(context.Background()) | 172 // c := memory.Use(context.Background()) |
142 // CoolFunc(c) | 173 // CoolFunc(c) |
143 // } | 174 // } |
144 // | 175 // |
145 // func CoolFunc(c context.Context, ...) { | 176 // func CoolFunc(c context.Context, ...) { |
146 // rds := gae.GetRDS(c) // returns a RawDatastore object | 177 // ds := datastore.Get(c) // returns a datastore.Interface object |
147 // mc := gae.GetMC(c) // returns a Memcache object | 178 // mc := memcache.Get(c) // returns a memcache.Interface object |
148 // // use them here | 179 // // use them here |
149 // | 180 // |
150 // // don't pass rds/mc/etc. directly, pass the context instead. | 181 // // don't pass ds/mc/etc. directly, pass the context instead. |
151 // SomeOtherFunction(c, ...) | 182 // SomeOtherFunction(c, ...) |
152 // | 183 // |
153 // // because you might need to: | 184 // // because you might need to: |
154 // rds.RunInTransaction(func (c context.Context) error { | 185 // ds.RunInTransaction(func (c context.Context) error { |
155 // SomeOtherFunction(c, ...) // c contains transaction versions of everyt
hing | 186 // SomeOtherFunction(c, ...) // c contains transactional versions of ever
ything |
156 // }, nil) | 187 // }, nil) |
157 // } | 188 // } |
158 // | 189 // |
159 // RawDatastore struct serialization is provided by the `rawdatastore` | |
160 // subpackage. All supported struct types and interfaces are provided in this | |
161 // package, however. You can operate without any struct | |
162 // serizialization/reflection by exclusively using PropertyMap. A goon-style | |
163 // Datastore interface is also provided in the `datastore` service package. | |
164 // | |
165 // Filters | 190 // Filters |
166 // | 191 // |
167 // Each service also supports "filters". Filters are proxy objects which have | 192 // Each service also supports "filters". Filters are proxy objects which have |
168 // the same interface as the service they're filtering, and pass data through to | 193 // the same interface as the service they're filtering, and pass data through to |
169 // the previous filter in the stack. Conceptually, a filtered version of, for | 194 // the previous filter in the stack. Conceptually, a filtered version of, for |
170 // example, the RawDatastore, could look like: | 195 // example, the Datastore, could look like: |
171 // User code | 196 // User code |
172 // <count filter (counts how many times each API is called by the user)> | 197 // <count filter (counts how many times each API is called by the user)> |
173 // <mcache filter (attempts to use memcache as a cache for rawdatastore)> | 198 // <dscache filter (attempts to use memcache as a cache for datastore)> |
174 // <count filter (counts how many times each API is actually hit)> | 199 // <count filter (counts how many times each API is actually hit)> |
175 // memory RawDatastore implementation | 200 // memory datastore.RawInterface implementation |
176 // | 201 // |
177 // So rawdatastore.Get would return the full stack. In code, this would look | 202 // So datastore.Get would return the full stack. In code, this would look |
178 // like: | 203 // like: |
179 // func HTTPHandler(r *http.Request) { | 204 // func HTTPHandler(r *http.Request) { |
180 // c := prod.Use(appengine.NewContext(r)) // production datastore | 205 // c := prod.UseRequest(r)» » » » » » »
// production datastore |
181 // c, rawCount := count.FilterRDS() // add count filter | 206 // c, rawCount := count.FilterRDS(c) // add count filter |
182 // c = mcache.FilterRDS(c) // add mcache filter | 207 // c = dscache.FilterRDS(c) // add dscache filter |
183 // c, userCount := count.FilterRDS() // add another count filter | 208 // c, userCount := count.FilterRDS(c) // add another count filter |
184 // } | 209 // } |
185 // | 210 // |
186 // Filters may or may not have state, it's up to the filter itself. In the case | 211 // Filters may or may not have state, it's up to the filter itself. In the case |
187 // of the count filter, it returns its state from the Filter<Service> method, | 212 // of the count filter, it returns its state from the Filter<Service> method, |
188 // and the state can be observed to see how many times each API was invoked. | 213 // and the state can be observed to see how many times each API was invoked. |
189 // Since filters stack, we can compare counts from rawCount versus userCount to | 214 // Since filters stack, we can compare counts from rawCount versus userCount to |
190 // see how many calls to the actual real datastore went through, vs. how many | 215 // see how many calls to the actual real datastore went through, vs. how many |
191 // went to memcache, for example. | 216 // went to memcache, for example. |
| 217 // |
| 218 // Note that Filters apply only to the service.RawInterface. All implementations |
| 219 // of service.Interface boil down to calls to service.RawInterface methods, but |
| 220 // it's possible that bad calls to the service.Interface methods could return |
| 221 // an error before ever reaching the filters or service implementation. |
192 package gae | 222 package gae |
OLD | NEW |