- Nothing yet
- Breaking: EventFlow no longer ignores columns named
Id
in MSSQL read models. If you were dependent on this, use theMsSqlReadModelIgnoreColumn
attribute - Fixed: Instead of using
MethodInfo.Invoke
to call methods on reflected types, e.g. when a command is published, EventFlow now compiles an expression tree instead. This has a slight initial overhead, but provides a significant performance improvement for subsequent calls - Fixed: Read model stores are only invoked if there's any read model updates
- Fixed: EventFlow now correctly throws an
ArgumentException
if EventFlow has been incorrectly configure with known versioned types, e.g. an event is emitted that hasn't been added during EventFlow initialization. EventFlow would handle the save operation correctly, but if EventFlow was reinitialized and the event was loaded before it being emitted again, an exception would be thrown as EventFlow would know which type to use. Please make sure to correctly load all event, command and job types before use - Fixed:
IReadModelFactory<>.CreateAsync(...)
is now correctly used in read store mangers - Fixed: Versioned type naming convention now allows numbers
- New: To customize how a specific read model is initially created, implement
a specific
IReadModelFactory<>
that can bootstrap that read model - New: How EventFlow handles MSSQL read models has been refactored to allow
significantly more freedom to developers. MSSQL read models are no longer
required to implement
IMssqlReadModel
, only the emptyIReadModel
interface. Effectively, this means that no specific columns are required, meaning that the following columns are no longer enforced on MSSQL read models. Use the new requiredMsSqlReadModelIdentityColumn
attribute to mark the identity column and the optional (but recommended)MsSqlReadModelVersionColumn
to mark the version column.string AggregateId
DateTimeOffset CreateTime
DateTimeOffset UpdatedTime
int LastAggregateSequenceNumber
- Obsolete:
IMssqlReadModel
andMssqlReadModel
. Developers should instead use theMsSqlReadModelIdentityColumn
andMsSqlReadModelVersionColumn
attributes to mark the identity and version columns (read above). EventFlow will continue to supportIMssqlReadModel
, but it will be removed at some point in the future - Fixed: Added missing
UseElasticsearchReadModel<TReadModel, TReadModelLocator>()
extension
- New: Added
Identity<>.NewComb()
that creates sequential unique IDs which can be used to minimize database fragmentation - New: Added
IReadModelContext.Resolver
to allow read models to fetch additional resources when events are applied - New: The
PrettyPrint()
type extension method, mostly used for verbose logging, now prints even prettier type names, e.g.KeyValuePair<Boolean,Int64>
instead of merelyKeyValuePair'2
, making log messages slightly more readable
- Breaking:
Entity<T>
now inherits fromValueObject
but uses only theId
field as equality component. OverrideGetEqualityComponents()
if you have a different notion of equality for a specific entity - Breaking:
Entity<T>
will now throw anArgumentNullException
if theid
passed to its constructor isnull
- Breaking: Fixed method spelling. Renamed
ISpecification<T>.WhyIsNotStatisfiedBy
toWhyIsNotSatisfiedBy
andSpecification<T>.IsNotStatisfiedBecause
toIsNotSatisfiedBecause
- New: Read model support for Elasticsearch via the new NuGet package
EventFlow.ReadStores.Elasticsearch
- Breaking:
AddDefaults
now also adds the job type definition to theIJobsDefinitonService
- New: Implemented a basic specification pattern by providing
ISpecification<T>
, an easy-to-useSpecificaion<T>
and a set of extension methods. Look at the EventFlow specification tests to get started - Fixed:
IEventDefinitionService
,ICommandDefinitonService
andIJobsDefinitonService
now longer throw an exception if an existing event is loaded, i.e., multiple calls toAddEvents(...)
,AddCommand(...)
andAddJobs(...)
no longer throws an exception - Fixed:
DomainError.With(...)
no longer executesstring.format
if only one argument is parsed
- POTENTIAL DATA LOSS for the files event store: The EventFlow
internal functionality regarding event stores has been refactored resulting
in information regarding aggregate names being removed from the event
persistence layer. The files based event store no longer stores its events in
the path
[STORE PATH]\[AGGREGATE NAME]\[AGGREGATE ID]\[SEQUENCE].json
, but in the path[STORE PATH]\[AGGREGATE ID]\[SEQUENCE].json
. Thus if you are using the files event store for tests, you should move the events into the new file structure. Alternatively, implement the newIFilesEventLocator
and provide your own custom event file layout. - Breaking: Event stores have been split into two parts, the
IEventStore
and the newIEventPersistence
.IEventStore
has the same interface before but the implementation is now no longer responsible for persisting the events, only converting and serializing the persisted events.IEventPersistence
handles the actual storing of events and thus if any custom event stores have been implemented, they should implement to the newIEventPersistence
instead. - New: Added
IEntity
,IEntity<>
and an optionalEntity<>
that developers can use to implement DDD entities.
- Fixed: Using NuGet package
EventFlow.Autofac
causes an exception with the messageThe type 'EventFlow.Configuration.Registrations.AutofacStartable' is not assignable to service 'Autofac.IStartable
during EventFlow setup
- Breaking: Removed
HasRegistrationFor<>
andGetRegisteredServices()
fromIServiceRegistration
and added them toIResolver
instead. The methods required that all service registrations went through EventFlow, which in most cases they will not - Obsolete: Marked
IServiceRegistration.RegisterIfNotRegistered(...)
, use thekeepDefault = true
on the otherRegister(...)
methods instead - New: Major changes have been done to how EventFlow handles service
registration and bootstrapping in order for developers to skip calling
CreateResolver()
(orCreateContainer()
if using theEventFlow.Autofac
package) completely. EventFlow will register its bootstrap services in the IoC container and configure itself whenever the container is created - New: Introduced
IBootstrap
interface that you can register. It has a singleBootAsync(...)
method that will be called as soon as the IoC container is ready (similar to that ofIStartable
of Autofac) - Fixed: Correct order of service registration decorators. They are now applied in the same order they are applied, e.g., the last registered service decorator will be the "outer" service
- Fixed: Added missing
ICommand<,>
interface to abstractCommand<,>
class inEventFlow.Commands
.
- Fixed: Added
UseHangfireJobScheduler()
and markedUseHandfireJobScheduler()
obsolete, fixing method spelling mistake
- Breaking: All
EventFlowOptions
extensions are nowIEventFlowOptions
instead andEventFlowOptions
implements this interface. If you have made your own extensions, you will need to use the newly created interface instead. Changed in order to make testing of extensions and classes dependent on the EventFlow options easier to test - New: You can now bundle your configuration of EventFlow into modules that
implement
IModule
and register these by callingEventFlowOptions.RegisterModule(...)
- New: EventFlow now supports scheduled job execution via e.g. Hangfire. You
can create your own scheduler or install the new
EventFlow.Hangfire
NuGet package. Read the jobs documentation for more details - New: Created the OWIN
CommandPublishMiddleware
middleware that can handle publishing of commands by posting a JSON serialized command to e.g./commands/ping/1
in whichping
is the command name and1
its version. Remember to add authentication - New: Created a new interface
ICommand<TAggregate,TIdentity,TSourceIdentity>
to allow developers to control the type ofICommand.SourceId
. Using theICommand<TAggregate,TIdentity>
(or Command<TAggregate,TIdentity>) will still yield the same result as before, i.e.,ICommand.SourceId
being of typeISourceId
- New: The
AddDefaults(...)
now also adds the command type definition to the newICommandDefinitonService
- Breaking:
EventFlowOptions.AddDefaults(...)
now also adds query handlers - New: Added an optional
Predicate<Type>
to the following option extension methods that scan anAssembly
:AddAggregateRoots(...)
,AddCommandHandlers(...)
,AddDefaults(...)
,AddEventUpgraders(...)
,AddEvents(...)
,AddMetadataProviders(...)
,AddQueryHandlers(...)
andAddSubscribers(...)
- Fixed:
EventFlowOptions.AddAggregateRoots(...)
now prevents abstract classes from being registered when passingIEnumerable<Type>
- Fixed: Events published to RabbitMQ are now in the right order for chains
of subscribers, if
event A -> subscriber -> command -> aggregate -> event B
, then the order of published events to RabbitMQ wasevent B
and thenevent A
- Breaking: Aggregate root no longer have
Aggregate
removed from their when name, i.e., the metadata property with keyaggregate_name
(orMetadataKeys.AggregateName
). If you are dependent on the previous naming, use the newAggregateName
attribute and apply it to your aggregates - Breaking: Moved
Identity<>
andIIdentity
from theEventFlow.Aggregates
namespace toEventFlow.Core
as the identities are not specific for aggregates - Breaking:
ICommand.Id
is renamed toICommand.AggregateId
to make "room" for the newICommand.SourceId
property. If commands are serialized, then it might be important verify that the serialization still works. EventFlow does not serialize commands, so no mitigation is provided. If theCommand<,>
is used, make sure to use the correct protected constructor - Breaking:
IEventStore.StoreAsync(...)
now requires an additionalISourceId
argument. To create a random one, useSourceId.New
, but it should be e.g. the command ID that resulted in the events. Note, this method isn't typically used by developers - New: Added
ICommand.SourceId
, which contains the ID of the source. The default (if your commands inherit fromCommand<,>
) will be a newCommandId
each time the aCommand<,>
instance is created. You can pass specific value, merely use the newly added constructor taking the ID. Alternatively you commands could inherit from the newDistinctCommand
, enabling commands with the same state to have the sameSourceId
- New: Duplicate commands can be detected using the new
ISourceId
. Read the EventFlow article regarding commands for more details - New: Aggregate names can now be configured using the attribute
AggregateName
. The name can be accessed using the newIAggregateRoot.Name
property - New: Added
Identity<>.NewDeterministic(Guid, string)
enabling creation of deterministic GUIDs - New: Added new metadata key
source_id
(MetadataKeys.SourceId
) containing the source ID, typically the ID of the command from which the event originated - New: Added new metadata key
event_id
(MetadataKeys.EventId
) containing a deterministic ID for the event. Events with the same aggregate sequence number and from aggregates with the same identity, will have the same event identity - Fixed:
Identity<>.With(string)
now throws anArgumentException
instead of aTargetInvocationException
when passed an invalid identity - Fixed: Aggregate roots now build the cache of
Apply
methods once, instead of when the method is requested the first time
- Breaking:
EventFlowOptions.AddDefaults(...)
now also adds event definitions - New: RabbitMQ is now supported through the new
NuGet package called
EventFlow.RabbitMQ
which enables domain events to be published to the bus - New: If you want to subscribe to all domain events, you can implement
and register a service that implements
ISubscribeSynchronousToAll
. Services that implement this will automatically be added using theAddSubscribers(...)
orAddDefaults(...)
extension toEventFlowOptions
- New: Use
EventFlowOptions.UseAutofacAggregateRootFactory(...)
to use an Autofac aggregate root factory, enabling you to use services in your aggregate root constructor - New: Use
EventFlowOptions.UseResolverAggregateRootFactory()
to use the resolver to create aggregate roots. Same asUseAutofacAggregateRootFactory(...)
but for when using the internal IoC container - New: Use
EventFlowOptions.AddAggregateRoots(...)
to register aggregate root types - New: Use
IServiceRegistration.RegisterType(...)
to register services by type
- Breaking: Updated NuGet reference
Newtonsoft.Json
to v7.0.1 (up from v6.0.8) - Breaking: Remove the empty constructor from
SingleValueObject<>
- New: Added
SingleValueObjectConverter
to help create clean JSON when e.g. domain events are serialized - New: Added a protected method
Register(IEventApplier)
toAggregateRoot<,>
that enables developers to override how events are applied. Use this to e.g. implement state objects - New: Create
AggregateState<,,>
that developers can use to create aggregate state objects. CallRegister(...)
with the state object as argument to redirect events to it - New: Allow
AggregateRoot<,>.Apply(...)
, i.e., methods for applying events, to beprivate
andprotected
- New: Made
AggregateRoot<,>.Emit(...)
protected and virtual to allow overrides that e.g. add a standard set of metadata from the aggregate state. - New: Made
AggregateRoot<,>.ApplyEvent(...)
protected and virtual to allow more custom implementations of applying events to the aggregate root. - Fixed: Updated internal NuGet reference
Dapper
to v1.42 (up from v1.38)
- Braking:
IEventStore.LoadAllEventsAsync
andIEventStore.LoadAllEvents
now take aGlobalPosition
as an argument instead of along
for the starting position. TheGlobalPosition
is basically a wrapper around a string that hides the inner workings of each event store. - New: NuGet package
EventFlow.EventStores.EventStore
that provides integration to Event Store. Its an initial version and shouldn't be used in production.
-
Breaking: Remove all functionality related to global sequence numbers as it proved problematic to maintain. It also matches this quote:
Order is only assured per a handler within an aggregate root boundary. There is no assurance of order between handlers or between aggregates. Trying to provide those things leads to the dark side.
Greg Young
- If you use a MSSQL read store, be sure to delete the
LastGlobalSequenceNumber
column during update, or set it to defaultNULL
IDomainEvent.GlobalSequenceNumber
removedIEventStore.LoadEventsAsync
andIEventStore.LoadEvents
taking aGlobalSequenceNumberRange
removed
- If you use a MSSQL read store, be sure to delete the
-
Breaking: Remove the concept of event caches. If you really need this then implement it by registering a decorator for
IEventStore
-
Breaking: Moved
IDomainEvent.BatchId
to metadata and createdMetadataKeys.BatchId
to help access it -
New:
IEventStore.DeleteAggregateAsync
to delete an entire aggregate stream. Please consider carefully if you really want to use it. Storage might be cheaper than the historic knowledge within your events -
New:
IReadModelPopulator
is new and enables you to both purge and populate read models by going though the entire event store. Currently its only basic functionality, but more will be added -
New:
IEventStore
now hasLoadAllEventsAsync
andLoadAllEvents
that enables you to load all events in the event store a few at a time. -
New:
IMetadata.TimestampEpoch
contains the Unix timestamp version ofIMetadata.Timestamp
. Also, an additional metadata keytimestamp_epoch
is added to events containing the same data. Note, theTimestampEpoch
onIMetadata
handles cases in which thetimestamp_epoch
is not present by using the existing timestamp -
Fixed:
AggregateRoot<>
now reads the aggregate version from domain events applied during aggregate load. This resolves an issue for when anIEventUpgrader
removed events from the event stream -
Fixed:
InMemoryReadModelStore<,>
is now thread safe
- New: EventFlow now includes a
IQueryProcessor
that enables you to implement queries and query handlers in a structure manner. EventFlow ships with two ready-to-use queries and related handlersReadModelByIdQuery<TReadModel>
: Supported by in-memory and MSSQL read model storesInMemoryQuery<TReadModel>
: Only supported by in-memory read model store, but lets you search for any read model based on aPredicate<TReadModel>
- Breaking: Read models have been significantly improved as they can now
subscribe to events from multiple aggregates. Use a custom
IReadModelLocator
to define how read models are located. The suppliedILocateByAggregateId
simply uses the aggregate ID. To subscribe to other events, simply implementIAmReadModelFor<,,>
and make sure you have supplied a proper read model locator.UseMssqlReadModel
signature changed, change to.UseMssqlReadModel<MyReadModel, ILocateByAggregateId>()
in order to have the previous functionalityUseInMemoryReadStoreFor
signature changed, change to.UseInMemoryReadStoreFor<MyReadModel, ILocateByAggregateId>()
in order to have the previous functionality
- Breaking: A warning is no longer logged if you forgot to subscribe to a aggregate event in your read model as read models are no longer strongly coupled to a specific aggregate and its events
- Breaking:
ITransientFaultHandler
now takes the strategy as a generic argument instead of theUse<>
method. If you want to configure the retry strategy, useConfigureRetryStrategy(...)
instead - New: You can now have multiple
IReadStoreManager
if you would like to implement your own read model handling - New:
IEventStore
now has aLoadEventsAsync
andLoadEvents
that loadsIDomainEvent
s based on global sequence number range - New: Its now possible to register generic services without them being
constructed generic types, i.e., register
typeof(IMyService<>)
astypeof(MyService<>)
- New: Table names for MSSQL read models can be assigned using the
TableAttribute
fromSystem.ComponentModel.DataAnnotations
- Fixed: Subscribers are invoked after read stores have been updated, which ensures that subscribers can use any read models that were updated
- POTENTIAL DATA LOSS for files event store: Files event store now
stores its log as JSON instead of an
int
in the form{"GlobalSequenceNumber":2}
. So rename the current file and put in the global sequence number before startup - Breaking: Major changes has been made regarding how the aggregate
identity is implemented and referenced through interfaces. These changes makes
it possible to access the identity type directly though all interface. Some
notable examples are listed here. Note that this has NO impact on how data
is stored!
IAggregateRoot
changed toIAggregateRoot<TIdentity>
ICommand<TAggregate>
changed toICommand<TAggregate,TIdentity>
ICommandHandler<TAggregate,TCommand>
changed toICommandHandler<TAggregate,TIdentity, TCommand>
IAmReadModelFor<TEvent>
changed toIAmReadModelFor<TAggregate,TIdentity,TEvent>
IDomainEvent<TEvent>
changed toIDomainEvent<TAggregate,TIdentity>
- New:
ICommandBus.Publish
now takes aCancellationToken
argument - Fixed: MSSQL should list columns to SELECT when fetching events
- Breaking:
ValueObject
now uses public properties instead of both private and public fields - Breaking: Aggregate IDs are no longer
string
but objects implementingIIdentity
- Breaking: MSSQL transient exceptions are now retried
- Breaking: All methods on
IMsSqlConnection
has an extraLabel
argument - New:
ITransientFaultHandler
added along with default retry strategies for optimistic concurrency and MSSQL transient exceptions - New: Release notes added to NuGet packages
- New: Better logging and more descriptive exceptions
- Fixed: Unchecked missing in
ValueObject
when claculating hash - Fixed:
NullReferenceException
thrown ifnull
was stored inSingleValueObject
andToString()
was called
- First stable version of EventFlow