This repository has been archived by the owner on Feb 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 32
Event Processor SDK #48
Open
peterschwarz
wants to merge
2
commits into
hyperledger-archives:main
Choose a base branch
from
peterschwarz:sde-sdk-rfc
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
- Event Processor SDK | ||
- Start Date: 2019-07-02 | ||
- RFC PR: (leave this empty) | ||
- Sawtooth Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
The Event Processor SDK is an extension to the Sawtooth SDK that provides a | ||
simplified abstraction to consume events generated on block commits by the | ||
Sawtooth validator. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
The events emitted by the validator are an incredibly useful tool for pulling | ||
data off-chain, but the current SDK does not provide a straight-forward way to | ||
subscribe and receive events. This API provides an analogous framework to the | ||
transaction processors by removing the low-level transport and messaging | ||
details. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
The Event Processor SDK provides APIs for generating subscriptions, a handler | ||
interface for events, and an event processor for managing messaging and | ||
dispatching events to handlers. | ||
|
||
Subscriptions are generated through a subscription builder. The protocol | ||
requirements and format are isolated behind this abstraction. The builder is | ||
used to specify the event types and their filters. | ||
|
||
The event handler interface handles a subset of events received. It specifies | ||
the names of events of interest. It is provided the events of interest by the | ||
event processor. Each handler should manage its own state, such as connections | ||
to databases or log files. | ||
|
||
The event processor takes a validator endpoint, a set of subscriptions, and a | ||
set of event handlers. The event processor manages connecting to the validator, | ||
submitting the subscriptions, and receiving the event messages. As events are | ||
received, the event processor finds the appropriate handlers for the given set | ||
of events, and provides the events of interest to each handler. | ||
|
||
Unlike transaction handlers, there may be multiple event handlers that are | ||
interested in specific event types. For example, there may be a subscription | ||
for `sawtooth/block-commit`, `sawtooth/state-delta`, and `my-app/custom-event`. | ||
A handler may be created that handles the `block-commit` and `state-delta` | ||
events, and writes the state values to a | ||
[slowly-changing-dimensions](https://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2:_add_new_row) | ||
database. A second handler may handle the `block-commit` and `custom-event` and | ||
forward the values to an external queue system, such as Apache Kafka. | ||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
## Subscription Builders | ||
[subscription-builders]: #subscription-builders | ||
|
||
The subscription builder API provides a clean abstraction for specifying | ||
Sawtooth event subscriptions. | ||
|
||
The following API is presented in Rust, but is applicable to all of the SDKs. | ||
The builders are not traits, in this case, but the implementation of the structs | ||
and the functions are omitted here. | ||
|
||
```rust | ||
impl SubscriptionBuilder { | ||
fn with_last_known_block_ids(&mut self, block_ids: Vec<String>) | ||
-> Self | ||
{ ... } | ||
|
||
fn with_event(&mut self, event_filter: EventFilter) | ||
-> Self | ||
{ ... } | ||
|
||
fn build(self) | ||
-> Result<Subscription, SubscriptionBuildError> | ||
{ ... } | ||
} | ||
|
||
enum FilterConstraint { | ||
Any, | ||
All, | ||
} | ||
|
||
impl EventFilterBuilder { | ||
fn with_event_type(&mut self, event_type: String) | ||
-> Self | ||
{ ... } | ||
|
||
fn with_regex_filter( | ||
&mut self, | ||
key: String, | ||
regex: String, | ||
constraint: FilterConstraint | ||
) -> Self | ||
{...} | ||
|
||
fn with_simple_filter( | ||
&mut self, | ||
key: String, | ||
value: String, | ||
constraint: FilterConstraint | ||
) -> Self | ||
{...} | ||
|
||
fn build(self) -> Result<EventFilter, EventFilterBuildError> | ||
{ ... } | ||
} | ||
``` | ||
|
||
Subscriptions (`Subscription` instances) are made up of a series of event | ||
filters (`EventFilter` instances). These filters match on specific key/value | ||
pairs, either through an exact match or a regular expressions. The filters may | ||
apply to all match keys in an event or any, as specified by the | ||
`FilterConstraint` set when the filter is created. | ||
|
||
## Event Handlers | ||
[event-handlers]: #event-handler | ||
|
||
The event handler interface is the core piece that SDK consumers use to | ||
implement their business logic. It is analogous to the transaction handler API. | ||
The main difference between them is that it doesn't necessarily have to be | ||
deterministic. | ||
|
||
Events are handled as a group. Many events provide only a slice of the | ||
information that occurs at block commit time. For example, `block-commit` and | ||
`state-delta` events are often handled in pairs, when connecting to a validator | ||
network that is using a lottery-style consensus. | ||
|
||
The events passed to the handler are guaranteed to have occurred within a single | ||
block. | ||
|
||
The following API is presented in Rust, but is applicable to all of the SDKs. | ||
|
||
```rust | ||
trait EventHandler { | ||
fn accepts_event_types(&self) -> &[&str]; | ||
fn handle(&self, events: &[Event]) -> Result<(), HandleEventError>; | ||
} | ||
|
||
enum HandleEventError { | ||
Internal(Box<dyn std::error::Error>), | ||
} | ||
``` | ||
|
||
The `accepts_event_types` function returns the list of events that the handler | ||
will accept. The events provided to the handler are merely references, as | ||
multiple handlers may read the data contained in the same event. | ||
|
||
## Event Processor | ||
[event-processor]: #event-processor | ||
|
||
The event processor uses the subscriptions, the handlers and a validator | ||
component endpoint to connect, subscribe and process event messages. It is | ||
analogous to the transaction processor. Its main difference is that it may | ||
dispatch the same event to multiple handlers. | ||
|
||
The following API is presented in Rust, but is applicable to all of the SDKs. | ||
The event processor is not a trait in this case, but the implementation of the | ||
struct and the functions are omitted here. | ||
|
||
```rust | ||
struct EventProcessor | ||
{ ... } | ||
|
||
impl EventProcessor { | ||
pub fn new(validator_endpoint: String) -> Self | ||
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. [nitpick]: Missing {} has broken the syntax highlighter 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. fixed |
||
{ ... } | ||
|
||
pub fn add_subscription(&mut self, subscription: Subscription) | ||
-> Result<(), EventProcessorError> | ||
{ ... } | ||
|
||
pub fn add_event_handler(&mut self, event_handler: Box<dyn EventHandler>) | ||
-> Result<(), EventProcessorError> | ||
{ ... } | ||
|
||
pub fn start(self) | ||
-> Result<ShutdownHandle, EventProcessorError> | ||
{ ... } | ||
} | ||
|
||
struct ShutdownHandle | ||
{ ... } | ||
|
||
impl ShutdownHandle { | ||
pub fn shutdown(mut self) -> Result<(), EventProcessorError> | ||
{ ... } | ||
} | ||
``` | ||
|
||
A `EventProcessorError` indicates errors when adding a service or starting the | ||
processor. The `ShutdownHandle` returned from start blocks until any background | ||
resources have been terminated | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
The only drawback is the fact this extension to the SDKs should be implemented | ||
in all the current first-tier SDKs. It has increased maintenance costs. | ||
|
||
# Rationale and alternatives | ||
[alternatives]: #alternatives | ||
|
||
The benefit of providing as simple API for event processing as is provided for | ||
transaction execution is obvious. Much of the positive feedback from the | ||
community is based around the ease of creating applications at the state and | ||
transaction level. This adds a third leg in the platform by providing an easy | ||
way to offload state to external databases with richer query capabilities. | ||
|
||
The alternative is continuing to maintain the current stream and protobuf | ||
message method for subscribing to and receiving events. Improvements to | ||
documentation could improve adoption of the event system. However, this current | ||
method still would be clumsy. | ||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
Much of the concepts described here are inspired by the current Sawtooth | ||
transaction processor API in the SDK. | ||
|
||
A similar, though not complete implementation of this API was implemented as | ||
part of Hyperledger Grid's state delta export capabilities. Similarly, the | ||
builder patterns used for protocol objects is found in much of Grid. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
None. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
[Thinking out loud]: Can this be trait object passed to a function like "with_filter()", where is either regex or simple or something else coming up later.