10 KiB
10 KiB
Bus Package Specification
Overview
The Bus package provides a robust command and event bus implementation that matches Laravel's bus functionality. It integrates with our Queue, Event, and Pipeline packages to provide a complete message bus solution with support for command handling, event dispatching, and middleware processing.
Related Documentation
- See Laravel Compatibility Roadmap for implementation status
- See Foundation Integration Guide for integration patterns
- See Testing Guide for testing approaches
- See Getting Started Guide for development setup
- See Events Package Specification for event handling
- See Queue Package Specification for queue integration
Core Features
1. Command Bus
/// Core command bus implementation
class CommandBus implements CommandBusContract {
final Container _container;
final QueueContract? _queue;
final PipelineContract _pipeline;
CommandBus(
this._container,
this._pipeline, [
this._queue
]);
/// Dispatches a command
Future<dynamic> dispatch(Command command) async {
if (command is ShouldQueue && _queue != null) {
return await dispatchToQueue(command);
}
return await dispatchNow(command);
}
/// Dispatches a command immediately
Future<dynamic> dispatchNow(Command command) async {
var handler = _resolveHandler(command);
return await _pipeline
.send(command)
.through(_getPipes())
.then((cmd) => handler.handle(cmd));
}
/// Dispatches a command to queue
Future<dynamic> dispatchToQueue(Command command) async {
await _queue!.push(QueuedCommandJob(
command: command,
handler: _resolveHandler(command)
));
}
/// Creates a command batch
PendingBatch batch(List<Command> commands) {
return PendingCommandBatch(this, commands);
}
/// Creates a command chain
PendingChain chain(List<Command> commands) {
return PendingCommandChain(this, commands);
}
/// Resolves command handler
Handler _resolveHandler(Command command) {
var handlerType = command.handler;
return _container.make(handlerType);
}
/// Gets command pipes
List<PipeContract> _getPipes() {
return [
TransactionPipe(),
ValidationPipe(),
AuthorizationPipe()
];
}
}
2. Event Bus
/// Core event bus implementation
class EventBus implements EventBusContract {
final Container _container;
final EventDispatcherContract _events;
final QueueContract? _queue;
EventBus(
this._container,
this._events, [
this._queue
]);
/// Dispatches an event
Future<void> dispatch(Event event) async {
if (event is ShouldQueue && _queue != null) {
await dispatchToQueue(event);
} else {
await dispatchNow(event);
}
}
/// Dispatches an event immediately
Future<void> dispatchNow(Event event) async {
await _events.dispatch(event);
}
/// Dispatches an event to queue
Future<void> dispatchToQueue(Event event) async {
await _queue!.push(QueuedEventJob(
event: event,
listeners: _events.getListeners(event.runtimeType)
));
}
/// Registers an event listener
void listen<T>(void Function(T event) listener) {
_events.listen<T>(listener);
}
/// Registers an event subscriber
void subscribe(EventSubscriber subscriber) {
_events.subscribe(subscriber);
}
}
3. Bus Middleware
/// Transaction middleware
class TransactionPipe implements PipeContract {
final DatabaseManager _db;
TransactionPipe(this._db);
@override
Future<dynamic> handle(dynamic passable, Function next) async {
return await _db.transaction((tx) async {
return await next(passable);
});
}
}
/// Validation middleware
class ValidationPipe implements PipeContract {
final Validator _validator;
ValidationPipe(this._validator);
@override
Future<dynamic> handle(dynamic passable, Function next) async {
if (passable is ValidatableCommand) {
await _validator.validate(
passable.toMap(),
passable.rules()
);
}
return await next(passable);
}
}
/// Authorization middleware
class AuthorizationPipe implements PipeContract {
final AuthManager _auth;
AuthorizationPipe(this._auth);
@override
Future<dynamic> handle(dynamic passable, Function next) async {
if (passable is AuthorizableCommand) {
if (!await passable.authorize(_auth)) {
throw UnauthorizedException();
}
}
return await next(passable);
}
}
4. Command Batching
/// Pending command batch
class PendingCommandBatch implements PendingBatch {
final CommandBus _bus;
final List<Command> _commands;
bool _allowFailures = false;
PendingCommandBatch(this._bus, this._commands);
/// Allows failures in batch
PendingBatch allowFailures() {
_allowFailures = true;
return this;
}
/// Dispatches the batch
Future<void> dispatch() async {
for (var command in _commands) {
try {
await _bus.dispatchNow(command);
} catch (e) {
if (!_allowFailures) rethrow;
}
}
}
}
/// Pending command chain
class PendingCommandChain implements PendingChain {
final CommandBus _bus;
final List<Command> _commands;
PendingCommandChain(this._bus, this._commands);
/// Dispatches the chain
Future<void> dispatch() async {
dynamic result;
for (var command in _commands) {
if (command is ChainedCommand) {
command.setPreviousResult(result);
}
result = await _bus.dispatchNow(command);
}
}
}
Integration Examples
1. Command Bus Usage
// Define command
class CreateOrder implements Command {
final String customerId;
final List<String> products;
@override
Type get handler => CreateOrderHandler;
}
// Define handler
class CreateOrderHandler implements Handler<CreateOrder> {
final OrderRepository _orders;
CreateOrderHandler(this._orders);
@override
Future<Order> handle(CreateOrder command) async {
return await _orders.create(
customerId: command.customerId,
products: command.products
);
}
}
// Dispatch command
var order = await bus.dispatch(CreateOrder(
customerId: '123',
products: ['abc', 'xyz']
));
2. Event Bus Usage
// Define event
class OrderCreated implements Event {
final Order order;
OrderCreated(this.order);
}
// Register listener
eventBus.listen<OrderCreated>((event) async {
await notifyCustomer(event.order);
});
// Dispatch event
await eventBus.dispatch(OrderCreated(order));
3. Command Batching
// Create batch
await bus.batch([
CreateOrder(...),
UpdateInventory(...),
NotifyShipping(...)
])
.allowFailures()
.dispatch();
// Create chain
await bus.chain([
CreateOrder(...),
ProcessPayment(...),
ShipOrder(...)
])
.dispatch();
Testing
void main() {
group('Command Bus', () {
test('dispatches commands', () async {
var bus = CommandBus(container, pipeline);
var command = CreateOrder(...);
await bus.dispatch(command);
verify(() => handler.handle(command)).called(1);
});
test('handles command batch', () async {
var bus = CommandBus(container, pipeline);
await bus.batch([
CreateOrder(...),
UpdateInventory(...)
]).dispatch();
verify(() => bus.dispatchNow(any())).called(2);
});
});
group('Event Bus', () {
test('dispatches events', () async {
var bus = EventBus(container, dispatcher);
var event = OrderCreated(order);
await bus.dispatch(event);
verify(() => dispatcher.dispatch(event)).called(1);
});
test('queues events', () async {
var bus = EventBus(container, dispatcher, queue);
var event = OrderShipped(order);
await bus.dispatch(event);
verify(() => queue.push(any())).called(1);
});
});
}
Next Steps
- Implement core bus features
- Add middleware support
- Add batching/chaining
- Add queue integration
- Write tests
- Add benchmarks
Development Guidelines
1. Getting Started
Before implementing bus features:
- Review Getting Started Guide
- Check Laravel Compatibility Roadmap
- Follow Testing Guide
- Use Foundation Integration Guide
- Review Events Package Specification
- Review Queue Package Specification
2. Implementation Process
For each bus feature:
- Write tests following Testing Guide
- Implement following Laravel patterns
- Document following Getting Started Guide
- Integrate following Foundation Integration Guide
3. Quality Requirements
All implementations must:
- Pass all tests (see Testing Guide)
- Meet Laravel compatibility requirements
- Follow integration patterns (see Foundation Integration Guide)
- Match specifications in related packages
4. Integration Considerations
When implementing bus features:
- Follow patterns in Foundation Integration Guide
- Ensure Laravel compatibility per Laravel Compatibility Roadmap
- Use testing approaches from Testing Guide
- Follow development setup in Getting Started Guide
5. Performance Guidelines
Bus system must:
- Handle high command throughput
- Process events efficiently
- Support async operations
- Scale horizontally
- Meet performance targets in Laravel Compatibility Roadmap
6. Testing Requirements
Bus tests must:
- Cover all command scenarios
- Test event handling
- Verify queue integration
- Check middleware behavior
- Follow patterns in Testing Guide
7. Documentation Requirements
Bus documentation must:
- Explain command patterns
- Show event examples
- Cover error handling
- Include performance tips
- Follow standards in Getting Started Guide