Raiser
A type-safe, async-first domain event library for Dart. Raiser provides a clean event bus implementation following clean architecture principles, perfect for decoupling components in your application.
Features
- Type-safe event handling — Generic handlers ensure compile-time type checking
- Async-first design — All handlers are asynchronous by default
- Priority-based ordering — Control handler execution order with priorities
- Middleware support — Wrap handler execution with cross-cutting concerns
- Flexible error strategies — Choose how errors are handled during event propagation
- Subscription management — Cancel handlers when no longer needed
- Domain event metadata — Built-in support for event IDs, timestamps, and aggregate IDs
- DDD-friendly — Designed with Domain-Driven Design patterns in mind
Installation
dependencies:
raiser: ^1.0.0
Quick Start
import 'package:raiser/raiser.dart';
// Define an event
class UserCreated extends DomainEvent {
final String userId;
final String email;
UserCreated({required this.userId, required this.email});
@override
Map<String, dynamic> toMetadataMap() => {
...super.toMetadataMap(),
'userId': userId,
'email': email,
};
}
void main() async {
final bus = EventBus();
// Subscribe to events
bus.on<UserCreated>((event) async {
print('Welcome ${event.email}!');
});
// Publish events
await bus.publish(UserCreated(userId: '123', email: '[email protected]'));
}
Domain Events
All events extend DomainEvent, which provides automatic metadata:
| Property | Description |
|---|---|
id |
Unique identifier (auto-generated) |
timestamp |
Creation time (auto-captured) |
aggregateId |
Optional link to a domain aggregate |
class OrderPlaced extends DomainEvent {
final String orderId;
final double amount;
OrderPlaced({required this.orderId, required this.amount, super.aggregateId});
@override
Map<String, dynamic> toMetadataMap() => {
...super.toMetadataMap(),
'orderId': orderId,
'amount': amount,
};
}
Event Handlers
Function Handlers
Quick inline handlers:
bus.on<UserCreated>((event) async {
await sendWelcomeEmail(event.email);
});
Class-Based Handlers
Better for complex logic and testing:
class WelcomeEmailHandler implements EventHandler<UserCreated> {
@override
Future<void> handle(UserCreated event) async {
await sendWelcomeEmail(event.email);
}
}
bus.register<UserCreated>(WelcomeEmailHandler());
Handler Priority
Higher values execute first:
bus.on<OrderPlaced>((e) async => print('Second'), priority: 0);
bus.on<OrderPlaced>((e) async => print('First'), priority: 10);
bus.on<OrderPlaced>((e) async => print('Last'), priority: -5);
Middleware
Wrap handler execution with cross-cutting concerns like logging, timing, or validation:
// Add middleware that wraps all handler execution
bus.addMiddleware((event, next) async {
print('Before: ${event.runtimeType}');
await next();
print('After: ${event.runtimeType}');
}, priority: 100);
// Middleware with higher priority wraps those with lower priority
bus.addMiddleware((event, next) async {
final stopwatch = Stopwatch()..start();
await next();
print('Took ${stopwatch.elapsedMilliseconds}ms');
}, priority: 50);
Subscriptions
Both on(), register(), and addMiddleware() return a Subscription:
final subscription = bus.on<UserCreated>((event) async {
// Handle event
});
// Stop receiving events
subscription.cancel();
// Check status
print(subscription.isCancelled); // true
Error Handling
Configure error behavior with ErrorStrategy:
| Strategy | Behavior |
|---|---|
stop |
Halt on first error, rethrow immediately (default) |
continueOnError |
Run all handlers, throw AggregateException |
swallow |
Run all handlers, errors only go to callback |
// Stop on first error (default)
final bus = EventBus(errorStrategy: ErrorStrategy.stop);
// Collect all errors
final bus = EventBus(errorStrategy: ErrorStrategy.continueOnError);
// Silent failures with logging
final bus = EventBus(
errorStrategy: ErrorStrategy.swallow,
onError: (error, stackTrace) => logger.error('Failed: $error'),
);
AggregateException
When using continueOnError:
try {
await bus.publish(event);
} on AggregateException catch (e) {
print('${e.errors.length} handlers failed');
}
Code Generation
For automatic handler discovery and registration, use the companion packages:
dependencies:
raiser: ^1.0.0
raiser_annotation: ^1.0.0
dev_dependencies:
build_runner: ^2.4.0
raiser_generator: ^1.0.0
See raiser_generator for details.
License
MIT License - see LICENSE for details.
Libraries
- raiser
- Raiser - A type-safe domain event library for Dart.