Artemis is a high-performance, event-driven framework for building applications that process streams of events through collectors, strategies, and executors. It provides a clean architecture for building reactive systems that can handle complex event processing workflows.
- Event-driven architecture: Built around the concept of event streams and reactive processing
- Modular design: Separate concerns with collectors, strategies, and executors
- High performance: Asynchronous processing with efficient resource utilization
- Type safety: Leverages Rust's type system for compile-time guarantees
- Flexible mapping: Built-in support for mapping between different event and action types
- Convenient macros: Simplify common patterns with provided macros
The framework consists of three main components:
Sources of events that implement the Collector trait. Collectors are responsible for gathering events from various sources (e.g., blockchain nodes, APIs, file systems) and feeding them into the processing pipeline.
Processing logic that transforms events into actions via the Strategy trait. Strategies contain your business logic and determine what actions should be taken in response to specific events.
Action handlers that implement the Executor trait. Executors are responsible for carrying out the actions determined by strategies (e.g., sending transactions, making API calls, writing to databases).
Add Artemis to your Cargo.toml:
[dependencies]
artemis = "0.1.0"
tokio = { version = "1.0", features = ["full"] }
eyre = "0.6"use artemis::{Engine, types::{Collector, Strategy, Executor}};
use async_trait::async_trait;
use eyre::Result;
// Define your event and action types
#[derive(Clone, Debug)]
pub struct MyEvent {
pub data: String,
}
#[derive(Clone, Debug)]
pub struct MyAction {
pub response: String,
}
// Implement a collector
pub struct MyCollector;
#[async_trait]
impl Collector<MyEvent> for MyCollector {
async fn get_event_stream(&self) -> Result<tokio::sync::mpsc::UnboundedReceiver<MyEvent>> {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
// Simulate event generation
tokio::spawn(async move {
for i in 0..10 {
let event = MyEvent {
data: format!("Event {}", i),
};
if tx.send(event).is_err() {
break;
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
});
Ok(rx)
}
}
// Implement a strategy
pub struct MyStrategy;
#[async_trait]
impl Strategy<MyEvent, MyAction> for MyStrategy {
async fn process_event(&self, event: MyEvent) -> Result<Option<MyAction>> {
Ok(Some(MyAction {
response: format!("Processed: {}", event.data),
}))
}
}
// Implement an executor
pub struct MyExecutor;
#[async_trait]
impl Executor<MyAction> for MyExecutor {
async fn execute(&self, action: MyAction) -> Result<()> {
println!("Executing: {}", action.response);
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Create an engine
let mut engine = Engine::new();
// Add components
engine.add_collector(Box::new(MyCollector));
engine.add_strategy(Box::new(MyStrategy));
engine.add_executor(Box::new(MyExecutor));
// Run the engine
engine.run_and_join().await?;
Ok(())
}Artemis provides convenient macros when working with multiple event or action types using enums:
use artemis::{map_collector, map_executor};
#[derive(Clone, Debug)]
pub enum Event {
TypeA(EventA),
TypeB(EventB),
}
#[derive(Clone, Debug)]
pub enum Action {
TypeX(ActionX),
TypeY(ActionY),
}
// Map specific collectors and executors to enum variants
let collector_a = map_collector!(MyCollectorA::new(), Event::TypeA);
let executor_x = map_executor!(MyExecutorX::new(), Action::TypeX);
engine.add_collector(collector_a);
engine.add_executor(executor_x);map_collector!(collector, EnumVariant)- Wraps collector output in enum variantmap_boxed_collector!(boxed_collector, EnumVariant)- Same as above but for already boxed collectorsmap_executor!(executor, EnumVariant)- Filters enum actions and passes specific type to executormap_boxed_executor!(boxed_executor, EnumVariant)- Same as above but for already boxed executors
- ✅ Reduced boilerplate - No manual mapping code needed
- ✅ Type safety - Compile-time verification of enum variants
- ✅ Clear intent - Code clearly shows the mapping relationships
- ✅ Automatic boxing - Handles trait object creation automatically
use artemis::Engine;
use eyre::{Result, WrapErr};
#[tokio::main]
async fn main() -> Result<()> {
let mut engine = Engine::new();
// Add your components...
engine.run_and_join().await
.wrap_err("Failed to run Artemis engine")?;
Ok(())
}The engine supports graceful shutdown through Rust's standard Drop trait and async cancellation:
use tokio::signal;
#[tokio::main]
async fn main() -> Result<()> {
let mut engine = Engine::new();
// Add your components...
// Set up graceful shutdown
tokio::select! {
result = engine.run_and_join() => {
result?;
}
_ = signal::ctrl_c() => {
println!("Received Ctrl+C, shutting down gracefully...");
}
}
Ok(())
}The repository includes several examples demonstrating different aspects of the framework:
cargo run --example basicDemonstrates fundamental usage with macro-based type mapping between specific types and enums.
cargo run --example advancedShows a more complex scenario with multiple collectors, strategies, and executors working with blockchain-like events.
cargo run --example macro_demoFocused demonstration of all available macros and their benefits for type mapping.
Run the test suite:
cargo testRun with documentation tests:
cargo test --docGenerate and view documentation:
cargo doc --openCollector<T>- Sources of eventsStrategy<E, A>- Event processing logicExecutor<T>- Action handlers
CollectorMap<T, U>- Maps collector output to different typesExecutorMap<T, U>- Maps executor input from different types
map_collector!- Create mapped collectorsmap_executor!- Create mapped executorsmap_boxed_collector!- Create boxed mapped collectorsmap_boxed_executor!- Create boxed mapped executors
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the Apache-2.0 License - see the LICENSE file for details.