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