Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.3.1] - 2025-07-20
Changed
- BREAKING: Refactored EventTypeMapper to use behavior-based approach instead of process dictionary
- BREAKING: EventTypeMappers must now implement
ExESDB.Commanded.EventTypeMapper
behaviour - Removed dynamic event type mapping in favor of explicit function clauses
- Improved error messages for missing or invalid event type mappers
- Better validation of event type mapper configuration at startup
Added
ExESDB.Commanded.EventTypeMapper
behaviour defining the required interface- Comprehensive documentation for implementing event type mappers
- Early validation of event type mapper implementations
- Clearer error messages for event type mapping failures
Fixed
- Process dictionary-based event type mapping which could cause issues in certain scenarios
- Potential race conditions in event type mapper configuration
- Unclear error messages when event type mapper was misconfigured
Migration Guide
EventTypeMappers
must now implement the ExESDB.Commanded.EventTypeMapper
behaviour:
# In your application's config/config.exs:
config :my_app, MyApp.CommandedApp,
event_store: [
adapter: ExESDB.Commanded.Adapter,
event_type_mapper: MyApp.EventTypeMapper,
store_id: :my_store,
# ... other config
]
# In lib/my_app/event_type_mapper.ex:
defmodule MyApp.EventTypeMapper do
@behaviour ExESDB.Commanded.EventTypeMapper
# Explicit function clauses for each event type
def to_event_type(MyApp.Events.UserRegistered), do: "user_registered:v1"
def to_event_type(MyApp.Events.EmailVerified), do: "email_verified:v1"
# Fallback for unknown event types
def to_event_type(unknown_event) do
raise "Unknown event type: #{inspect(unknown_event)}"
end
end
[0.2.4] - 2025-07-18
Added
- Comprehensive Integration Test Suite: Complete integration tests validating adapter behavior with real ExESDB.System instances
- Test Suite Documentation: Detailed guide (
guides/test-suite.md
) covering unit tests, integration tests, and testing best practices - Integration Test Helper: Utility module (
test/support/integration_test_helper.ex
) for system management and test data creation - ExESDB v0.4.1 Dependency: Added for test and development environments only
- Test Coverage: Validates timeout prevention, event persistence, system restart resilience, concurrent operations, and large payload handling
Changed
- Version Bump: Updated version from 0.2.3 to 0.2.4
- Documentation: Added test suite guide to ExDoc documentation
- Test Helper: Enhanced test helper with conditional ExESDB startup for integration tests
Test Suite Features
- Timeout Prevention: Ensures system doesn't fail due to timeouts when appending events
- Event Persistence: Verifies events can be retrieved after being appended
- System Restart Resilience: Tests that events persist across ExESDB.System restarts
- Concurrent Operations: Validates handling of multiple concurrent stream operations
- Large Payload Handling: Tests handling of events with >1KB data payloads
- Automatic Cleanup: Tests clean up data directories and processes automatically
- Configurable Ports: Uses random ports to avoid conflicts in CI/CD environments
- Comprehensive Logging: Detailed logging for troubleshooting and debugging
[0.2.3] - 2025-07-18
Fixed
- HOTFIX: Fixed
Enum.EmptyError
when no snapshots exist for an aggregate - Improved guard clause in
read_snapshot/2
to properly handle empty snapshots list
[0.2.2] - 2025-07-18
Added
- Store-aware naming for supervisors and processes to support umbrella applications
- Enhanced logging with store identification for better debugging
- Comprehensive test suite for store-aware functionality
Changed
- BREAKING:
AggregateListenerSupervisor
now requiresstore_id
parameter - BREAKING:
SubscriptionProxySupervisor
now requiresstore_id
parameter - BREAKING:
AggregateListenerSupervisor.stop_listener/1
now requiresstore_id
as first parameter:stop_listener(store_id, pid)
- BREAKING:
AggregateListenerSupervisor.stats/0
now requiresstore_id
parameter:stats(store_id)
- BREAKING:
AggregateListenerSupervisor.list_listeners/0
now requiresstore_id
parameter:list_listeners(store_id)
- BREAKING:
SubscriptionProxySupervisor.stop_proxy/1
now requiresstore_id
as first parameter:stop_proxy(store_id, pid)
- BREAKING:
SubscriptionProxySupervisor.list_proxies/0
now requiresstore_id
parameter:list_proxies(store_id)
AggregateListener
processes now use store-specific Registry namingSubscriptionProxy
processes now use global names with store prefixes- All supervisors and processes now include store information in log messages
- Enhanced supervision tree isolation between different stores
- Improved
read_snapshot/2
function to properly find latest snapshots usingExESDB.SnapshotsReader.list_snapshots/2
- Enhanced
to_snapshot_record/1
mapper function with nil protection forcreated_at
field
Fixed
- CRITICAL: Snapshot loading after server restart - snapshots now properly load the latest version instead of hardcoded version 0
- Naming conflicts when multiple stores are used in umbrella applications
- Process registry conflicts between different event stores
- Supervisor name clashes in multi-store environments
- Potential crashes when
created_at
field is nil in snapshot records
Technical Details
Store-Aware Supervisor Naming
AggregateListenerSupervisor
instances now use names likeExESDB.Commanded.AggregateListenerSupervisor.StoreId
SubscriptionProxySupervisor
instances now use names likeExESDB.Commanded.Adapter.SubscriptionProxySupervisor.StoreId
- Each store gets its own Registry:
ExESDB.Commanded.AggregateListenerSupervisor.StoreId.Registry
Process Naming
AggregateListener
processes use store-specific Registry via tuplesSubscriptionProxy
processes use global names with store prefixes:{:global, {store_id, name}}
Logging Improvements
- All log messages now include store identification
- Format:
AggregateListener[store_id]: message
andSubscriptionProxy[name] (store: store_id): message
Migration Guide
Before:
child_specs = [
{AggregateListenerSupervisor, []},
{SubscriptionProxySupervisor, []}
]
After:
child_specs = [
{AggregateListenerSupervisor, [store_id: :my_store]},
{SubscriptionProxySupervisor, [store_id: :my_store]}
]
API Changes:
# Before
AggregateListenerSupervisor.stop_listener(pid)
AggregateListenerSupervisor.stats()
AggregateListenerSupervisor.list_listeners()
# After
AggregateListenerSupervisor.stop_listener(:my_store, pid)
AggregateListenerSupervisor.stats(:my_store)
AggregateListenerSupervisor.list_listeners(:my_store)
[0.1.4] - Previous Release
Added
- Initial release functionality
- Basic ExESDB adapter for Commanded
- AggregateListener and SubscriptionProxy processes
- Support for event streaming and subscriptions