Skip to content
This repository was archived by the owner on Apr 23, 2021. It is now read-only.

Commit 9be28a8

Browse files
authored
Improve documentation & test coverage (#29)
1 parent 613b53e commit 9be28a8

14 files changed

+326
-72
lines changed

Package.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,23 @@ import PackageDescription
44
let package = Package(
55
name: "swift-baggage-context",
66
products: [
7-
.library(name: "Baggage",
7+
.library(
8+
name: "Baggage",
89
targets: [
9-
"Baggage"
10+
"Baggage",
1011
]
1112
),
12-
.library(name: "BaggageLogging",
13+
.library(
14+
name: "BaggageLogging",
1315
targets: [
14-
"BaggageLogging"
16+
"BaggageLogging",
1517
]
1618
),
1719
],
1820
dependencies: [
19-
.package(url: "https://github.com/apple/swift-log.git", from: "1.3.0")
21+
.package(url: "https://github.com/apple/swift-log.git", from: "1.3.0"),
2022
],
2123
targets: [
22-
2324
.target(
2425
name: "Baggage",
2526
dependencies: []
@@ -39,15 +40,15 @@ let package = Package(
3940
.testTarget(
4041
name: "BaggageTests",
4142
dependencies: [
42-
"Baggage"
43+
"Baggage",
4344
]
4445
),
4546

4647
.testTarget(
4748
name: "BaggageLoggingTests",
4849
dependencies: [
4950
"Baggage",
50-
"BaggageLogging"
51+
"BaggageLogging",
5152
]
5253
),
5354

@@ -60,7 +61,7 @@ let package = Package(
6061
"Baggage",
6162
"BaggageLogging",
6263
"BaggageBenchmarkTools",
63-
]
64+
]
6465
),
6566
.target(
6667
name: "BaggageBenchmarkTools",

README.md

Lines changed: 128 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
# Baggage Context
22

33
[![Swift 5.2](https://img.shields.io/badge/Swift-5.2-ED523F.svg?style=flat)](https://swift.org/download/)
4+
[![Swift 5.1](https://img.shields.io/badge/Swift-5.1-ED523F.svg?style=flat)](https://swift.org/download/)
5+
[![Swift 5.0](https://img.shields.io/badge/Swift-5.0-ED523F.svg?style=flat)](https://swift.org/download/)
46
[![CI](https://github.com/slashmo/gsoc-swift-baggage-context/workflows/CI/badge.svg)](https://github.com/slashmo/gsoc-swift-baggage-context/actions?query=workflow%3ACI)
57

6-
`BaggageContext` is a minimal (zero-dependency) "context" library meant to "carry" baggage (metadata) for cross-cutting tools such as tracers.
7-
It is purposefully not tied to any specific use-case (in the spirit of the [Tracing Plane paper](https://cs.brown.edu/~jcmace/papers/mace18universal.pdf)'s BaggageContext), however it should enable a vast majority of use cases cross-cutting tools need to support. Unlike mentioned in the paper, our `BaggageContext` does not implement its own serialization scheme (today).
8+
`BaggageContext` is a minimal (zero-dependency) "context" library meant to "carry" baggage (metadata) for cross-cutting
9+
tools such as tracers. It is purposefully not tied to any specific use-case (in the spirit of the
10+
[Tracing Plane paper](https://cs.brown.edu/~jcmace/papers/mace18universal.pdf)'s BaggageContext). However, it should
11+
enable a vast majority of use cases cross-cutting tools need to support. Unlike mentioned in the paper, our
12+
`BaggageContext` does not implement its own serialization scheme (today).
813

9-
See https://github.com/slashmo/gsoc-swift-tracing for actual instrument types and implementations which can be used to deploy various cross-cutting instruments all reusing the same baggage type. More information can be found in the [SSWG meeting notes](https://gist.github.com/ktoso/4d160232407e4d5835b5ba700c73de37#swift-baggage-context--distributed-tracing).
14+
See https://github.com/slashmo/gsoc-swift-tracing for actual instrument types and implementations which can be used to
15+
deploy various cross-cutting instruments all reusing the same baggage type. More information can be found in the
16+
[SSWG meeting notes](https://gist.github.com/ktoso/4d160232407e4d5835b5ba700c73de37#swift-baggage-context--distributed-tracing).
1017

1118
## Installation
1219

13-
You can install the `BaggageContext` library through the Swift Package Manager. The library itself is called `Baggage`, so that's what you'd import in your Swift files.
20+
You can install the `BaggageContext` library through the Swift Package Manager. The library itself is called `Baggage`,
21+
so that's what you'd import in your Swift files.
1422

1523
```swift
1624
dependencies: [
@@ -22,14 +30,92 @@ dependencies: [
2230
]
2331
```
2432

33+
## Usage
34+
35+
`BaggageContext` is intended to be used in conjunction with the instrumentation of distributed systems. To make this
36+
instrumentation work, all parties involved operate on the same `BaggageContext` type. These are the three common
37+
parties, in no specific order, and guidance on how to use `BaggageContext`:
38+
39+
### End Users - explicit context passing
40+
41+
You'll likely interact with some API that takes a context. In most cases you already have a context at hand so you
42+
should pass that along. If you're certain you don't have a context at hand, pass along an empty one after thinking about
43+
why that's the case.
44+
45+
**TODO**: Document the reasoning behind `.background` & `.TODO` once merged ([#26](#26))
46+
47+
While this might seem like a burden to take on, this will allow you to immediately add instrumentation (e.g. tracing)
48+
once your application grows. Let's say your profiling some troublesome performance regressions. You won't have the time
49+
to go through the entire system to start passing contexts around.
50+
51+
> TL;DR: You should always pass around `BaggageContext`, so that you're ready for when you need it.
52+
53+
Once you are ready to instrument your application, you already have everything in place to get going. Instead of each
54+
instrument operating on its own context type they'll be using the same `BaggageContext` that you're already passing
55+
around to the various instrumentable libraries & frameworks you make use of, so you're free to mix & match any
56+
compatible instrument(s) 🙌 Check out the [swift-tracing](https://github.com/slashmo/gsoc-swift-tracing) repository for
57+
instructions on how to get up & running.
58+
59+
### Library & Framework Authors - passing context and instrumenting libraries
60+
61+
Developers creating frameworks/libraries (e.g. NIO, gRPC, AsyncHTTPClient, ...) which benefit from being instrumented
62+
should adopt `BaggageContext` as part of their public API. AsyncHTTPClient for example might accept a context like this:
63+
64+
```swift
65+
let context = BaggageContext()
66+
client.get(url: "https://swift.org", context: context)
67+
```
68+
69+
For more information on where to place this argument and how to name it, take a look at the
70+
[Context-Passing Guidelines](#Context-Passing-Guidelines).
71+
72+
Generally speaking, frameworks and libraries should treat baggage as an _opaque container_ and simply thread it along
73+
all asynchronous boundaries a call may have to go through. Libraries and frameworks should not attempt to reuse context
74+
as a means of passing values that they need for "normal" operation.
75+
76+
At cross-cutting boundaries, e.g. right before sending an HTTP
77+
request, they inject the `BaggageContext` into the HTTP headers, allowing context propagation. On the receiving side, an
78+
HTTP server should extract the request headers into a `BaggageContext`. Injecting/extracting is part of the
79+
`swift-tracing` libraries [and documented in its own repository](https://github.com/slashmo/gsoc-swift-tracing).
80+
81+
### Instrumentation Authors - defining, injecting and extracting baggage
82+
83+
When implementing instrumentation for cross-cutting tools, `BaggageContext` becomes the way you propagate metadata such
84+
as trace ids. Because each instrument knows what values might be added to the `BaggageContext` they are the ones
85+
creating `BaggageContextKey` types dictating the type of value associated with each key added to the context. To make
86+
accessing values a bit more convenient, we encourage you to add computed properties to `BaggageContextProtocol`:
87+
88+
```swift
89+
private enum TraceIDKey: BaggageContextKey {
90+
typealias Value = String
91+
}
92+
93+
extension BaggageContextProtocol {
94+
var traceID: String? {
95+
get {
96+
return self[TraceIDKey.self]
97+
}
98+
set {
99+
self[TraceIDKey.self] = newValue
100+
}
101+
}
102+
}
103+
104+
var context = BaggageContext()
105+
context.traceID = "4bf92f3577b34da6a3ce929d0e0e4736"
106+
print(context.traceID ?? "new trace id")
107+
```
108+
25109
## Context-Passing Guidelines
26110

27-
In order for context-passing to feel consistent and Swifty among all server-side (and not only) libraries and frameworks
28-
aiming to adopt `BaggageContext` (or any of its uses, such as Distributed Tracing), we suggest the following set of guidelines:
111+
For context-passing to feel consistent and Swifty among all server-side (and not only) libraries and frameworks
112+
aiming to adopt `BaggageContext` (or any of its uses, such as Distributed Tracing), we suggest the following set of
113+
guidelines:
29114

30115
### Argument naming/positioning
31116

32-
In order to propagate baggage through function calls (and asynchronous-boundaries it may often be necessary to pass it explicitly (unless wrapper APIs are provided which handle the propagation automatically).
117+
Propagating baggage context through your system is to be done explicitly, meaning as a parameter in function calls,
118+
following the "flow" of execution.
33119

34120
When passing baggage context explicitly we strongly suggest sticking to the following style guideline:
35121

@@ -38,16 +124,20 @@ When passing baggage context explicitly we strongly suggest sticking to the foll
38124
2. Defaulted non-function parameters (e.g. `(mode: Mode = .default)`),
39125
3. Required function parameters, including required trailing closures (e.g. `(onNext elementHandler: (Value) -> ())`),
40126
4. Defaulted function parameters, including optional trailing closures (e.g. `(onComplete completionHandler: (Reason) -> ()) = { _ in }`).
41-
- Baggage Context should be passed as: **the last parameter in the required non-function parameters group in a function declaration**.
127+
- Baggage Context should be passed as **the last parameter in the required non-function parameters group in a function declaration**.
42128

43-
This way when reading the call side, users of these APIs can learn to "ignore" or "skim over" the context parameter and the method signature remains human-readable and “Swifty”.
129+
This way when reading the call side, users of these APIs can learn to "ignore" or "skim over" the context parameter and
130+
the method signature remains human-readable and “Swifty”.
44131

45132
Examples:
46133

47134
- `func request(_ url: URL,` **`context: BaggageContext`** `)`, which may be called as `httpClient.request(url, context: context)`
48135
- `func handle(_ request: RequestObject,` **`context: BaggageContextCarrier`** `)`
49-
- if a "framework context" exists and _carries_ the baggage context already, it is permitted to pass that context together with the baggage;
50-
- it is _strongly recommended_ to store the baggage context as `baggage` property of `FrameworkContext` in such cases, in order to avoid the confusing spelling of `context.context`, and favoring the self-explanatory `context.baggage` spelling when the baggage is contained in a framework context object.
136+
- if a "framework context" exists and _carries_ the baggage context already, it is permitted to pass that context
137+
together with the baggage;
138+
- it is _strongly recommended_ to store the baggage context as `baggage` property of `FrameworkContext` in such cases,
139+
in order to avoid the confusing spelling of `context.context`, and favoring the self-explanatory `context.baggage`
140+
spelling when the baggage is contained in a framework context object.
51141
- `func receiveMessage(_ message: Message, context: FrameworkContext)`
52142
- `func handle(element: Element,` **`context: BaggageContextCarrier`** `, settings: Settings? = nil)`
53143
- before any defaulted non-function parameters
@@ -58,29 +148,45 @@ Examples:
58148
In case there are _multiple_ "framework-ish" parameters, such as passing a NIO `EventLoop` or similar, we suggest:
59149

60150
- `func perform(_ work: Work, for user: User,` _`frameworkThing: Thing, eventLoop: NIO.EventLoop,`_ **`context: BaggageContext`** `)`
61-
- pass the baggage as **last** of such non-domain specific parameters as it will be _by far more_ omnipresent than any specific framework parameter - as it is expected that any framework should be accepting a context if it is able to do so. While not all libraries are necessarily going to be implemented using the same frameworks.
151+
- pass the baggage as **last** of such non-domain specific parameters as it will be _by far more_ omnipresent than any
152+
specific framework parameter - as it is expected that any framework should be accepting a context if it can do so.
153+
While not all libraries are necessarily going to be implemented using the same frameworks.
62154

63-
We feel it is important to preserve Swift's human-readable nature of function definitions. In other words, we intend to keep the read-out-loud phrasing of methods to remain _"request that url (ignore reading out loud the context parameter)"_ rather than _"request (ignore this context parameter when reading) that url"_.
155+
We feel it is important to preserve Swift's human-readable nature of function definitions. In other words, we intend to
156+
keep the read-out-loud phrasing of methods to remain _"request that URL (ignore reading out loud the context parameter)"_
157+
rather than _"request (ignore this context parameter when reading) that URL"_.
64158

65159
#### When to use what context type?
66160

67161
This library defines the following context (carrier) types:
68162

69163
- `struct BaggageContext` - which is the actual context object,
70-
- `protocol BaggageContextCarrier` - which should be used whenever a library implements an API and does not necessarily care where it gets a `context` value from
71-
- this pattern enables other frameworks to pass their `FrameworkContext`, like so: `get(context: MyFrameworkContext())` if they already have such context in scope (e.g. Vapor's `Request` object is a good example, or Lambda Runtime's `Lambda.Context`
164+
- `protocol BaggageContextCarrier` - which should be used whenever a library implements an API and does not necessarily
165+
care where it gets a `context` value from
166+
- this pattern enables other frameworks to pass their `FrameworkContext`, like so:
167+
`get(context: MyFrameworkContext())` if they already have such context in scope (e.g. Vapor's `Request` object is a
168+
good example, or Lambda Runtime's `Lambda.Context`
72169
- `protocol LoggingBaggageContextCarrier` - which in addition exposes a logger bound to the passed context
73170

74-
Finally, some frameworks will have APIs which accept the specific `MyFrameworkContext`, withing frameworks specifically a lot more frequently than libraries one would hope. It is important when designing APIs to keep in mind -- can this API work with any context, or is it always going to require _my framework context_, and erring on accepting the most general type possible.
171+
Finally, some frameworks will have APIs which accept the specific `MyFrameworkContext`, withing frameworks specifically
172+
a lot more frequently than libraries one would hope. It is important when designing APIs to keep in mind -- can this API
173+
work with any context, or is it always going to require _my framework context_, and erring on accepting the most
174+
general type possible.
75175

76176
#### Existing context argument
77177

78-
When adapting an existing library/framework to support `BaggageContext` and it already has a "framework context" which is expected to be passed through "everywhere", we suggest to follow these guidelines to adopting BaggageContext:
79-
80-
1. Add a `BaggageContext` as a property called `baggage` to your own `context` type, so that the call side for your users becomes `context.baggage` (rather than the confusing `context.context`)
81-
2. If you cannot or it would not make sense to carry baggage inside your framework's context object, pass (and accept (!)) the `BaggageContext` in your framework functions like follows:
82-
- if they take no framework context, accept a `context: BaggageContext` which is the same guideline as for all other cases
83-
- if they already _must_ take a context object and you are out of words (or your API already accepts your framework context as "context"), pass the baggage as **last** parameter (see above) yet call the parameter `baggage` to disambiguate your `context` object from the `baggage` context object.
178+
When adapting an existing library/framework to support `BaggageContext` and it already has a "framework context" which
179+
is expected to be passed through "everywhere", we suggest to follow these guidelines for adopting BaggageContext:
180+
181+
1. Add a `BaggageContext` as a property called `baggage` to your own `context` type, so that the call side for your
182+
users becomes `context.baggage` (rather than the confusing `context.context`)
183+
2. If you cannot or it would not make sense to carry baggage inside your framework's context object,
184+
pass (and accept (!)) the `BaggageContext` in your framework functions like follows:
185+
- if they take no framework context, accept a `context: BaggageContext` which is the same guideline as for all other
186+
cases
187+
- if they already _must_ take a context object and you are out of words (or your API already accepts your framework
188+
context as "context"), pass the baggage as **last** parameter (see above) yet call the parameter `baggage` to
189+
disambiguate your `context` object from the `baggage` context object.
84190

85191
Examples:
86192

0 commit comments

Comments
 (0)