From eaeb8f76bc140f3667c6a5674e391670b77ca698 Mon Sep 17 00:00:00 2001 From: <> Date: Sat, 16 Nov 2024 10:43:32 +0000 Subject: [PATCH] Deployed c27cb47 with MkDocs version: 1.6.1 --- .nojekyll | 0 404.html | 1008 +++ CNAME | 1 + additional/configuration/index.html | 1117 +++ additional/dos-and-donts/index.html | 1308 ++++ additional/faq/index.html | 1239 +++ additional/snapshots/index.html | 1609 ++++ additional/specifications/index.html | 1200 +++ additional/value-objects/index.html | 1275 ++++ assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.83f73b43.min.js | 16 + assets/javascripts/bundle.83f73b43.min.js.map | 7 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.el.min.js | 1 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.he.min.js | 1 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.hy.min.js | 1 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.kn.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + assets/javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.te.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.6ce7567c.min.js | 42 + .../workers/search.6ce7567c.min.js.map | 7 + assets/logo.png | Bin 0 -> 3292 bytes assets/stylesheets/main.0253249f.min.css | 1 + assets/stylesheets/main.0253249f.min.css.map | 1 + assets/stylesheets/palette.06af60db.min.css | 1 + .../stylesheets/palette.06af60db.min.css.map | 1 + basics/aggregates/index.html | 1272 ++++ basics/commands/index.html | 1413 ++++ basics/event-upgrade/index.html | 1249 +++ basics/identity/index.html | 1173 +++ basics/jobs/index.html | 1311 ++++ basics/metadata/index.html | 1245 +++ basics/queries/index.html | 1243 +++ basics/sagas/index.html | 1320 ++++ basics/subscribers/index.html | 1411 ++++ getting-started/index.html | 1603 ++++ images/logo-with-contour.svg | 1 + index.html | 1153 +++ integration/event-stores/index.html | 1359 ++++ integration/mongodb/index.html | 1109 +++ integration/mssql/index.html | 1123 +++ integration/postgresql/index.html | 1174 +++ integration/rabbitmq/index.html | 1111 +++ integration/read-stores/index.html | 1593 ++++ integration/redis/index.html | 1202 +++ migrations/v0-to-v1/index.html | 1553 ++++ search/search_index.json | 1 + sitemap.xml | 103 + sitemap.xml.gz | Bin 0 -> 397 bytes 76 files changed, 40756 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 CNAME create mode 100644 additional/configuration/index.html create mode 100644 additional/dos-and-donts/index.html create mode 100644 additional/faq/index.html create mode 100644 additional/snapshots/index.html create mode 100644 additional/specifications/index.html create mode 100644 additional/value-objects/index.html create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.83f73b43.min.js create mode 100644 assets/javascripts/bundle.83f73b43.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.el.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.he.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js.map create mode 100644 assets/logo.png create mode 100644 assets/stylesheets/main.0253249f.min.css create mode 100644 assets/stylesheets/main.0253249f.min.css.map create mode 100644 assets/stylesheets/palette.06af60db.min.css create mode 100644 assets/stylesheets/palette.06af60db.min.css.map create mode 100644 basics/aggregates/index.html create mode 100644 basics/commands/index.html create mode 100644 basics/event-upgrade/index.html create mode 100644 basics/identity/index.html create mode 100644 basics/jobs/index.html create mode 100644 basics/metadata/index.html create mode 100644 basics/queries/index.html create mode 100644 basics/sagas/index.html create mode 100644 basics/subscribers/index.html create mode 100644 getting-started/index.html create mode 100644 images/logo-with-contour.svg create mode 100644 index.html create mode 100644 integration/event-stores/index.html create mode 100644 integration/mongodb/index.html create mode 100644 integration/mssql/index.html create mode 100644 integration/postgresql/index.html create mode 100644 integration/rabbitmq/index.html create mode 100644 integration/read-stores/index.html create mode 100644 integration/redis/index.html create mode 100644 migrations/v0-to-v1/index.html create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..8afd04da2 --- /dev/null +++ b/404.html @@ -0,0 +1,1008 @@ + + + +
+ + + + + + + + + + + + + + +EventFlow configuration can be done via the .Configure(o => {})
method, which is available on the EventFlowOptions
object.
1 +2 +3 +4 +5 +6 +7 +8 +9 |
|
In this example, we enable asynchronous subscribers and configure EventFlow to throw exceptions for subscriber errors. You can customize the configuration options to suit your needs.
+ + + + + + + + + + + + + + + + +Whenever creating an application that uses CQRS+ES there are several +things you need to keep in mind to make it easier and minimize the +potential bugs. This guide will give you some details on typical +problems and how EventFlow can help you minimize the risk.
+Consider moving complex business rules to specifications. +This eases both readability, testability and re-use.
+Make sure that when your aggregate events are JSON serialized, they +produce clean JSON as it makes it easier to work with and enables +easier deserialization of events in the future.
+Here's an example of good clean event JSON produced from a create user +event.
+1 +2 +3 +4 +5 |
|
Keep in mind that you need to keep the event types in your code for as +long as these events are in the event source, which in most cases is +forever as storage is cheap and information, i.e., your domain events, +are expensive.
+However, you should still clean your code. Have a look at how you can +upgrade and version your events for details on +how EventFlow supports you in this.
+Be very careful if aggregates emit multiple events for a single command, +subscribers will almost certainly +receive these out of order.
+ + + + + + + + + + + + + + + + +You can either replace the implementation of ICommandBus
with your own implementation, or add a decorator that adds the authentication logic.
While this is easy to support in some event stores like MSSQL, it +doesn't really make sense from a domain perspective. Greg Young also has +this to say on the subject:
+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. >
+ +Short answer, you shouldn't need it. But Mike has a way better answer:
+Quote
+In the Domain, everything flows in one direction: forward. When +something bad happens, a correction is applied. The Domain doesn't +care about the database and UoW is very coupled to the db. In my +opinion, it's a pattern which is usable only with data access +objects, and in probably 99% of the cases you won't be needing it. +As with the Singleton, there are better ways but everything depends +on proper domain design. > `Mike
+ +If your case falls within the 1% case, write a decorator for the
+ICommandBus
that starts a transaction, use MSSQL as event store and
+make sure your read models are stored in MSSQL as well.
It might be that your aggregates are emitting multiple events. Read about +subscribers and out of order events.
+ + + + + + + + + + + + + + + + +When working with long-lived aggregates, performance when loading +aggregates, and thereby making changes to them, becomes a real concern. +Consider aggregates that are comprised of several thousands of events, +some of which need to go through a rigorous +update process before they are applied to the +aggregates.
+EventFlow supports aggregate snapshots, which is basically a capture of +the entire aggregate state every few events. So instead of loading the +entire aggregate event history, the latest snapshot is loaded, then +applied to the aggregate and then the remaining events that were not +captured in the snapshot.
+To configure an aggregate root to support snapshots, inherit from
+SnapshotAggregateRoot<,,>
and define a serializable snapshot type
+that is marked with the ISnapshot
interface.
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 |
|
When using aggregate snapshots, there are several important details to +remember:
+When implementing an aggregate root that inherits from
+SnapshotAggregateRoot<,,>
, you need to pass the base class an
+implementation of ISnapshotStrategy
. The strategy is used to
+determine when a snapshot should be created, e.g., every 100 events.
EventFlow ships with two strategies that should be enough for most purposes as they +can be configured:
+SnapshotEveryFewVersionsStrategy:
Snapshots are created after a
+ predefined number of events, the default is 100
, but another
+ frequency can be specified.SnapshotRandomlyStrategy:
Snapshots are created randomly with a
+ predefined chance, the default is 1%
, but another can be
+ specified.As an application grows over time, the data required to be stored within +a snapshot will change. Either because some become obsolete or merely +because a better way of storing the aggregate state is found. If this +happens, the snapshots persisted in the snapshot store could potentially +become useless as aggregates are unable to apply them. The easy solution +would be to make change-by-addition and make sure that the old snapshots +can be deserialized into the new version.
+EventFlow provides an alternative solution, which is basically allowing +developers to upgrade snapshots similar to how events are +upgraded.
+Let's say we have an application that has developed three snapshot +versions over time.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 |
|
Note how version three of the UserAggregate
snapshot is called
+UserSnapshot
and not UserSnapshotV3
, it's basically to help
+developers tell which snapshot version is the current one.
Remember to add the [SnapshotVersion]
attribute as it enables
+control of the snapshot definition name. If left out, EventFlow will
+make a guess, which will be tied to the name of the class type.
The next step will be to implement upgraders, or mappers, that can +upgrade one snapshot to another.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 |
|
The snapshot types and upgraders then only need to be registered in +EventFlow.
+1 +2 +3 +4 |
|
Now, whenever a snapshot is loaded from the snapshot store, it is +automatically upgraded to the latest version and the aggregate only +needs to concern itself with the latest version.
+EventFlow has built-in support for some snapshot stores (more will be +implemented).
+The default implementation used by EventFlow does absolutely nothing +besides logging a warning if used. It exists only to help developers to +select a proper snapshot store. Making in-memory the default +implementation could present problems if snapshots were configured, but +the snapshot store configuration forgotten.
+For testing, or small applications, the in-memory snapshot store is
+configured by merely calling UseInMemorySnapshotStore()
.
+
1 +2 +3 |
|
To use the MSSQL snapshot store, you need to install the NuGet package
+EventFlow.MsSql
.
Configure the MSSQL connection and snapshot store as shown here.
+1 +2 +3 +4 +5 +6 +7 +8 +9 |
|
Note that if you already use MSSQL for the event- or read model store, you
+only need to invoke the ConfigureMsSql
extension once.
Before you can use the MSSQL snapshot store, the required database and +tables must be created. The database specified in your MSSQL connection +will not be automatically created, you have to do this yourself.
+To make EventFlow create the required tables, execute the following +code.
+1 +2 |
|
You should do this either on application start or preferably upon +application install or update, e.g., when the website is installed.
+If none of the above stores are adequate, a custom implementation is
+possible by implementing the interface ISnapshotPersistence
.
+However, there are some rules that the snapshot persistence store must
+follow:
EventFlow ships with an implementation of the +specification pattern +which could be used to e.g. make complex business rules easier to read and test.
+To use the specification implementation shipped with EventFlow, simply create a
+class that inherits from Specification<T>
.
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 |
|
Note that instead of simply returning a bool
to indicate whether or not the
+specification is satisfied, this implementation requires a reason (or reasons)
+why the specification is not satisfied.
The ISpecification<T>
interface has two methods defined, the traditional
+IsSatisfiedBy
as well as WhyIsNotSatisfiedBy
, which returns an
+empty enumerable if the specification was indeed satisfied.
1 +2 +3 +4 +5 +6 |
|
Specifications really become powerful when they are combined. EventFlow also
+ships with a series of extension methods for the ISpecification<T>
interface
+that allows easy combination of implemented specifications.
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 |
|
If you need a simple expression to combine with other more complex specifications
+you can use the bundled ExpressionSpecification<T>
, which is a specification
+wrapper for an expression.
1 +2 +3 +4 |
|
If the specification isn't satisfied, a string representation of the expression +is returned.
+ + + + + + + + + + + + + + + + +One of the important parts of creating an event sourced application is +to ensure that you can always read your event streams. It seems simple +enough, but it is a problem, especially for large applications that +undergo refactoring or domain changes.
+The basic idea is to store events in a structure that's easy to access +and migrate if the need should arise. EventFlow, like many other event +sourced systems, stores its events using JSON.
+You might wonder "but, why?", and the reason is somewhat similar to the +reasoning behind semantic URLs.
+Consider the following value object used to validate and contain +usernames in an application.
+1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 +12 +13 +14 |
|
First we do some cleanup and re-write it using EventFlows
+SingleValueObject<>
.
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 |
|
Now it looks simple and we might think we can use this value object +directly in our domain events. We could, but the resulting JSON will +look like this.
+1 +2 +3 +4 +5 |
|
This doesn't look very good. First, that extra property doesn't make it +easier to read and it takes up more space when serializing and +transmitting the event.
+In addition, if you use the value object in a web API, people using the +API will need to wrap the properties in their DTOs in a similar way. What +we would like is to modify our serialized event to look like this instead +and still use the value object in our events.
+1 +2 +3 |
|
To do this, we use the custom JSON serializer EventFlow has for single
+value objects called SingleValueObjectConverter
on our Username
+class like this.
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 +10 +11 |
|
The JSON converter understands the single value object and will +serialize and deserialize it correctly.
+Using this converter also enables to you replace e.g. raw string
and
+int
properties with value objects on existing events as they will be
+"JSON compatible".
Note
+Consider applying this to any classes that inherit from Identity<>
.