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

453 lines
12 KiB
Markdown

# Events Package Specification
## Overview
The Events package provides a robust event system that matches Laravel's event functionality while leveraging Dart's async capabilities. It integrates with our Queue, Bus, and Database packages to provide a complete event handling solution.
> **Related Documentation**
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
> - See [Testing Guide](testing_guide.md) for testing approaches
> - See [Getting Started Guide](getting_started.md) for development setup
> - See [Contracts Package Specification](contracts_package_specification.md) for event contracts
## Core Features
### 1. Event Dispatcher
```dart
/// Core event dispatcher implementation
class EventDispatcher implements EventDispatcherContract {
final Container _container;
final Map<Type, List<EventListener>> _listeners = {};
final List<EventSubscriber> _subscribers = {};
final QueueContract? _queue;
final BroadcasterContract? _broadcaster;
final List<dynamic> _afterCommitEvents = [];
EventDispatcher(
this._container, {
QueueContract? queue,
BroadcasterContract? broadcaster
}) : _queue = queue,
_broadcaster = broadcaster;
@override
void listen<T>(void Function(T event) listener) {
_listeners.putIfAbsent(T, () => []).add(
EventListener<T>(listener)
);
}
@override
Future<void> dispatch<T>(T event) async {
var listeners = _listeners[T] ?? [];
// Handle after commit events
if (event is ShouldDispatchAfterCommit && _isWithinTransaction()) {
_afterCommitEvents.add(event);
return;
}
// Handle queued events
if (event is ShouldQueue && _queue != null) {
await _queueEvent(event, listeners);
return;
}
// Handle broadcasting
if (event is ShouldBroadcast && _broadcaster != null) {
await _broadcastEvent(event);
}
// Notify listeners
await _notifyListeners(event, listeners);
}
@override
Future<void> dispatchAfterCommit<T>(T event) async {
if (_isWithinTransaction()) {
_afterCommitEvents.add(event);
} else {
await dispatch(event);
}
}
bool _isWithinTransaction() {
if (_container.has<DatabaseManager>()) {
var db = _container.make<DatabaseManager>();
return db.transactionLevel > 0;
}
return false;
}
Future<void> _dispatchAfterCommitEvents() async {
var events = List.from(_afterCommitEvents);
_afterCommitEvents.clear();
for (var event in events) {
await dispatch(event);
}
}
}
```
### 2. Event Discovery
```dart
/// Discovers event handlers through reflection and attributes
class EventDiscovery {
final Container _container;
final Reflector _reflector;
EventDiscovery(this._container, this._reflector);
/// Discovers event handlers in a directory
Future<void> discoverEvents(String path) async {
var files = Directory(path).listSync(recursive: true);
for (var file in files) {
if (file.path.endsWith('.dart')) {
await _processFile(file.path);
}
}
}
Future<void> _processFile(String path) async {
var library = await _reflector.loadLibrary(path);
for (var type in library.declarations.values) {
if (type is ClassMirror) {
_processClass(type);
}
}
}
void _processClass(ClassMirror classMirror) {
// Find @Handles annotations
for (var method in classMirror.declarations.values) {
if (method is MethodMirror) {
var handles = method.metadata
.firstWhere((m) => m.type == Handles,
orElse: () => null);
if (handles != null) {
var eventType = handles.getField('event').reflectee;
_registerHandler(classMirror.reflectedType, method.simpleName, eventType);
}
}
}
}
void _registerHandler(Type classType, Symbol methodName, Type eventType) {
var instance = _container.make(classType);
var dispatcher = _container.make<EventDispatcherContract>();
dispatcher.listen(eventType, (event) {
var mirror = reflect(instance);
mirror.invoke(methodName, [event]);
});
}
}
```
### 3. Event Broadcasting
```dart
/// Contract for event broadcasters
abstract class BroadcasterContract {
/// Broadcasts an event
Future<void> broadcast(
List<String> channels,
String eventName,
dynamic data
);
/// Creates a private channel
Channel privateChannel(String name);
/// Creates a presence channel
PresenceChannel presenceChannel(String name);
}
/// Pusher event broadcaster
class PusherBroadcaster implements BroadcasterContract {
final PusherClient _client;
final AuthManager _auth;
PusherBroadcaster(this._client, this._auth);
@override
Future<void> broadcast(
List<String> channels,
String eventName,
dynamic data
) async {
for (var channel in channels) {
await _client.trigger(channel, eventName, data);
}
}
@override
Channel privateChannel(String name) {
return PrivateChannel(_client, _auth, name);
}
@override
PresenceChannel presenceChannel(String name) {
return PresenceChannel(_client, _auth, name);
}
}
```
### 4. Integration with Queue
```dart
/// Job for processing queued events
class QueuedEventJob implements Job {
final dynamic event;
final List<EventListener> listeners;
final Map<String, dynamic> data;
QueuedEventJob({
required this.event,
required this.listeners,
this.data = const {}
});
@override
Future<void> handle() async {
for (var listener in listeners) {
try {
await listener.handle(event);
} catch (e) {
await _handleFailure(e);
}
}
}
@override
Future<void> failed([Exception? e]) async {
if (event is FailedEventHandler) {
await (event as FailedEventHandler).failed(e);
}
}
@override
int get tries => event is HasTries ? (event as HasTries).tries : 1;
@override
Duration? get timeout =>
event is HasTimeout ? (event as HasTimeout).timeout : null;
}
```
### 5. Integration with Bus
```dart
/// Event command for command bus integration
class EventCommand implements Command {
final dynamic event;
final List<EventListener> listeners;
EventCommand(this.event, this.listeners);
@override
Type get handler => EventCommandHandler;
}
/// Handler for event commands
class EventCommandHandler implements Handler<EventCommand> {
final EventDispatcher _events;
EventCommandHandler(this._events);
@override
Future<void> handle(EventCommand command) async {
await _events._notifyListeners(
command.event,
command.listeners
);
}
}
```
## Usage Examples
### Basic Event Handling
```dart
// Define event
class OrderShipped {
final Order order;
OrderShipped(this.order);
}
// Create listener
@Handles(OrderShipped)
class OrderShippedListener {
void handle(OrderShipped event) {
// Handle event
}
}
// Register and dispatch
dispatcher.listen<OrderShipped>((event) {
// Handle event
});
await dispatcher.dispatch(OrderShipped(order));
```
### After Commit Events
```dart
class OrderCreated implements ShouldDispatchAfterCommit {
final Order order;
OrderCreated(this.order);
}
// In transaction
await db.transaction((tx) async {
var order = await tx.create(orderData);
await dispatcher.dispatchAfterCommit(OrderCreated(order));
});
```
### Broadcasting
```dart
class MessageSent implements ShouldBroadcast {
final Message message;
@override
List<String> broadcastOn() => [
'private-chat.${message.roomId}'
];
@override
Map<String, dynamic> get broadcastWith => {
'id': message.id,
'content': message.content,
'user': message.user.toJson()
};
}
// Create private channel
var channel = broadcaster.privateChannel('chat.123');
await channel.whisper('typing', {'user': 'john'});
```
### Queue Integration
```dart
class ProcessOrder implements ShouldQueue {
final Order order;
@override
String get queue => 'orders';
@override
int get tries => 3;
@override
Duration get timeout => Duration(minutes: 5);
}
// Dispatch queued event
await dispatcher.dispatch(ProcessOrder(order));
```
## Testing
```dart
void main() {
group('Event Dispatcher', () {
test('dispatches after commit', () async {
var dispatcher = MockEventDispatcher();
var db = MockDatabase();
await db.transaction((tx) async {
await dispatcher.dispatchAfterCommit(OrderShipped(order));
expect(dispatcher.hasAfterCommitEvents, isTrue);
});
expect(dispatcher.hasAfterCommitEvents, isFalse);
verify(() => dispatcher.dispatch(any())).called(1);
});
test('discovers event handlers', () async {
var discovery = EventDiscovery(container, reflector);
await discovery.discoverEvents('lib/events');
var dispatcher = container.make<EventDispatcherContract>();
await dispatcher.dispatch(OrderShipped(order));
verify(() => orderListener.handle(any())).called(1);
});
});
}
```
## Next Steps
1. Complete after commit handling
2. Enhance event discovery
3. Add more broadcast drivers
4. Improve queue integration
5. Add performance optimizations
6. Write more tests
Would you like me to enhance any other package specifications?
## Development Guidelines
### 1. Getting Started
Before implementing event features:
1. Review [Getting Started Guide](getting_started.md)
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
3. Follow [Testing Guide](testing_guide.md)
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
5. Understand [Contracts Package Specification](contracts_package_specification.md)
### 2. Implementation Process
For each event feature:
1. Write tests following [Testing Guide](testing_guide.md)
2. Implement following Laravel patterns
3. Document following [Getting Started Guide](getting_started.md#documentation)
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
### 3. Quality Requirements
All implementations must:
1. Pass all tests (see [Testing Guide](testing_guide.md))
2. Meet Laravel compatibility requirements
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
4. Implement required contracts (see [Contracts Package Specification](contracts_package_specification.md))
### 4. Integration Considerations
When implementing events:
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
3. Use testing approaches from [Testing Guide](testing_guide.md)
4. Follow development setup in [Getting Started Guide](getting_started.md)
5. Implement all contracts from [Contracts Package Specification](contracts_package_specification.md)
### 5. Performance Guidelines
Event system must:
1. Handle high throughput efficiently
2. Minimize memory usage
3. Support async operations
4. Scale horizontally
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
### 6. Testing Requirements
Event tests must:
1. Cover all event scenarios
2. Test async behavior
3. Verify queue integration
4. Check broadcasting
5. Follow patterns in [Testing Guide](testing_guide.md)
### 7. Documentation Requirements
Event documentation must:
1. Explain event patterns
2. Show integration examples
3. Cover error handling
4. Include performance tips
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)