diff --git a/analysis_options.yaml b/analysis_options.yaml index 1353d31..a874774 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -35,9 +35,6 @@ linter: # **Error Prevention** - # Enforce non-nullable types where possible. - always_require_non_null_named_parameters: true - # **Documentation** # Require documentation for public members. public_member_api_docs: false diff --git a/packages/bounded/CHANGELOG.md b/packages/bounded/CHANGELOG.md index 6ab7957..ac5d613 100644 --- a/packages/bounded/CHANGELOG.md +++ b/packages/bounded/CHANGELOG.md @@ -12,7 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Strongly-typed identity primitives (`Identity` interface, `TypedIdentity` class) - Value object support with structural equality (`ValueObject` mixin) - Entity mixin with identity-based equality -- Aggregate root mixin with domain event collection +- Aggregate root base class for domain model entry points - Domain event interface (`DomainEvent`) -- Event collection methods on aggregates (record, retrieve, clear) - Pure Dart package with no infrastructure dependencies + +### Removed + +- Event collection from aggregate roots (no longer stores events internally) diff --git a/packages/bounded/lib/src/aggregate_root.dart b/packages/bounded/lib/src/aggregate_root.dart index b019de7..5df464b 100644 --- a/packages/bounded/lib/src/aggregate_root.dart +++ b/packages/bounded/lib/src/aggregate_root.dart @@ -1,4 +1,3 @@ -import 'domain_event.dart'; import 'entity.dart'; import 'identity.dart'; @@ -9,12 +8,6 @@ import 'identity.dart'; /// aggregate root is responsible for ensuring all invariants within the /// aggregate are maintained. /// -/// Aggregate roots can record domain events during state transitions. -/// These events represent facts that have occurred within the domain -/// and can be published or persisted by application layer code. -/// -/// An aggregate root is an [Entity] with event collection capabilities. -/// /// Example: /// ```dart /// class Order extends AggregateRoot { @@ -28,7 +21,6 @@ import 'identity.dart'; /// throw StateError('Order can only be placed when pending'); /// } /// status = OrderStatus.placed; -/// recordEvent(OrderPlaced(id, DateTime.now())); /// } /// } /// ``` @@ -40,45 +32,4 @@ abstract class AggregateRoot with Entity { @override ID get id => _id; - - final List _events = []; - - /// Records a domain event that occurred during a state transition. - /// - /// Events are stored internally and can be retrieved via [events] - /// for publishing or persistence by application layer code. - void recordEvent(DomainEvent event) { - _events.add(event); - } - - /// Returns a read-only view of domain events recorded by this aggregate. - /// - /// This list contains events that have been recorded but not yet cleared. - List get events => List.unmodifiable(_events); - - /// Clears all recorded domain events. - /// - /// This should be called after events have been published or persisted - /// to prevent them from being processed multiple times. - void clearEvents() { - _events.clear(); - } - - /// Returns all recorded domain events and clears them in a single operation. - /// - /// This is a convenience for the common application-layer flow: - /// 1) perform a domain operation - /// 2) publish/persist the resulting events - /// 3) clear the pending event buffer - /// - /// Events are returned in the order they were recorded. - List pullEvents() { - if (_events.isEmpty) { - return const []; - } - - final drained = List.unmodifiable(_events); - _events.clear(); - return drained; - } } diff --git a/packages/bounded/pubspec.yaml b/packages/bounded/pubspec.yaml index 6e795ff..bb33be9 100644 --- a/packages/bounded/pubspec.yaml +++ b/packages/bounded/pubspec.yaml @@ -12,39 +12,4 @@ dev_dependencies: test: ^1.25.0 lints: ^6.0.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/to/asset-from-package - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/to/font-from-package diff --git a/packages/bounded/test/aggregate_test.dart b/packages/bounded/test/aggregate_test.dart index 929a545..33d9b73 100644 --- a/packages/bounded/test/aggregate_test.dart +++ b/packages/bounded/test/aggregate_test.dart @@ -37,7 +37,6 @@ class Order extends AggregateRoot { throw StateError('Order can only be placed when pending'); } status = OrderStatus.placed; - recordEvent(OrderPlaced(id, at)); } void ship() { @@ -45,85 +44,30 @@ class Order extends AggregateRoot { throw StateError('Order can only be shipped when placed'); } status = OrderStatus.shipped; - recordEvent(OrderShipped(id)); } } void main() { group('AggregateRoot', () { - test('aggregate can record domain events during transitions', () { + test('aggregate can perform state transitions', () { final order = Order(const OrderId('order-123')); final placedAt = DateTime(2026, 1, 21); order.place(placedAt); - expect(order.events, hasLength(1)); - expect(order.events.first, isA()); - final event = order.events.first as OrderPlaced; - expect(event.orderId, equals(order.id)); - expect(event.placedAt, equals(placedAt)); + expect(order.status, equals(OrderStatus.placed)); }); - test('aggregate records multiple events', () { + test('aggregate can perform multiple transitions', () { final order = Order(const OrderId('order-123')); order.place(DateTime.now()); order.ship(); - expect(order.events, hasLength(2)); - expect(order.events[0], isA()); - expect(order.events[1], isA()); + expect(order.status, equals(OrderStatus.shipped)); }); - test('events property returns read-only view', () { - final order = Order(const OrderId('order-123')); - order.place(DateTime.now()); - - final events = order.events; - expect(() => (events as List).add(OrderShipped(order.id)), throwsUnsupportedError); - }); - - test('clearEvents removes all recorded events', () { - final order = Order(const OrderId('order-123')); - order.place(DateTime.now()); - - expect(order.events, hasLength(1)); - - order.clearEvents(); - - expect(order.events, isEmpty); - }); - - test('pullEvents drains events in order and clears', () { - final order = Order(const OrderId('order-123')); - - order.place(DateTime(2026, 1, 21)); - order.ship(); - - final drained = order.pullEvents(); - - expect(drained, hasLength(2)); - expect(drained[0], isA()); - expect(drained[1], isA()); - expect(order.events, isEmpty); - }); - - test('pullEvents returns empty list when no events are recorded', () { - final order = Order(const OrderId('order-123')); - - final drained = order.pullEvents(); - - expect(drained, isEmpty); - expect(order.events, isEmpty); - }); - - test('aggregate starts with no events', () { - final order = Order(const OrderId('order-123')); - - expect(order.events, isEmpty); - }); - - test('aggregate enforces invariants without infrastructure', () { + test('aggregate enforces invariants', () { final order = Order(const OrderId('order-123')); // Cannot ship without placing first diff --git a/packages/bounded_lints/example/pubspec.lock b/packages/bounded_lints/example/pubspec.lock index 538cf00..fcf05e5 100644 --- a/packages/bounded_lints/example/pubspec.lock +++ b/packages/bounded_lints/example/pubspec.lock @@ -195,18 +195,18 @@ packages: dependency: transitive description: name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df" url: "https://pub.dev" source: hosted - version: "4.9.0" + version: "4.10.0" lints: dependency: "direct dev" description: name: lints - sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.0" logging: dependency: transitive description: @@ -227,10 +227,10 @@ packages: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: "9f29b9bcc8ee287b1a31e0d01be0eae99a930dbffdaecf04b3f3d82a969f296f" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.18.1" package_config: dependency: transitive description: @@ -275,10 +275,10 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" stack_trace: dependency: transitive description: