OLD | NEW |
(Empty) | |
| 1 # Servicification Strategies |
| 2 |
| 3 This document captures strategies, hints, and best practices for solving typical |
| 4 challenges enountered when converting existing Chromium |
| 5 code to services. It is assumed that you have already read the high-level |
| 6 documentation on [what a service is](/services). |
| 7 |
| 8 If you're looking for Mojo documentation, please see the [general |
| 9 Mojo documentation](/mojo) and/or the [documentation on converting Chrome IPC to |
| 10 Mojo](/ipc). |
| 11 |
| 12 Note that throughout the below document we link to CLs to illustrate the |
| 13 strategies being made. Over the course of time code tends to shift, so it is |
| 14 likely that the code on trunk does not exactly match what it was at the time of |
| 15 the CLs. When necessary, use the CLs as a starting point for examining the |
| 16 current state of the codebase with respect to these issues (e.g., exactly where |
| 17 a service is embedded within the content layer). |
| 18 |
| 19 [TOC] |
| 20 |
| 21 ## Questions to Answer When Getting Started |
| 22 |
| 23 For the basic nuts and bolts of how to create a new service, see [the |
| 24 documentation on adding a new service](/services#Adding-a-new-service). This |
| 25 section gives questions that you should answer in order to shape the design of |
| 26 your service, as well as hints as to which answers make sense given your |
| 27 situation. |
| 28 |
| 29 ### Is your service global or per-BrowserContext? |
| 30 The Service Manager can either: |
| 31 |
| 32 - create one service instance per user ID or |
| 33 - field all connection requests for a given service via the same instance |
| 34 |
| 35 Which of these policies the Service Manager employs is determined by the |
| 36 contents of your service manifest: the former is the default, while the latter |
| 37 is selected by informing the Service Manager that your service requires the |
| 38 service_manager:all_users capability([example](https://cs.chromium.org/chromium/
src/services/device/manifest.json)). |
| 39 |
| 40 In practice, there is one user ID per-BrowserContext, so the question becomes: |
| 41 Is your Service a global or keyed by BrowserContext? In considering this |
| 42 question, there is one obvious hint: If you are converting per-Profile classes |
| 43 (e.g., KeyedServices), then your service is almost certainly going to be |
| 44 per-user. More generally, if you envision needing to use *any* state related to |
| 45 the user (e.g., you need to store files in the user's home directory), then your |
| 46 service should be per-user. |
| 47 |
| 48 Conversely, your service could be a good fit for being global if it is a utility |
| 49 that is unconcerned with the identity of the requesting client (e.g., the [data |
| 50 decoder service](/services/data_decoder), which simply decodes untrusted data in |
| 51 a separate process. |
| 52 |
| 53 ### Will you embed your service in //content, //chrome, or neither? |
| 54 |
| 55 At the start (and potentially even long-term), your service will likely not |
| 56 actually run in its own process but will rather be embedded in the browser |
| 57 process. This is especially true in the common case where you are converting |
| 58 existing browser-process code. |
| 59 |
| 60 You then have a question: Where should it be embedded? The answer to this |
| 61 question hinges on the nature and location of the code that you are converting: |
| 62 |
| 63 - //content is the obvious choice if you are converting existing //content code |
| 64 (e.g., the Device Service). Global services |
| 65 are embedded by [content::ServiceManagerContext](https://cs.chromium.org/chrom
ium/src/content/browser/service_manager/service_manager_context.cc?type=cs&q=Cre
ateDeviceService), |
| 66 while per-user services are naturally embedded by [content::BrowserContext](ht
tps://cs.chromium.org/chromium/src/content/browser/browser_context.cc?type=cs&q=
CreateFileService). |
| 67 |
| 68 - If your service is converting existing //chrome code, then you will need |
| 69 to embed your service in //chrome rather than //content. Global services |
| 70 are embedded by [ChromeContentBrowserClient](https://cs.chromium.org/chromium/
src/chrome/browser/chrome_content_browser_client.cc?type=cs&q=CreateMediaService
), |
| 71 while per-user services are embedded by [ProfileImpl](https://cs.chromium.org/
chromium/src/chrome/browser/profiles/profile_impl.cc?type=cs&q=CreateIdentitySer
vice). |
| 72 |
| 73 - If you are looking to convert all or part of a component (i.e., a feature in |
| 74 //components) into a service, the question arises of whether your new service |
| 75 is worthy of being in //services (i.e., is it a foundational service?). If |
| 76 not, then it can be placed in an appropriate subdirectory of the component |
| 77 itself. See this [email |
| 78 thread](https://groups.google.com/a/chromium.org/forum/#!topic/services-dev/3A
Jx3gjHbZE) and its [resulting CL](https://codereview.chromium.org/2832633002) |
| 79 for discussion of this point, and if in doubt, start a similar email thread |
| 80 discussing your feature. |
| 81 |
| 82 ### If your service is embedded in the browser process, what is its threading mo
del? |
| 83 |
| 84 If your service is embedded in the browser process, it will run on the IO thread |
| 85 by default. You can change that by specifying a task runner as part of the |
| 86 information for constructing your service. In particular, if the code that you |
| 87 are converting is UI-thread code, then you likely want your service running on |
| 88 the UI thread. Look at the changes to profile_impl.cc in [this |
| 89 CL](https://codereview.chromium.org/2753753007) to see an example of setting the |
| 90 task runner that a service should be run on as part of the factory for creating |
| 91 the service. |
| 92 |
| 93 ### What is your approach for incremental conversion? |
| 94 |
| 95 In creating your service, you likely have two goals: |
| 96 |
| 97 - Making the service available to other services |
| 98 - Making the service self-contained |
| 99 |
| 100 Those two goals are not the same, and to some extent are at tension: |
| 101 |
| 102 - To satisfy the first, you need to build out the API surface of the service to |
| 103 a sufficient degree for the anticipated use cases. |
| 104 |
| 105 - To satisfy the second, you need to convert all clients of the code that you |
| 106 are servicifying to instead use the service, and then fold that code into the |
| 107 internal implementation of the service. |
| 108 |
| 109 Whatever your goals, you will need to proceed incrementally if your project is |
| 110 at all non-trivial (as they basically all are given the nature of the effort). |
| 111 You should explicitly decide what your approach to incremental bringup and |
| 112 conversion will be. Here some approaches that have been taken for various |
| 113 services: |
| 114 |
| 115 - Build out your service depending directly on existing code, |
| 116 convert the clients of that code 1-by-1, and fold the existing code into the |
| 117 service implementation when complete ([Identity Service](https://docs.google.c
om/document/d/1EPLEJTZewjiShBemNP5Zyk3b_9sgdbrZlXn7j1fubW0/edit)). |
| 118 - Build out the service with new code and make the existing code |
| 119 into a client library of the service. In that fashion, all consumers of the |
| 120 existing code get converted transparently ([Preferences Service](https://docs.
google.com/document/d/1JU8QUWxMEXWMqgkvFUumKSxr7Z-nfq0YvreSJTkMVmU/edit#heading=
h.19gc5b5u3e3x)). |
| 121 - Build out the new service piece-by-piece by picking a given |
| 122 bite-size piece of functionality and entirely servicifying that functionality |
| 123 ([Device Service](https://docs.google.com/document/d/1_1Vt4ShJCiM3fin-leaZx00-
FoIPisOr8kwAKsg-Des/edit#heading=h.c3qzrjr1sqn7)). |
| 124 |
| 125 These all have tradeoffs: |
| 126 |
| 127 - The first lets you incrementally validate your API and implementation, but |
| 128 leaves the service depending on external code for a long period of time. |
| 129 - The second can create a self-contained service more quickly, but leaves |
| 130 all the existing clients in place as potential cleanup work. |
| 131 - The third ensures that you're being honest as you go, but delays having |
| 132 the breadth of the service API up and going. |
| 133 |
| 134 Which makes sense depends both on the nature of the existing code and on |
| 135 the priorities for doing the servicification. The first two enable making the |
| 136 service available for new use cases sooner at the cost of leaving legacy code in |
| 137 place longer, while the last is most suitable when you want to be very exacting |
| 138 about doing the servicification cleanly as you go. |
| 139 |
| 140 ## Platform-Specific Issues |
| 141 |
| 142 ### Android |
| 143 As you servicify code running on Android, you might find that you need to port |
| 144 interfaces that are served in Java. Here is an [example CL](https://codereview.c
hromium.org/2643713002) that gives a basic |
| 145 pattern to follow in doing this. |
| 146 |
| 147 You also might need to register JNI in your service. That is simple to set |
| 148 up, as illustrated in [this CL](https://codereview.chromium.org/2690963002). |
| 149 (Note that that CL is doing more than *just* enabling the Device Service to |
| 150 register JNI; you should take the register_jni.cc file added there as your |
| 151 starting point to examine the pattern to follow). |
| 152 |
| 153 Finally, it is possible that your feature will have coupling to UI process state |
| 154 (e.g., the Activity) via Android system APIs. To handle this challenging |
| 155 issue, see the section on [Coupling to UI](#Coupling-to-UI). |
| 156 |
| 157 ### iOS |
| 158 The high-level [servicification design doc](https://docs.google.com/document/d/1
5I7sQyQo6zsqXVNAlVd520tdGaS8FCicZHrN0yRu-oU/edit) gives the motivations and |
| 159 vision for supporting services on iOS (in particular, search for the mentions |
| 160 of iOS within the doc). |
| 161 |
| 162 Services are not *yet* supported, but this support is being actively being |
| 163 worked on; see [this bug](crbug.com/705982) for the current status. If you have |
| 164 a use case or need for services on iOS, contact blundell@chromium.org. |
| 165 |
| 166 ## Client-Specific Issues |
| 167 |
| 168 ### Services and Blink |
| 169 Connecting to services directly from Blink is fully supported. [This |
| 170 CL](https://codereview.chromium.org/2698083007) gives a basic example of |
| 171 connecting to an arbitrary service by name from Blink (look at the change to |
| 172 SensorProviderProxy.cpp as a starting point). |
| 173 |
| 174 Below, we go through strategies for some common challenges encountered when |
| 175 servicifying features that have Blink as a client. |
| 176 |
| 177 #### Mocking Interface Impls in JS |
| 178 It is a common pattern in Blink's layout tests to mock a remote Mojo interface |
| 179 in JS. [This CL](https://codereview.chromium.org/2643713002) illustrates the |
| 180 basic pattern for porting such mocking of an interface hosted by |
| 181 //content/browser to an interface hosted by an arbitrary service (see the |
| 182 changes to mock-battery-monitor.js). |
| 183 |
| 184 #### Feature Impls That Depend on Blink Headers |
| 185 In the course of servicifying a feature that has Blink as a client, you might |
| 186 encounter cases where the feature implementation has dependencies on Blink |
| 187 public headers (e.g., defining POD structs that are used both by the client and |
| 188 by the feature implementation). These dependencies pose a challenge: |
| 189 |
| 190 - Services should not depend on Blink, as this is a dependency inversion (Blink |
| 191 is a client of services). |
| 192 - However, Blink is very careful about accepting dependencies from Chromium. |
| 193 |
| 194 To meet this challenge, you have two options: |
| 195 |
| 196 1. Move the code in question from C++ to mojom (e.g., if it is simple structs). |
| 197 2. Move the code into the service's C++ client library, being very explicit |
| 198 about its usage by Blink. See [this CL](https://codereview.chromium.org/24150
83002) for a basic pattern to follow. |
| 199 |
| 200 #### Frame-Scoped Connections |
| 201 You must think carefully about the scoping of the connection being made |
| 202 from Blink. In particular, some feature requests are necessarily scoped to a |
| 203 frame in the context of Blink (e.g., geolocation, where permission to access the |
| 204 interface is origin-scoped). Servicifying these features is then challenging, as |
| 205 Blink has no frame-scoped connection to arbitrary services (by design, as |
| 206 arbitrary services have no knowledge of frames or even a notion of what a frame |
| 207 is). |
| 208 |
| 209 After a [long |
| 210 discussion](https://groups.google.com/a/chromium.org/forum/#!topic/services-dev/
CSnDUjthAuw), |
| 211 the policy that we have adopted for this challenge is the following: |
| 212 |
| 213 CURRENT |
| 214 |
| 215 - The renderer makes a request through its frame-scoped connection to the |
| 216 browser. |
| 217 - The browser obtains the necessary permissions before directly servicing the |
| 218 request. |
| 219 |
| 220 AFTER SERVICIFYING THE FEATURE IN QUESTION |
| 221 |
| 222 - The renderer makes a request through its frame-scoped connection to the |
| 223 browser. |
| 224 - The browser obtains the necessary permissions before forwarding the |
| 225 request on to the underlying service that hosts the feature. |
| 226 |
| 227 Notably, from the renderer's POV essentially nothing changes here. |
| 228 |
| 229 In the longer term, this will still be the basic model, only with "the browser" |
| 230 replaced by "the Navigation Service" or "the web permissions broker". |
| 231 |
| 232 ## Strategies for Challenges to Decoupling from //content |
| 233 |
| 234 ### Coupling to UI |
| 235 |
| 236 Some feature implementations have hard constraints on coupling to UI on various |
| 237 platforms. An example is NFC on Android, which requires the Activity of the view |
| 238 in which the requesting client is hosted in order to access the NFC platform |
| 239 APIs. This coupling is at odds with the vision of servicification, which is to |
| 240 make the service physically isolatable. However, when it occurs, we need to |
| 241 accommodate it. |
| 242 |
| 243 The high-level decision that we have reached is to scope the coupling to the |
| 244 feature *and* platform in question (rather than e.g. introducing a |
| 245 general-purpose FooServiceDelegate), in order to make it completely explicit |
| 246 what requires the coupling and to avoid the coupling creeping in scope. |
| 247 |
| 248 The basic strategy to support this coupling while still servicifying the feature |
| 249 in question is to inject a mechanism of mapping from an opaque "context ID" to |
| 250 the required context. The embedder (e.g., //content) maintains this map, and the |
| 251 service makes use of it. The embedder also serves as an intermediary: It |
| 252 provides a connection that is appropriately context-scoped to clients. When |
| 253 clients request the feature in question, the embedder forwards the request on |
| 254 along with the appropriate context ID. The service impl can then map that |
| 255 context ID back to the needed context on-demand using the mapping functionality |
| 256 injected into the service impl. |
| 257 |
| 258 To make this more concrete, see [this CL](https://codereview.chromium.org/273494
3003). |
| 259 |
| 260 ### Shutdown of singletons |
| 261 |
| 262 You might find that your feature includes singletons that are shut down as part |
| 263 of //content's shutdown process. As part of decoupling the feature |
| 264 implementation entirely from //content, the shutdown of these singletons must be |
| 265 either ported into your service or eliminated: |
| 266 |
| 267 - In general, as Chromium is moving away from graceful shutdown, the first |
| 268 question to analyze is: Do the singletons actually need to be shut down at |
| 269 all? |
| 270 - If you need to preserve shutdown of the singleton, the naive approach is to |
| 271 move the shutdown of the singleton to the destructor of your service |
| 272 - However, you should carefully examine when your service is destroyed compared |
| 273 to when the previous code was executing, and ensure that any differences |
| 274 introduced do not impact correctness. |
| 275 |
| 276 See [this thread](https://groups.google.com/a/chromium.org/forum/#!topic/service
s-dev/Y9FKZf9n1ls) for more discussion of this issue. |
| 277 |
| 278 ### Tests that muck with service internals |
| 279 It is often the case that browsertests reach directly into what will become part |
| 280 of the internal service implementation to either inject mock/fake state or to |
| 281 monitor private state. |
| 282 |
| 283 This poses a challenge: As part of servicification, *no* code outside the |
| 284 service impl should depend on the service impl. Thus, these dependencies need to |
| 285 be removed. The question is how to do so while preserving testing coverage. |
| 286 |
| 287 To answer this question, there are several different strategies. These |
| 288 strategies are not mutually-exclusive; they can and should be combined to |
| 289 preserve the full breadth of coverage. |
| 290 |
| 291 - Blink client-side behavior can be tested via [layout tests](https://codereview
.chromium.org/2731953003) |
| 292 - To test service impl behavior, create [service tests](https://codereview.chrom
ium.org/2774783003). |
| 293 - To preserve tests of end-to-end behavior (e.g., that when Blink makes a |
| 294 request via a Web API in JS, the relevant feature impl receives a connection |
| 295 request), we are planning on introducing the ability to register mock |
| 296 implementations with the Service Manager. |
| 297 |
| 298 To emphasize one very important point: it is in general necessary to leave |
| 299 *some* test of end-to-end functionality, as otherwise it is too easy for bustage |
| 300 to slip in via e.g. changes to how services are registered. See [this thread](ht
tps://groups.google.com/a/chromium.org/forum/#!topic/services-dev/lJCKAElWz-E) |
| 301 for further discussion of this point. |
OLD | NEW |