platform/doc/bus_package_specification.md
2024-11-25 20:33:45 -07:00

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

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

  1. Implement core bus features
  2. Add middleware support
  3. Add batching/chaining
  4. Add queue integration
  5. Write tests
  6. Add benchmarks

Development Guidelines

1. Getting Started

Before implementing bus features:

  1. Review Getting Started Guide
  2. Check Laravel Compatibility Roadmap
  3. Follow Testing Guide
  4. Use Foundation Integration Guide
  5. Review Events Package Specification
  6. Review Queue Package Specification

2. Implementation Process

For each bus feature:

  1. Write tests following Testing Guide
  2. Implement following Laravel patterns
  3. Document following Getting Started Guide
  4. Integrate following Foundation Integration Guide

3. Quality Requirements

All implementations must:

  1. Pass all tests (see Testing Guide)
  2. Meet Laravel compatibility requirements
  3. Follow integration patterns (see Foundation Integration Guide)
  4. Match specifications in related packages

4. Integration Considerations

When implementing bus features:

  1. Follow patterns in Foundation Integration Guide
  2. Ensure Laravel compatibility per Laravel Compatibility Roadmap
  3. Use testing approaches from Testing Guide
  4. Follow development setup in Getting Started Guide

5. Performance Guidelines

Bus system must:

  1. Handle high command throughput
  2. Process events efficiently
  3. Support async operations
  4. Scale horizontally
  5. Meet performance targets in Laravel Compatibility Roadmap

6. Testing Requirements

Bus tests must:

  1. Cover all command scenarios
  2. Test event handling
  3. Verify queue integration
  4. Check middleware behavior
  5. Follow patterns in Testing Guide

7. Documentation Requirements

Bus documentation must:

  1. Explain command patterns
  2. Show event examples
  3. Cover error handling
  4. Include performance tips
  5. Follow standards in Getting Started Guide