-
Notifications
You must be signed in to change notification settings - Fork 256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add better OTel information into Brighter #3096
Changes from all commits
bbd8a50
3d90bcb
f0389f0
d53f219
5fb36d1
1bb44cf
a800131
d20f83d
55b6668
a441496
8ad0ee7
4dddacd
5fac6d0
9b9c880
035a696
0116558
d30d0ca
12e6ddc
a69ec09
e1159b3
fe3938d
c8c918f
44a37bc
1b73ed8
3b3b511
761659e
e8e40dc
86cb6f8
772fe37
bff43ea
fc80d94
04553ec
37ab0ba
3a4d9e6
18c4737
5212ea7
dea427f
674df49
487b73e
4e3768c
2ae8252
2c5d155
c310cf9
1bf34c7
387585e
c46bc59
53aa14d
fa66b5c
0f6fed2
6716840
3b4173a
60cbbcc
3bc7c45
b9b8aed
ab0e0d0
4b7d7e4
ad919a9
d6638e3
54d625a
3708e1a
e086b58
a08f006
13390d3
3e3b9f0
e7af155
1d2206a
c629438
35f490e
231669a
2cb24d0
a86008c
50cf272
9508300
3a34d53
e633fab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# 11. Support for Bulk Messaging Operations | ||
|
||
Date: 2019-08-01 | ||
|
||
## Status | ||
|
||
Proposed | ||
|
||
## Context | ||
|
||
To support Transactional Messaging the Command Processor uses an Outbox when producing a message via an external bus. This means that the CommandProcessor has two associated calls: | ||
|
||
* A `DepositPost` call to translate the `IRequest` into a `Message` and store it in an `Outbox` | ||
* A `ClearOutbox` call to retrieve the `Message` from the `Outbox` and dispatch it via a `Producer` | ||
|
||
Within these two steps we perform a number of activities. | ||
|
||
For `DepositPost` we: | ||
|
||
* Lookup the `IAmAProducer` associated with a `Request` | ||
* Translate an `IRequest` into the `Message`, executing the `IAmAMessageTransform` and `IAmAMessageMapper` pipeline | ||
* Store the resulting `Message` in an `Outbox` | ||
|
||
For `ClearOutbox` we: | ||
|
||
* Retrieve the `Message` from the `Outbox` | ||
* Lookup the `IAmAProducer` associated with a `Request` | ||
* We then produce the `Message` via the `Producer` | ||
|
||
For the sake of efficiency we chose to support passing an array of `IRequest` to `DepositPost`. We want to bulk write this set of messages to the Outbox, over writing each one individually. For this reason we would like to be able to batch up our writes to the `Outbox` in `DepositPost` | ||
|
||
We always pass an array of `Message` identifiers to `ClearOutbox`. For efficiency we obtain the set of matching messages by id from the `Outbox` | ||
|
||
A question arises from the relationship between the single `DepositPost` and the bulk `DepositPost`. | ||
|
||
The bulk version of `DepositPost` requires two modifications to the deposit behavior: | ||
|
||
* As it is an `IEnumerable<IRequest>` the collection passed to the bulk `DepositPost` does not bind a generic parameter of the derived type of `IRequest` that we can use to lookup the associated mapper/transform pipeline and producer. | ||
* As it is a batch, we want to accumulate all the writes to the `Outbox` over making each one individually. | ||
|
||
In addition though we would like to re-use the `Deposit Post` functionality as far as possible to avoid divergent code paths reducing modifiability such as when adding OTel or fixing bugs. | ||
|
||
In V9 our solution was as follows: | ||
|
||
* Bucket any batch of `IRequest` by their type | ||
* By batch late bind a `BulkMessageMap` and then run it through the collection | ||
* Feed the resulting set of messages to a bulk version of the `Outbox` via `AddToOutbox` | ||
|
||
The main issue with this approach is that the flow for `DepositPost` differs between bulk and single versions, which makes it more complex to modify. | ||
|
||
## Decision | ||
|
||
It is worth noting the following: | ||
|
||
* The `BulkMessageMap` is a bulk, iterative operation; it's main purpose is late-binding of the type from an `IEnumerable<IRequest>` | ||
* Producing a `Message` via a `Producer` is not a bulk operation in Brighter | ||
|
||
The only bulk operations we have in the flow are the read and write from the `Outbox` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In v9 there is also a Bulk Mark Dispatches via the MarkDispatchedAsync(IEnumerable ids, ...) method on the relational outbox There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, we won't have changed that |
||
|
||
* All reads from the `Outbox` are a bulk operation in V9, that iterate over the `Message` collection and pass it to the `Producer` | ||
|
||
It is possible for us to late bind the bulk `DepositPost` to a single `DepositPost` call using reflection to invoke the method. This would resolve the binding issue. We can tag the method with an attribute to indicate that it may be called via late binding for documentation and to simplify the reflection code. | ||
|
||
This leaves us with the main difference being the bulk write to the `Outbox` | ||
|
||
We have two alternatives here: | ||
|
||
* Always use a bulk `Add` method on the `Outbox`; as with `Clear` simply treat the first parameter as a collection of `IRequest` | ||
* Provide a mechanism to indicate that the `DepositPost` should be considered as part of a batch. | ||
|
||
Whilst the first seems simpler, two complications arise. The first is the need to do late-binding, the second is the different semantics for a bulk operation in OTel (this might also push us to add a single `Clear` operation and call that repeatedly from a batch parent). | ||
|
||
So in this case we intend to opt for the second option. | ||
|
||
* In a bulk `DepositPost`, where we have a batch, begin a new batch via `StartBatch` that returns a `BatchId` | ||
* Pass an optional `BatchId` to a late bound call to the single `IRequest` version of `DepositPost` (which provides late binding) | ||
* Where we have an optional `BatchId` within the individual `DepositPost` call `AddToBatch` on the `Outbox` instead of `Add` | ||
* In the batch `DepositPost` call `EndBatch` to write the batch | ||
|
||
Both `StartBatch` and `EndBatch` are `Outbox` code. As they will be shared by all `Outbox` types they need to be implemented in an abstract base class. | ||
|
||
`EndBatch` may need to group the writes by `RoutingKey` as a batch endpoint might be called for multiple topics. | ||
|
||
## Consequences | ||
|
||
* We have a single version of `DepositPost` for the pipeline, but we can call it as part of a batch | ||
* We should consider having a single message id `Clear` that does not create a batch span |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# 12. Provide In-Memory and Simple Implementations | ||
|
||
Date: 2019-08-01 | ||
|
||
## Status | ||
|
||
Accepted | ||
|
||
## Context | ||
|
||
Brighter provides interfaces to: | ||
|
||
* Transports such as `IAmAProducer` and `IAmAMessageConsumer` | ||
* Outboxes such as `IAmAnOutbox` or Inboxes such as `IAmAnInbox` | ||
* Factories such as `IAmAHandlerFactory` | ||
|
||
All of these abstractions allow us or end users to extend Brighter. | ||
|
||
When writing tests it is useful to have implementations of these that provide a simple, or in-memory, version of the functionality. A typical option is to provide a Test Double, such as a Fake, Stub or Mock version within our test suite, with simple functionality tailored to that test, for example a FakeProducer; we also might provide a Simple version of a factory such that a test does not need to use a full IoC container to run. | ||
|
||
But we have a number of problems from this strategy: | ||
|
||
* These types are not exposed to end users, who might wish to use them in their own tests | ||
* We do not have fully functioning in memory versions of these, with their own tests, so they may alter the behavior of types for whom they are a dependency when under test. | ||
|
||
## Decision | ||
|
||
We should provide in-memory or simple versions of libraries that intend to use to extend Brighter. These should have tests in `paramore.brighter.inmemorytests` that prove they provide standard functionality. They should be usable by end users for writing their own tests. | ||
|
||
An incomplete list of these include: | ||
|
||
* `SimpleHandlerFactoryAsync` | ||
* `SimpleHandlerFactorySync` | ||
* `SimpleMessageMapperFactory` | ||
* `SimpleMessagemapperFactoryAsync` | ||
* `SimpleMessageTransformerFactory` | ||
* `SimpleMessageTransformerFactoryAsync` | ||
* `InMemoryOutbox` | ||
* `InMemoryInbox` | ||
* `InMemoryProducer` | ||
* `InMemoryConsumer` | ||
* `InMemoryArchiveProvider` | ||
|
||
As we review tests we should look for where we might want to promote useful test doubles into lightweight implementations that can be used for testing. | ||
|
||
Similar examples in dotnet include `FakeTimeProvider` which helps with Time related tests. I would tend to avoid the `Fake` name over In-Memory or Simple as this is our branding for those helpers. | ||
|
||
## Consequences | ||
|
||
In some cases it may be appropriate to use the in-memory or simple versions as an internal default when no override is provided, as this enables parts of Brighter to rely on their existence, even if the user does not provide them. | ||
|
||
In tests always prefer the usage of these in-memory or simple classes over new Test Doubles. Only use a Test Double where you want to simulate error conditions, or would have to adjust the normal behavior of the in-memory or simple version to test a specific case. The purpose of the in-memory or simple versions is to provide a standard, simple, implementation so it would subvert that to force it to behave differently just to accomodate tests. | ||
|
||
This means we have removed some common test doubles such as FakeProducer and FakeOutbox for their in-memory or simple versions. | ||
|
||
We occassionally have large PRs because a change has to flow through all the transports or outbox/inboxes. With this strategy we can use the in-memory or simple versions in the core tests to 'prove' the approach, with only signature changes needed for others (which may be fake). We can then merge that and proceed to the concrete implementations, one-by-one. |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Brighter v9 does support Bulk clearing via the
IAmABulkMessageProducerAsync
interfaceThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take a look, although we have not made changes to clear along these lines yet