{"id":86814,"date":"2020-04-01T19:00:23","date_gmt":"2020-04-01T19:00:23","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=86814"},"modified":"2021-07-29T19:44:05","modified_gmt":"2021-07-29T19:44:05","slug":"getting-started-with-cqrs-part-3","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/getting-started-with-cqrs-part-3\/","title":{"rendered":"Getting Started with CQRS \u2013 Part 3"},"content":{"rendered":"<p><strong>The series so far:<\/strong><\/p>\n<ol>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/dotnet\/c-programming\/getting-started-with-cqrs-part-1\/\">Getting Started with CQRS \u2013 Part 1<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/dotnet\/c-programming\/getting-started-with-cqrs-part-2\/\">Getting Started with CQRS \u2013 Part 2<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/dotnet\/c-programming\/getting-started-with-cqrs-part-3\/\">Getting Started with CQRS \u2013 Part 3<\/a><\/li>\n<\/ol>\n\n<p>Until now, this series played a bit with a \u2018fake\u2019 CQRS implementation built on top of a simple architecture in ASP.NET. It used SQLite and Mongo to store the data at the end of the tiers. <a href=\"https:\/\/martinfowler.com\/bliki\/CQRS.html\">It\u2019s been well-known<\/a> through the community that this pattern, unlike many others, must be carefully analyzed before putting into your systems, especially if your systems are big and have been functioning for a long time.<\/p>\n<p>If you\u2019re a novice developer, or you don\u2019t have much help from experienced .NET engineers, I wouldn\u2019t recommend adding CQRS to your domain. There are tons of things to consider in order to scale up your applications, rather than going for the first design pattern that\u2019ll save the nation. You can (and certainly must) check the performance gaps, refactor the bottlenecks, and safely implement concurrency in places where it would be welcome.<\/p>\n<p>If you feel adventurous (and still want to play safe), you can go for <a href=\"https:\/\/eventstore.com\/\">Event Store<\/a> or <a href=\"https:\/\/axoniq.io\/\">Axon<\/a>.<\/p>\n<p>Instead of thinking about the cons of wanting to adopt CQRS, consider <a href=\"https:\/\/martinfowler.com\/eaaDev\/EventSourcing.html\"><strong>Event Sourcing<\/strong><\/a>, or ES. Those are the magic words. After all, why not save as much historical data of your system\u2019s events as possible?<\/p>\n<p>Just think about it. If your system<\/p>\n<ul>\n<li>deals with actions other than ordinary CRUD operations,<\/li>\n<li>or it needs to reconstitute the information from and to some point in history,<\/li>\n<li>or it needs to be audited or analyzed (i.e., machine learning, BI),<\/li>\n<li>or it is perfectly described by the events happening on it.<\/li>\n<\/ul>\n<p>Well, then you probably have the need for an ES solution.<\/p>\n<p>The irony here is that, for the sake of simplicity, the example will continue to use the Customers CRUD application, but this time adapted to the use of CQRS along with ES. But don\u2019t worry, I\u2019ll keep it simple and straightforward, so then you can follow up when applying to your systems.<\/p>\n<p>Before you jump into it, understand a bit about ES and some patterns that accompany it next.<\/p>\n<h2>What is Event Sourcing?<\/h2>\n<p>When you think about reality, how things work, and how they happen, you can see a chronological sequence of steps and events going on. The act of an event ending is called a <em>commit<\/em>.<\/p>\n<p>The same thing happens with systems. When an API receives a request and stores it to a database, an event (or, usually, a series of events) happens there. Whether it is synchronous or not, they\u2019re taking place in the servers while transporting data around. What if you store these events, like snapshots of how they are exactly when they happen? That\u2019s when ES comes into play.<\/p>\n<p>Then, you can take advantage of distributed systems, cloud and microservices to send those same events over a powerful message broker, store them in robust NoSQL databases, digest and transform them with flexible tools, all of that easily integrated, error-prone and monitored.<\/p>\n<p>Now, put all those events together in a stream, like a river, and let it flow. Alongside the stream, you can plug in subscribers, watchers, or readers that\u2019ll consume the data and do whatever they want with it, without affecting the original data. The data is read-only. Do you want to get the last record? Sort your stream and get the top record. Sounds good, doesn&#8217;t it?<\/p>\n<h2>Aggregating things<\/h2>\n<p>The <a href=\"https:\/\/www.martinfowler.com\/bliki\/DDD_Aggregate.html\">Aggregate pattern<\/a> is known for walking hand in hand with the ES. Once you\u2019re dealing with a stream, i.e., a list of items or records, it\u2019s often easier for the domain to consider a specific list of them as a single and cohesive unit &#8212; an aggregate.<\/p>\n<p>In other words, to be considered an aggregate a list of objects must be consistent together, they must be related at some point that makes sense to your domain. A good way to think of it is a transaction. If your objects usually commit together within a transaction, they are probably a good fit for an aggregate.<\/p>\n<p>The example project will make use of the pattern below. Then, you\u2019ll get to see how both patterns conversate with each other.<\/p>\n<p>Figure 1 shows how the final project will look.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1301\" height=\"750\" class=\"wp-image-86815\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/04\/word-image.png\" \/><\/p>\n<p class=\"caption\">Figure 1. Final project architecture<\/p>\n<p>Here, some new actors arrived at the house. The structure of commands and queries will practically remain the same, with some adjustments. A new layer to handle the commands is necessary to process each one and trigger the correspondent domain aggregations.<\/p>\n<p>The aggregations, in turn, take care of summarizing the data to the repository interface, the one which stores the events in an <strong>Event Store<\/strong>. To create the event store, you\u2019ll use an in-memory <em>Dictionary<\/em>, just to keep things simple. However, you\u2019re free to change any of the tools for the ones of your preference.<\/p>\n<p>An <strong>Event Bus<\/strong> layer supplies the features needed to send the events asynchronously from one side to the others. Both <em>Repository<\/em> and <em>Event Bus<\/em> layers are going to be reused for reading and writing models since they just define the interface methods for such.<\/p>\n<p>The <strong>Event Handlers<\/strong> are a couple of classes that deal with the same RabbitMQ messages created in the previous articles and then save the customers to the Mongo database. However, this time it will be with everything asynchronous.<\/p>\n<p>Mongo is going to be the database to supply data for the read model. Mainly because it\u2019s robust and performant, but also because the NoSQL adapts better to the query side, as already demonstrated. You can choose it to be your Event Store database as well.<\/p>\n<p>To help even more with the amount of code needed for this implementation, a part of it is going to take advantage of the <a href=\"https:\/\/github.com\/gautema\/CQRSlite\">CQRSLite framework<\/a>, an extended version of a framework proposed and created by <a href=\"https:\/\/github.com\/gregoryyoung\/m-r\">Greg Young<\/a>. It is a lightweight framework to help when creating CQRS and event sourcing applications in C# and provides the classes and features colored in red in Figure 1. Don\u2019t worry; by the end of the article, you\u2019ll understand how it works integrated with the application.<\/p>\n<h2>Setup adjustments<\/h2>\n<p>For this part of the tutorial, I\u2019ll change things a bit. First, the example doesn\u2019t use SQLite anymore. As you may have noticed, Mongo is the only database tool used for storing data.<\/p>\n<p>RabbitMQ, MongoDB, Compass, and Postman are used as developing and testing tools as before.<\/p>\n<p>Before proceeding, download the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/03\/CustomerApi.zip\">source code<\/a> to your local machine, and copy the <em>\/CQRSLite<\/em> folder to the root of your current CustomerApi project.<\/p>\n<p><em>Note: The reason you\u2019re copying from my project is that the original GitHub project is updated continuously, and so are the package names. If you still want to go with the GitHub version, be aware of these naming changes.<\/em><\/p>\n<p>The example also makes use of some new packages from NuGet:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.nuget.org\/packages\/NLog\/\">NLog<\/a>: it\u2019ll be interesting to have some logs spread around<\/li>\n<li><a href=\"https:\/\/www.nuget.org\/packages\/System.Runtime.Caching\/\">System.Runtime.Caching<\/a>: this one is useful for in-memory caching<\/li>\n<li><a href=\"https:\/\/github.com\/dotnet\/wcf\">System.ServiceModel.Primitives<\/a> and <a href=\"https:\/\/www.nuget.org\/packages\/System.Private.ServiceModel\/\">System.Private.ServiceModel<\/a>: these provide the common types used by all of the WCF libraries<\/li>\n<li><a href=\"https:\/\/www.nuget.org\/packages\/Castle.Windsor.MsDependencyInjection\/\">Castle.Windsor.MsDependencyInjection<\/a> and <a href=\"https:\/\/www.nuget.org\/packages\/Castle.Facilities.AspNetCore\/\">Castle.Facilities.AspNetCore<\/a>: the <a href=\"http:\/\/www.castleproject.org\/\">Castle Project<\/a> facilitates the configuration of dependencies and avoids circular dependencies problems since everything is going to work together within the same project.<\/li>\n<\/ul>\n<p>Go ahead and install them all via <em>NuGet Package Manager<\/em>.<\/p>\n<p>Don\u2019t forget to install the packages used in the previous two articles:<\/p>\n<ul>\n<li>RabbitMQ.Client<\/li>\n<li>mongocsharpdriver<\/li>\n<\/ul>\n<p>The current project structure will change almost completely. It can be seen in Figure 2. The only remaining folder is the <em>\/Controllers<\/em> which hosts the <em>CustomersController.cs<\/em> the same way, with a few changes to be made.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"356\" height=\"387\" class=\"wp-image-86816\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/04\/word-image-1.png\" \/><\/p>\n<p class=\"caption\">Figure 2. New project structure.<\/p>\n<p>Please, update your project with the same folders accordingly. The new division is made of:<\/p>\n<ul>\n<li><em>Commons<\/em>: it\u2019ll store common interfaces for the bus components, and repositories, as well as constants, exceptions, etc.<\/li>\n<li><em>CQRSLite<\/em>: the framework you\u2019ve imported previously<\/li>\n<li><em>ReadModels<\/em>: it\u2019ll contain the services to provide reading features for the queries<\/li>\n<li><em>WriteModels<\/em>: the domain, commands, events, event store for the command side.<\/li>\n<li><em>Services<\/em>: contains the services, in this case, just <em>CustomerService<\/em>.<\/li>\n<\/ul>\n<h2>The Write Models<\/h2>\n<p>Begin by working on the lengthiest side. The folder <em>\/WriteModels<\/em> will have five others inside of it: <em>\/Commands<\/em>, <em>\/Domain<\/em>, <em>\/Events<\/em>, <em>\/EventStore<\/em> and <em>\/VOs<\/em>. Go ahead and create them.<\/p>\n<p>Create a new class called <em>Command<\/em> (or move the old one to here), along with its three basic implementations: <em>CreateCustomerCommand<\/em>, <em>UpdateCustomerCommand<\/em> and <em>DeleteCustomerCommand<\/em>. You can check their respective codes in Listing 1 below.<\/p>\n<p class=\"caption\">Listing 1. Command classes.<\/p>\n<pre class=\"lang:c# theme:vs2012\">\/\/ class Command\r\nusing CQRSlite.Commands;\r\nusing System;\r\nusing System.Runtime.Serialization;\r\nnamespace CustomerApi.WriteModels.Commands\r\n{\r\n    [DataContract]\r\n    [KnownType(typeof(CreateCustomerCommand))]\r\n    [KnownType(typeof(UpdateCustomerCommand))]\r\n    [KnownType(typeof(DeleteCustomerCommand))]\r\n    public abstract class Command : ICommand\r\n    {\r\n        [DataMember]\r\n        public Guid Id { get; set; }\r\n        [DataMember]\r\n        public int ExpectedVersion { get; set; }\r\n    }\r\n}\r\n\/\/ class CreateCustomerCommand\r\nusing CustomerApi.WriteModels.VOs;\r\nusing System.Collections.Generic;\r\nusing System.Runtime.Serialization;\r\nnamespace CustomerApi.WriteModels.Commands\r\n{\r\n    [DataContract]\r\n    public class CreateCustomerCommand : Command\r\n    {\r\n        [DataMember]\r\n        public string Name { get; set; }\r\n        [DataMember]\r\n        public string Email { get; set; }\r\n        [DataMember]\r\n        public int Age { get; set; }\r\n        [DataMember]\r\n        public List&lt;Phone&gt; Phones { get; set; }\r\n    }\r\n}\r\n\/\/ class UpdateCustomerCommand\r\nusing CustomerApi.WriteModels.VOs;\r\nusing System.Collections.Generic;\r\nusing System.Runtime.Serialization;\r\nnamespace CustomerApi.WriteModels.Commands\r\n{\r\n\t[DataContract]\r\n\tpublic class UpdateCustomerCommand : Command\r\n\t{\r\n\t\t[DataMember]\r\n\t\tpublic string Name { get; set; }\r\n\t\t[DataMember]\r\n\t\tpublic int Age { get; set; }\r\n\t\t[DataMember]\r\n\t\tpublic List&lt;Phone&gt; Phones { get; set; }\r\n\t}\r\n}\r\n\/\/ class DeleteCustomerCommand\r\nusing System.Runtime.Serialization;\r\nnamespace CustomerApi.WriteModels.Commands\r\n{\r\n\t[DataContract]\r\n\tpublic class DeleteCustomerCommand : Command\r\n\t{\r\n\t\t\r\n\t}\r\n}<\/pre>\n<p>Note that their constitution is not that different from what\u2019s been created before, except for getting simpler. The id is now a <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.guid?view=netframework-4.8\">Guid<\/a> (A globally unique identifier represented by a hash string), which facilitates the usage with Mongo. The conversion methods (to events and entities) are removed and, now, left with the attributes only.<\/p>\n<p>They are annotated with data contracts from the <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.runtime.serialization?view=netframework-4.8\">System Serialization<\/a> package to help with the serialization and deserialization through the API.<\/p>\n<p>You\u2019ll see that the VOs\u2019 class is missing. For that, under the <em>\/VOs<\/em> folder, create a new class called <code>Phone<\/code> and add the following code:<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CustomerApi.Commons;\r\nusing System.Runtime.Serialization;\r\nnamespace CustomerApi.WriteModels.VOs\r\n{\r\n\t[DataContract]\r\n\tpublic class Phone\r\n\t{\r\n\t\t[DataMember]\r\n\t\tpublic PhoneType Type { get; set; }\r\n\t\t[DataMember]\r\n\t\tpublic int AreaCode { get; set; }\r\n\t\t[DataMember]\r\n\t\tpublic int Number { get; set; }\r\n\t}\r\n}<\/pre>\n<p>Next, create a new folder <em>\/Handlers<\/em> inside of the <em>\/Commands<\/em> folder, and add the class <code>CustomerCommandHandler<\/code> represented in Listing 2.<\/p>\n<p class=\"caption\">Listing 2. CustomerCommandHandler class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CQRSlite.Commands;\r\nusing CQRSlite.Domain;\r\nusing CustomerApi.WriteModels.Domain.Aggregates;\r\nusing CustomerApi.WriteModels.VOs;\r\nusing System;\r\nusing System.Linq;\r\nnamespace CustomerApi.WriteModels.Commands.Handlers\r\n{\r\n    public class CustomerCommandHandler : ICommandHandler&lt;CreateCustomerCommand&gt;,\r\n                                        ICommandHandler&lt;UpdateCustomerCommand&gt;,\r\n                                        ICommandHandler&lt;DeleteCustomerCommand&gt;\r\n    {\r\n        private readonly ISession _session;\r\n        private NLog.Logger logger = NLog.LogManager.GetLogger(\"CustomerCommandHandlers\");\r\n        public CustomerCommandHandler(ISession session)\r\n        {\r\n            _session = session;\r\n        }\r\n        public void Handle(CreateCustomerCommand command)\r\n        {\r\n            var item = new CustomerAggregate(\r\n                command.Id,\r\n                command.Email,\r\n                command.Name,\r\n                command.Age,\r\n                command.Phones.Select(x =&gt; new Phone()\r\n                {\r\n                    Type = x.Type,\r\n                    AreaCode = x.AreaCode,\r\n                    Number = x.Number\r\n                }).ToList(),\r\n                command.ExpectedVersion);\r\n            _session.Add(item);\r\n            _session.Commit();\r\n        }\r\n        private T Get&lt;T&gt;(Guid id, int? expectedVersion = null) where T : AggregateRoot\r\n        {\r\n            try\r\n            {\r\n                return _session.Get&lt;T&gt;(id, expectedVersion);\r\n            }\r\n            catch (Exception e)\r\n            {\r\n                logger.Error(\"Cannot get object of type {0} with id={1} ({2}) from session\", typeof(T), id, expectedVersion);\r\n                throw e;\r\n            }\r\n        }\r\n        public void Handle(UpdateCustomerCommand command)\r\n        {\r\n            logger.Info(\"Handling UpdateCustomerCommand {0} ({1})\", command.Id, command.ExpectedVersion);\r\n            CustomerAggregate item = Get&lt;CustomerAggregate&gt;(command.Id);\r\n            item.Update(\r\n                command.Id,\r\n                command.Name,\r\n                command.Age,\r\n                command.Phones.Select(x =&gt; new Phone()\r\n                {\r\n                    Type = x.Type,\r\n                    AreaCode = x.AreaCode,\r\n                    Number = x.Number\r\n                }).ToList(),\r\n                command.ExpectedVersion);\r\n            _session.Commit();\r\n        }\r\n        public void Handle(DeleteCustomerCommand command)\r\n        {\r\n            CustomerAggregate item = Get&lt;CustomerAggregate&gt;(command.Id);\r\n            item.Delete();\r\n            _session.Commit();\r\n        }\r\n    }\r\n}<\/pre>\n<p>There are some interesting changes here. The previous version of this class kept both the <code>repository<\/code> and <code>eventPublisher<\/code>, at the same time, to mock the behavior of a CQRS. Now, with the courtesy of CQRSLite, there is the <code>ICommandHandler<\/code> interface that enables the command handler to implement as many <code>Handle()<\/code> methods as the total of commands. This way, you can decide what is going to happen with each type of command once they arrive.<\/p>\n<p>Get back to Figure 1 again and take another look. From now on, you need to safely aggregate the command\u2019s data and store it to the event store as events. That\u2019s why you\u2019re saving, updating and deleting the information directly to the <em>session<\/em> object. The session is also another perk from the CQRSLite framework. It has a triad of repositories with fallback management, to assure that the information is going to be persisted. Of course, everything is in memory, so make sure to adapt the framework to connect to a real database in case you\u2019re considering using it in production.<\/p>\n<p>Each operation over the session must be committed. At the end of this operation, the framework publishes the event as a message to the <code>IEventPublisher<\/code> object it has injected. If you want to overwrite its behavior (and you do), you need to create your own publisher handler and implement it (soon).<\/p>\n<p><em>Obs: For all the CQRSLite components mentioned in this article, and for a better understanding, it\u2019s recommended that you go into each one and analyze their content.<\/em><\/p>\n<p>Next stop: <em>Domain<\/em> folder. This has two inner folders: <em>\/Aggregates<\/em> and <em>\/Bus<\/em> (create them). Inside of the first folder, create the class shown in Listing 3.<\/p>\n<p class=\"caption\">Listing 3. CustomerAggregate class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CQRSlite.Domain;\r\nusing CustomerApi.WriteModels.Events;\r\nusing CustomerApi.WriteModels.VOs;\r\nusing System;\r\nusing System.Collections.Generic;\r\nnamespace CustomerApi.WriteModels.Domain.Aggregates\r\n{\r\n    public class CustomerAggregate : AggregateRoot\r\n    {\r\n        private string email;\r\n        private string name;\r\n        private int age;\r\n        private List&lt;Phone&gt; phones;\r\n        private void Apply(CustomerCreatedEvent e)\r\n        {\r\n            Version = e.Version++;\r\n            email = e.Email;\r\n            age = e.Age;\r\n            phones = e.Phones;\r\n        }\r\n        private void Apply(CustomerUpdatedEvent e)\r\n        {\r\n            Version = e.Version++;\r\n            name = e.Name;\r\n            age = e.Age;\r\n            phones = e.Phones;\r\n        }\r\n        private void Apply(CustomerDeletedEvent e)\r\n        {\r\n            Version = e.Version++;\r\n        }\r\n        private CustomerAggregate() { }\r\n        public CustomerAggregate(Guid id, string email, string name, int age, List&lt;Phone&gt; phones, int version)\r\n        {\r\n            if (string.IsNullOrEmpty(email))\r\n            {\r\n                throw new ArgumentException(\"email\");\r\n            }\r\n            else if (string.IsNullOrEmpty(name))\r\n            {\r\n                throw new ArgumentException(\"name\");\r\n            }\r\n            else if (age == 0)\r\n            {\r\n                throw new ArgumentException(\"age\");\r\n            }\r\n            else if (phones == null || phones.Count == 0)\r\n            {\r\n                throw new ArgumentException(\"phones\");\r\n            }\r\n            Id = id;\r\n            ApplyChange(new CustomerCreatedEvent(id, email, name, age, phones, version));\r\n        }\r\n        public void Update(Guid id, string name, int age, List&lt;Phone&gt; phones, int version)\r\n        {\r\n            ApplyChange(new CustomerUpdatedEvent(id, name, age, phones, version));\r\n        }\r\n        public void Delete()\r\n        {\r\n            ApplyChange(new CustomerDeletedEvent(Id, Version));\r\n        }\r\n    }\r\n}<\/pre>\n<p>The Aggregate pattern is rich not only for allowing you to group data that\u2019s relevant but also for being a great place to validate it. Again, you\u2019re making use of <code>AggregateRoot<\/code> class from CQRSLite, which provides a handful of methods to apply the pattern to any object that extends from it. Don\u2019t forget that it keeps the list of events in memory, and it\u2019s ok to be this way since you\u2019re interested in the data being transported to the event store.<\/p>\n<p>For each creation, update or deletion, you\u2019re making sure to increment the version and validate the data. It\u2019s focused in simple validations, like whether the data is null or empty. Feel free to play around here too, even injecting other validator classes of your own and fancy treat the exception flows.<\/p>\n<p>In the <em>\/Bus<\/em> folder, start with the Rabbit stuff. You\u2019ve already created a publisher and a subscriber, so you\u2019ll just adapt them a bit and make sure it works integrated with CQRSLite. Please, refer to the Listings 4 and 5 for that.<\/p>\n<p class=\"caption\">Listing 4. AMQPEventSubscriber class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CQRSlite.Events;\r\nusing CustomerApi.WriteModels.Events;\r\nusing CustomerApi.WriteModels.Events.Handlers;\r\nusing Microsoft.AspNetCore.Hosting;\r\nusing Microsoft.Extensions.Configuration;\r\nusing Newtonsoft.Json;\r\nusing RabbitMQ.Client;\r\nusing RabbitMQ.Client.Events;\r\nusing RabbitMQ.Client.MessagePatterns;\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Reflection;\r\nusing System.Text;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nnamespace CustomerApi.WriteModels.Domain.Bus\r\n{\r\n    public class AMQPEventSubscriber\r\n    {\r\n        private readonly IBusEventHandler[] _handlers;\r\n        private Dictionary&lt;Type, MethodInfo&gt; lookups = new Dictionary&lt;Type, MethodInfo&gt;();\r\n        public AMQPEventSubscriber(IHostingEnvironment env, IBusEventHandler[] handlers)\r\n        {\r\n            _handlers = handlers;\r\n            foreach (var handler in _handlers)\r\n            {\r\n                var meth = (from m in handler.GetType()\r\n                        .GetMethods(BindingFlags.Public | BindingFlags.Instance)\r\n                            let prms = m.GetParameters()\r\n                            where prms.Count() == 1 &amp;&amp; m.Name.Contains(\"Handle\")\r\n                            select new\r\n                            {\r\n                                EventType = handler.HandlerType,\r\n                                Method = m\r\n                            }).FirstOrDefault();\r\n                if (meth != null)\r\n                {\r\n                    lookups.Add(meth.EventType, meth.Method);\r\n                }\r\n            }\r\n            new Thread(() =&gt;\r\n            {\r\n                Start(env.ContentRootPath);\r\n            }).Start();\r\n        }\r\n        public void Start(string contentRootPath)\r\n        {\r\n            ConnectionFactory connectionFactory = new ConnectionFactory();\r\n            var builder = new ConfigurationBuilder()\r\n                .SetBasePath(contentRootPath)\r\n                .AddJsonFile(\"appsettings.json\", optional: false, reloadOnChange: false)\r\n                .AddEnvironmentVariables();\r\n            builder.Build().GetSection(\"amqp\").Bind(connectionFactory);\r\n            connectionFactory.AutomaticRecoveryEnabled = true;\r\n            connectionFactory.NetworkRecoveryInterval = TimeSpan.FromSeconds(15);\r\n            using (IConnection conn = connectionFactory.CreateConnection())\r\n            {\r\n                using (IModel channel = conn.CreateModel())\r\n                {\r\n                    DeclareQueues(channel);\r\n                    var subscriptionCreated = new Subscription(channel, Constants.QUEUE_CUSTOMER_CREATED, false);\r\n                    var subscriptionUpdated = new Subscription(channel, Constants.QUEUE_CUSTOMER_UPDATED, false);\r\n                  var subscriptionDeleted = new Subscription(channel, Constants.QUEUE_CUSTOMER_DELETED, false);\r\n                    while (true)\r\n                    {\r\n                        \/\/ Sleeps for 5 sec before trying again\r\n                        Thread.Sleep(5000);\r\n                        new Thread(() =&gt;\r\n                        {\r\n                            ListerCreated(subscriptionCreated);\r\n                        }).Start();\r\n                        new Thread(() =&gt;\r\n                        {\r\n                            ListenUpdated(subscriptionUpdated);\r\n                        }).Start();\r\n                        new Thread(() =&gt;\r\n                        {\r\n                            ListenDeleted(subscriptionDeleted);\r\n                        }).Start();\r\n                    }\r\n                }\r\n            }\r\n        }\r\n        private void ListenDeleted(Subscription subscriptionDeleted)\r\n        {\r\n            BasicDeliverEventArgs eventArgsDeleted = subscriptionDeleted.Next();\r\n            if (eventArgsDeleted != null)\r\n            {\r\n                string messageContent = Encoding.UTF8.GetString(eventArgsDeleted.Body);\r\n                HandleEvent(JsonConvert.DeserializeObject&lt;CustomerDeletedEvent&gt;(messageContent));\r\n                subscriptionDeleted.Ack(eventArgsDeleted);\r\n            }\r\n        }\r\n        private void ListenUpdated(Subscription subscriptionUpdated)\r\n        {\r\n            BasicDeliverEventArgs eventArgsUpdated = subscriptionUpdated.Next();\r\n            if (eventArgsUpdated != null)\r\n            {\r\n                string messageContent = Encoding.UTF8.GetString(eventArgsUpdated.Body);\r\n                HandleEvent(JsonConvert.DeserializeObject&lt;CustomerUpdatedEvent&gt;(messageContent));\r\n                subscriptionUpdated.Ack(eventArgsUpdated);\r\n            }\r\n        }\r\n        private void ListerCreated(Subscription subscriptionCreated)\r\n        {\r\n            BasicDeliverEventArgs eventArgsCreated = subscriptionCreated.Next();\r\n            if (eventArgsCreated != null)\r\n            {\r\n                string messageContent = Encoding.UTF8.GetString(eventArgsCreated.Body);\r\n                HandleEvent(JsonConvert.DeserializeObject&lt;CustomerCreatedEvent&gt;(messageContent));\r\n                subscriptionCreated.Ack(eventArgsCreated);\r\n            }\r\n        }\r\n        private void HandleEvent(IEvent @event)\r\n        {\r\n            var theHandler = _handlers.SingleOrDefault(x =&gt; x.HandlerType == @event.GetType());\r\n            Task.Run(() =&gt;\r\n            {\r\n                foreach (KeyValuePair&lt;Type, MethodInfo&gt; entry in lookups)\r\n                {\r\n                    if (entry.Key == @event.GetType())\r\n                    {\r\n                        entry.Value.Invoke(theHandler, new[] { (object)@event });\r\n                    }\r\n                }\r\n            }).Wait();\r\n        }\r\n        private static void DeclareQueues(IModel channel)\r\n        {\r\n            channel.QueueDeclare(\r\n                queue: Constants.QUEUE_CUSTOMER_CREATED,\r\n                durable: false,\r\n                exclusive: false,\r\n                autoDelete: false,\r\n                arguments: null\r\n            );\r\n            channel.QueueDeclare(\r\n                queue: Constants.QUEUE_CUSTOMER_UPDATED,\r\n                durable: false,\r\n                exclusive: false,\r\n                autoDelete: false,\r\n                arguments: null\r\n            );\r\n            channel.QueueDeclare(\r\n                queue: Constants.QUEUE_CUSTOMER_DELETED,\r\n                durable: false,\r\n                exclusive: false,\r\n                autoDelete: false,\r\n                arguments: null\r\n            );\r\n        }\r\n    }\r\n}<\/pre>\n<p>Don\u2019t forget to add the same <code>Constants<\/code> class created in the previous tutorial to <em>\/Bus<\/em> folder:<\/p>\n<pre class=\"lang:c# theme:vs2012\">namespace CustomerApi.WriteModels.Domain.Bus\r\n{\r\n\tpublic class Constants\r\n    {\r\n\t\tpublic const string QUEUE_CUSTOMER_CREATED = \"customer_created\";\r\n\t\tpublic const string QUEUE_CUSTOMER_UPDATED = \"customer_updated\";\r\n\t\tpublic const string QUEUE_CUSTOMER_DELETED = \"customer_deleted\";\r\n\t}\r\n}<\/pre>\n<p>The class is pretty much the same as before, so this section. Except for the constructor that injects an array of <code>IBusEventHandler<\/code> (more on it soon). Just for you to not see a bunch of errors in your IDE, create this class into <em>Events\/Handlers\/<\/em> folder:<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CQRSlite.Events;\r\nusing System;\r\nnamespace CustomerApi.WriteModels.Events.Handlers\r\n{\r\n    public interface IBusEventHandler\r\n    {\r\n        Type HandlerType { get; }\r\n        void Handle(IEvent @event);\r\n    }\r\n}<\/pre>\n<p>Look carefully at the code in Listing 4\u2019s constructor. It makes use of reflection to collect the handler type (an attribute of each event handler to define which type of event it takes care of) and the respective method it must execute when called. The method, in case, will always be <code>Handle()<\/code>.<\/p>\n<p>Plus, the thread to start the RabbitMQ subscriber that was previously located into the Startup class, has been moved to this class, just to make more sense.<\/p>\n<p>Every <code>ListenXX<\/code> method calls the new <code>HandleEvent()<\/code> that, in turn, invokes (via reflection) the method mapped for the current event being subscribed.<\/p>\n<p class=\"caption\">Listing 5. AMQPEventPublisher class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CQRSlite.Events;\r\nusing CustomerApi.WriteModels.Events;\r\nusing Microsoft.AspNetCore.Hosting;\r\nusing Microsoft.Extensions.Configuration;\r\nusing Newtonsoft.Json;\r\nusing RabbitMQ.Client;\r\nusing System.Text;\r\nnamespace CustomerApi.WriteModels.Domain.Bus\r\n{\r\n\tpublic class AMQPEventPublisher : IEventPublisher\r\n\t{\r\n\t\tprivate readonly ConnectionFactory connectionFactory;\r\n\t\tpublic AMQPEventPublisher(IHostingEnvironment env, AMQPEventSubscriber aMQPEventSubscriber)\r\n\t\t{\r\n\t\t\tconnectionFactory = new ConnectionFactory();\r\n\t\t\tvar builder = new ConfigurationBuilder()\r\n\t\t\t\t.SetBasePath(env.ContentRootPath)\r\n\t\t\t\t.AddJsonFile(\"appsettings.json\", optional: false, reloadOnChange: false)\r\n\t\t\t\t.AddEnvironmentVariables();\r\n\t\t\t\r\n\t\t\tbuilder.Build().GetSection(\"amqp\").Bind(connectionFactory);\r\n\t\t}\r\n\t\tpublic void Publish&lt;T&gt;(T @event) where T : IEvent\r\n\t\t{\r\n\t\t\tusing (IConnection conn = connectionFactory.CreateConnection())\r\n\t\t\t{\r\n\t\t\t\tusing (IModel channel = conn.CreateModel())\r\n\t\t\t\t{\r\n\t\t\t\t\tvar queue = @event is CustomerCreatedEvent ? \r\n\t\t\t\t\t\tConstants.QUEUE_CUSTOMER_CREATED : @event is CustomerUpdatedEvent ? \r\n\t\t\t\t\t\t\tConstants.QUEUE_CUSTOMER_UPDATED : Constants.QUEUE_CUSTOMER_DELETED;\r\n\t\t\t\t\tchannel.QueueDeclare(\r\n\t\t\t\t\t\tqueue: queue,\r\n\t\t\t\t\t\tdurable: false,\r\n\t\t\t\t\t\texclusive: false,\r\n\t\t\t\t\t\tautoDelete: false,\r\n\t\t\t\t\t\targuments: null\r\n\t\t\t\t\t);\r\n\t\t\t\t\tvar body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(@event));\r\n\t\t\t\t\tchannel.BasicPublish(\r\n\t\t\t\t\t\texchange: \"\",\r\n\t\t\t\t\t\troutingKey: queue,\r\n\t\t\t\t\t\tbasicProperties: null,\r\n\t\t\t\t\t\tbody: body\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>The publisher class is the same as before, except it implements the <code>IEventPublisher<\/code> interface from CQRSLite &#8212; the one that is called by <code>session<\/code> after each commit.<\/p>\n<p>The <code>Constants<\/code> class, which hosts the names of the queues, must be moved to the <em>Bus<\/em> folder too.<\/p>\n<p>The folder <em>\/Events<\/em> hosts, as the name suggests, the same events already created. The changes are very slight. The first one is an <code>AbstractEvent<\/code> class to hold the information of ids, version, and datetime of the event:<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CQRSlite.Events;\r\nusing System;\r\nnamespace CustomerApi.WriteModels.Events\r\n{\r\n    public class AbstractEvent : IEvent\r\n    {\r\n        public Guid Id { get; set; }\r\n        public int Version { get; set; }\r\n        public DateTimeOffset TimeStamp { get; set; }\r\n    }\r\n}<\/pre>\n<p>This will compensate the repetition of values among its children. Look at the current implementation of the creation event:<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CustomerApi.WriteModels.VOs;\r\nusing System;\r\nusing System.Collections.Generic;\r\nnamespace CustomerApi.WriteModels.Events\r\n{\r\n\tpublic class CustomerCreatedEvent : AbstractEvent\r\n\t{\r\n\t\tpublic string Email { get; set; }\r\n\t\tpublic string Name { get; set; }\r\n\t\tpublic int Age { get; set; }\r\n\t\tpublic List&lt;Phone&gt; Phones { get; set; }\r\n\t\tpublic CustomerCreatedEvent()\r\n\t\t{\r\n\t\t}\r\n        public CustomerCreatedEvent(Guid id, string email, string name, int age, List&lt;Phone&gt; phones, int version)\r\n\t\t{\r\n\t\t\tId = id;\r\n\t\t\tEmail = email;\r\n\t\t\tName = name;\r\n\t\t\tAge = age;\r\n\t\t\tPhones = phones;\r\n\t\t\tVersion = version;\r\n        }\r\n\t}\r\n}<\/pre>\n<p>The converter methods are removed; the rest is just fields and constructors. If you feel that\u2019s too repetitive, you can set one single VO class for all the operations you\u2019re dealing with: database storing, API and event transits. Go ahead and adjust the other events alike:<\/p>\n<pre class=\"lang:c# theme:vs2012\">\/\/ CustomerUpdatedEvent class\r\nusing CustomerApi.WriteModels.VOs;\r\nusing System;\r\nusing System.Collections.Generic;\r\nnamespace CustomerApi.WriteModels.Events\r\n{\r\n\tpublic class CustomerUpdatedEvent : AbstractEvent\r\n\t{\r\n\t\tpublic string Name { get; set; }\r\n\t\tpublic int Age { get; set; }\r\n\t\tpublic List&lt;Phone&gt; Phones { get; set; }\r\n\t\tpublic CustomerUpdatedEvent()\r\n\t\t{\r\n\t\t}\r\n\t\tpublic CustomerUpdatedEvent(Guid id, string name, int age, List&lt;Phone&gt; phones, int version)\r\n\t\t{\r\n\t\t\tId = id;\r\n\t\t\tName = name;\r\n\t\t\tAge = age;\r\n\t\t\tPhones = phones;\r\n\t\t\tVersion = version;\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>And for the event of deletion, you have:<\/p>\n<pre class=\"lang:c# theme:vs2012\">\/\/ CustomerDeletedEvent class\r\nusing System;\r\nnamespace CustomerApi.WriteModels.Events\r\n{\r\n\tpublic class CustomerDeletedEvent : AbstractEvent\r\n\t{\r\n        public CustomerDeletedEvent(Guid id, int version)\r\n        {\r\n            Id = id;\r\n            Version = version;\r\n        }\r\n    }\r\n}<\/pre>\n<p>Now\u2019s time to create the implementations of <code>IBusEventHandler<\/code>. One for each event. Start with the event of creation (Listing 6).<\/p>\n<p class=\"caption\">Listing 6. CustomerCreatedEventHandler class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CQRSlite.Events;\r\nusing CustomerApi.ReadModels;\r\nusing CustomerApi.ReadModels.Repositories;\r\nusing NLog;\r\nusing System;\r\nusing System.Linq;\r\nnamespace CustomerApi.WriteModels.Events.Handlers\r\n{\r\n    public class CustomerCreatedEventHandler : IBusEventHandler\r\n    {\r\n        private readonly CustomerReadModelRepository readModelRepository;\r\n        private Logger logger = LogManager.GetLogger(\"CustomerCreatedEventHandler\");\r\n        public CustomerCreatedEventHandler(CustomerReadModelRepository readModelRepository)\r\n        {\r\n            this.readModelRepository = readModelRepository;\r\n        }\r\n        public Type HandlerType\r\n        {\r\n            get { return typeof(CustomerCreatedEvent); }\r\n        }\r\n        public async void Handle(IEvent @event)\r\n        {\r\n            CustomerCreatedEvent customerCreatedEvent = (CustomerCreatedEvent)@event;\r\n            \r\n            await readModelRepository.Create(new Customer()\r\n            {\r\n                Id = customerCreatedEvent.Id,\r\n                Email = customerCreatedEvent.Email,\r\n                Name = customerCreatedEvent.Name,\r\n                Age = customerCreatedEvent.Age,\r\n                Phones = customerCreatedEvent.Phones.Select(x =&gt;\r\n                    new Phone()\r\n                    {\r\n                        Type = x.Type,\r\n                        AreaCode = x.AreaCode,\r\n                        Number = x.Number\r\n                    }).ToList()\r\n            });\r\n            logger.Info(\"A new CustomerCreatedEvent has been processed: {0} ({1})\", customerCreatedEvent.Id, customerCreatedEvent.Version);\r\n        }\r\n    }\r\n}<\/pre>\n<p>The first thing to note is that those are the classes responsible for updating the final database, Mongo.<\/p>\n<p>Second, the <code>HandlerType<\/code>. Remember the reflection created to define which method takes care of each event that\u2019s arriving? Well, the <code>Handle(<\/code><em>)<\/em> is the method based on this type. Each handler is associated with an event by what\u2019s defined in <code>HandlerType<\/code> field.<\/p>\n<p>Third, be aware of the asynchronous nature of the methods now. It helps to turn the whole implementation faster by no locking of resources. The handling <em>per se<\/em> is just the same Mongo management seen before with a few changes.<\/p>\n<p>Listing 7 shows how the update event is handled.<\/p>\n<p class=\"caption\">Listing 7. CustomerUpdatedEventHandler class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CQRSlite.Events;\r\nusing CustomerApi.ReadModels;\r\nusing CustomerApi.ReadModels.Repositories;\r\nusing NLog;\r\nusing System;\r\nusing System.Linq;\r\nnamespace CustomerApi.WriteModels.Events.Handlers\r\n{\r\n    public class CustomerUpdatedEventHandler : IBusEventHandler\r\n    {\r\n        private readonly CustomerReadModelRepository readModelRepository;\r\n        \r\n        private Logger logger = LogManager.GetLogger(\"CustomerUpdatedEventHandler\");\r\n        public CustomerUpdatedEventHandler(CustomerReadModelRepository readModelRepository)\r\n        {\r\n            this.readModelRepository = readModelRepository;\r\n        }\r\n        public Type HandlerType\r\n        {\r\n            get { return typeof(CustomerUpdatedEvent); }\r\n        }\r\n        public async void Handle(IEvent @event)\r\n        {\r\n            CustomerUpdatedEvent customerUpdatedEvent = (CustomerUpdatedEvent)@event;\r\n            \r\n            Customer customer = await readModelRepository.GetCustomer(@event.Id);\r\n            await readModelRepository.Update(new Customer()\r\n            {\r\n                Id = customerUpdatedEvent.Id,\r\n                Email = customer.Email,\r\n                Name = customerUpdatedEvent.Name != null ? customerUpdatedEvent.Name : customer.Name,\r\n                Age = customerUpdatedEvent.Age != 0 ? customerUpdatedEvent.Age : customer.Age,\r\n                Phones = customerUpdatedEvent.Phones != null ? customerUpdatedEvent.Phones.Select(x =&gt;\r\n                    new Phone()\r\n                    {\r\n                        Type = x.Type,\r\n                        AreaCode = x.AreaCode,\r\n                        Number = x.Number\r\n                    }).ToList() : customer.Phones\r\n            });\r\n            logger.Info(\"A new CustomerUpdatedEvent has been processed: {0} ({1})\", customerUpdatedEvent.Id, customerUpdatedEvent.Version);\r\n        }\r\n    }\r\n}<\/pre>\n<p>This class has the implementation except for the handle method. This time, it checks for the nullity of each customer\u2019s attribute, since you don\u2019t want to update absent or null values.<\/p>\n<p>Finally, Listing 8 shows how the deletion takes place.<\/p>\n<p class=\"caption\">Listing 8. CustomerDeletedEventHandler class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CQRSlite.Events;\r\nusing CustomerApi.ReadModels.Repositories;\r\nusing NLog;\r\nusing System;\r\nnamespace CustomerApi.WriteModels.Events.Handlers\r\n{\r\n    public class CustomerDeletedEventHandler : IBusEventHandler\r\n    {\r\n        private readonly CustomerReadModelRepository readModelRepository;\r\n        private Logger logger = LogManager.GetLogger(\"CustomerDeletedEventHandler\");\r\n        public CustomerDeletedEventHandler(CustomerReadModelRepository readModelRepository)\r\n        {\r\n            this.readModelRepository = readModelRepository;\r\n        }\r\n        public Type HandlerType\r\n        {\r\n            get { return typeof(CustomerDeletedEvent); }\r\n        }\r\n        public async void Handle(IEvent @event)\r\n        {\r\n            CustomerDeletedEvent customerDeletedEvent = (CustomerDeletedEvent)@event;\r\n            \r\n            await readModelRepository.Remove(customerDeletedEvent.Id);\r\n            logger.Info(\"A new CustomerDeletedEvent has been processed: {0} ({1})\", customerDeletedEvent.Id, customerDeletedEvent.Version);\r\n        }\r\n    }\r\n}<\/pre>\n<p>This class has just a single call to Mongo\u2019s removal method. Don\u2019t worry about the errors in Visual Studio caused by not finding the class <code>CustomerReadModelRepository<\/code>. As soon it\u2019s created it in the read model part, they\u2019ll disappear.<\/p>\n<p>Now to finish the write model by creating the last class into the <em>\/EventStore<\/em> folder: <code>CustomerEventStore<\/code> (Listing 9).<\/p>\n<p class=\"caption\">Listing 9. CustomerEventStore class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CQRSlite.Events;\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nnamespace CustomerApi.WriteModels.EventStore\r\n{\r\n    public class CustomerEventStore : IEventStore\r\n    {\r\n        private readonly Dictionary&lt;Guid, List&lt;IEvent&gt;&gt; customerInMemDictionary = new Dictionary&lt;Guid, List&lt;IEvent&gt;&gt;();\r\n        public IEnumerable&lt;IEvent&gt; Get(Guid aggregateId, int fromVersion)\r\n        {\r\n            List&lt;IEvent&gt; customerEvents;\r\n            customerInMemDictionary.TryGetValue(aggregateId, out customerEvents);\r\n            if (customerEvents != null)\r\n            {\r\n                return customerEvents.Where(x =&gt; x.Version &gt; fromVersion);\r\n            }\r\n            return new List&lt;IEvent&gt;();\r\n        }\r\n        public void Save(IEvent @event)\r\n        {\r\n            List&lt;IEvent&gt; customerEvents;\r\n            customerInMemDictionary.TryGetValue(@event.Id, out customerEvents);\r\n            if (customerEvents == null)\r\n            {\r\n                customerEvents = new List&lt;IEvent&gt;();\r\n                customerInMemDictionary.Add(@event.Id, customerEvents);\r\n            }\r\n            customerEvents.Add(@event);\r\n        }\r\n    }\r\n}<\/pre>\n<p>It is a simple event store with just two operations: <code>get<\/code> an aggregated element from the history and <code>save<\/code> a new one to the dictionary. Note that the <code>get<\/code> method receives a second argument <code>fromVersion<\/code> that you\u2019ll use to filter the items from that specific version.<\/p>\n<p>The key of each aggregation is the customer Guid, and the values a List. Since List guarantees the order of the stacked items, you can rest assured that the events are ok.<\/p>\n<h2>The Read Models<\/h2>\n<p>The read models are much simpler; they constitute the Mongo entities (you can import the same ones from the previous article) and the Mongo repository. For the matter of the best architecture, you can decide if you want to place the classes responsible for the event handling here since they exist in the borders of both worlds.<\/p>\n<p>First, create the entity models into the <em>\/ReadModels<\/em> folder, adapting from the ones in the previous tutorial:<\/p>\n<pre class=\"lang:c# theme:vs2012\">\/\/ Customer entity class\r\nusing MongoDB.Bson;\r\nusing MongoDB.Bson.Serialization.Attributes;\r\nusing System;\r\nusing System.Collections.Generic;\r\nnamespace CustomerApi.ReadModels\r\n{\r\n\tpublic class Customer\r\n    {\r\n\t\t[BsonElement(\"Id\")]\r\n\t\tpublic Guid Id { get; set; }\r\n\t\t[BsonElement(\"Email\")]\r\n\t\tpublic string Email { get; set; }\r\n\t\t[BsonElement(\"Name\")]\r\n\t\tpublic string Name { get; set; }\r\n\t\t[BsonElement(\"Age\")]\r\n\t\tpublic int Age { get; set; }\r\n\t\t[BsonElement(\"Phones\")]\r\n\t\tpublic List&lt;Phone&gt; Phones;\r\n\t}\r\n}\r\n\/\/ Phone entity class\r\nusing CustomerApi.Commons;\r\nusing MongoDB.Bson.Serialization.Attributes;\r\nnamespace CustomerApi.ReadModels\r\n{\r\n\tpublic partial class Phone\r\n    {\r\n\t\t[BsonElement(\"Type\")]\r\n\t\tpublic PhoneType Type { get; set; }\r\n\t\t[BsonElement(\"AreaCode\")]\r\n\t\tpublic int AreaCode { get; set; }\r\n\t\t[BsonElement(\"Number\")]\r\n\t\tpublic int Number { get; set; }\r\n\t}\r\n}<\/pre>\n<p>Listing 10 shows the content of the <em>CustomerReadModelRepository<\/em> class.<\/p>\n<p class=\"caption\">Listing 10. CustomerReadModelRepository class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using System;\r\nusing System.Collections.Generic;\r\nusing System.Threading.Tasks;\r\nusing MongoDB.Driver;\r\nnamespace CustomerApi.ReadModels.Repositories\r\n{\r\n    public class CustomerReadModelRepository\r\n\t{\r\n\t\tprivate const string _customerDB = \"CustomerDB\";\r\n\t\tprivate const string _customerCollection = \"Customers\";\r\n\t\tprivate IMongoDatabase _db;\r\n\t\tpublic CustomerReadModelRepository()\r\n\t\t{\r\n\t\t\tMongoClient _client = new MongoClient(\"mongodb:\/\/localhost:27017\");\r\n\t\t\t_db = _client.GetDatabase(_customerDB);\r\n\t\t\t_db.DropCollection(_customerCollection);\r\n\t\t\t_db.CreateCollection(_customerCollection);\r\n\t\t}\r\n\t\tpublic Task&lt;List&lt;Customer&gt;&gt; GetCustomers()\r\n\t\t{\r\n\t\t\treturn Task.Run(() =&gt;\r\n\t\t\t{\r\n\t\t\t\treturn _db.GetCollection&lt;Customer&gt;(_customerCollection).Find(_ =&gt; true).ToList();\r\n\t\t\t});\r\n\t\t}\r\n\t\tpublic Task&lt;Customer&gt; GetCustomer(Guid id)\r\n\t\t{\r\n\t\t\treturn Task.Run(() =&gt;\r\n\t\t\t{\r\n\t\t\t\treturn _db.GetCollection&lt;Customer&gt;(_customerCollection).Find(customer =&gt; customer.Id.Equals(id)).SingleOrDefault();\r\n\t\t\t});\r\n\t\t}\r\n\t\tpublic Task&lt;List&lt;Customer&gt;&gt; GetCustomerByEmail(string email)\r\n\t\t{\r\n\t\t\treturn Task.Run(() =&gt;\r\n\t\t\t{\r\n\t\t\t\treturn _db.GetCollection&lt;Customer&gt;(_customerCollection).Find(customer =&gt; customer.Email == email).ToList();\r\n\t\t\t});\r\n        }\r\n\t\tpublic Task&lt;bool&gt; Create(Customer customer)\r\n\t\t{\r\n\t\t\treturn Task.Run(() =&gt;\r\n\t\t\t{\r\n\t\t\t\t_db.GetCollection&lt;Customer&gt;(_customerCollection).InsertOne(customer);\r\n\t\t\t\treturn true;\r\n\t\t\t});\r\n\t\t}\r\n\t\tpublic Task&lt;bool&gt; Update(Customer customer)\r\n\t\t{\r\n\t\t\treturn Task.Run(() =&gt;\r\n\t\t\t{\r\n\t\t\t\tvar filter = Builders&lt;Customer&gt;.Filter.Where(_ =&gt; _.Id == customer.Id);\r\n\t\t\t\t_db.GetCollection&lt;Customer&gt;(_customerCollection).ReplaceOne(filter, customer);\r\n                return true;\r\n\t\t\t});\r\n\t\t}\r\n\t\tpublic Task&lt;bool&gt; Remove(Guid id)\r\n\t\t{\r\n\t\t\treturn Task.Run(() =&gt;\r\n\t\t\t{\r\n\t\t\t\tvar filter = Builders&lt;Customer&gt;.Filter.Where(_ =&gt; _.Id.Equals(id));\r\n\t\t\t\tvar operation = _db.GetCollection&lt;Customer&gt;(_customerCollection).DeleteOne(filter);\r\n                return true;\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>Most of its content remained the same. Pay attention to the dropping and recreating of the Mongo collection right into the constructor. That\u2019s because the whole implementation works based on in-memory collections storing the data and simulating a real environment.<\/p>\n<p>Once you restart the application multiple times and create many events, they\u2019ll lose the link to the original data in the physical database.<\/p>\n<p>Here, two options apply: you get to choose whether you prefer to implement the same repository to the session and event store management or delete all the items from the database, for the sake of simplicity. You can also create a routine to insert data to both database and event store, in order to test without having to input manually every time.<\/p>\n<p>Except for that, the only change left is that the methods are now async. Every operation runs inside of a new thread and returns a <code>Task<\/code>. The same task would be transported all the way up to the controller and, so, to the ASP.NET HTTP async engine, making the application\u2019s architecture async.<\/p>\n<h2>The rest of the classes<\/h2>\n<p>Two packages remain to be coded. Start with the <em>\/Commons<\/em> folder. The first class to be created is <code>PhoneType<\/code> (that already existed). I decided to put it here to facilitate its usage over the whole application.<\/p>\n<pre class=\"lang:c# theme:vs2012\">namespace CustomerApi.Commons\r\n{\r\n\tpublic enum PhoneType\r\n\t{\r\n\t\tHOMEPHONE, CELLPHONE, WORKPHONE\r\n\t}\r\n}<\/pre>\n<p>The <code>ServiceException <\/code>class just embraces a new type of exception to be used in the services. It\u2019s a generic utilitarian exception class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using System;\r\nusing System.Runtime.Serialization;\r\nusing System.Security;\r\nnamespace CustomerApi.Commons\r\n{\r\n    public class ServiceException : Exception\r\n    {\r\n        public ServiceException() : base() { }\r\n        public ServiceException(string message) : base(message) { }\r\n        [SecuritySafeCritical]\r\n        protected ServiceException(SerializationInfo info, StreamingContext context) : base(info, context) { }\r\n        public ServiceException(string message, Exception innerException) : base(message, innerException) { }\r\n    }\r\n}<\/pre>\n<p>The <em>\/Services<\/em> folder, in turn, is made of the methods that embrace both queries and commands of the CQRS model. Here, it\u2019s interesting to note that the development of these facades doesn\u2019t have to necessarily be in the same class, not even in the same project.<\/p>\n<p>Methods to request queries or issue commands can be in different applications and still work well as a distributed system that accesses the same data sources.<\/p>\n<p>Despite the fact they are put together, take a look at how they differentiate from each other (Listing 11).<\/p>\n<p class=\"caption\">Listing 11. CustomerService class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CustomerApi.Commons;\r\nusing CustomerApi.ReadModels;\r\nusing CustomerApi.ReadModels.Repositories;\r\nusing CustomerApi.WriteModels.Commands;\r\nusing CustomerApi.WriteModels.Commands.Handlers;\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Reflection;\r\nusing System.Threading.Tasks;\r\nnamespace CustomerApi.Services\r\n{\r\n    public class CustomerService : ICustomerService\r\n    {\r\n        private readonly CustomerCommandHandler _commandHandlers;\r\n        private readonly CustomerReadModelRepository _readModelRepository;\r\n        public CustomerService(CustomerCommandHandler commandHandlers, CustomerReadModelRepository readModelRepository)\r\n        {\r\n            this._commandHandlers = commandHandlers;\r\n            this._readModelRepository = readModelRepository;\r\n        }\r\n        public async Task&lt;bool&gt; IssueCommandAsync(Command cmd)\r\n        {\r\n            await Task.Run(() =&gt;\r\n            {\r\n                var method = (from meth in typeof(CustomerCommandHandler)\r\n                    .GetMethods(BindingFlags.Public | BindingFlags.Instance)\r\n                              let @params = meth.GetParameters()\r\n                              where @params.Count() == 1 &amp;&amp; @params[0].ParameterType == cmd.GetType()\r\n                              select meth).FirstOrDefault();\r\n                if (method == null)\r\n                {\r\n                    var name = cmd.GetType().Name;\r\n                    throw new ServiceException(string.Format(\"Command handler of {0} not found\", name));\r\n                }\r\n                method.Invoke(_commandHandlers, new[] { cmd });\r\n            });\r\n            return true;\r\n        }\r\n        public async Task&lt;List&lt;Customer&gt;&gt; GetAllCustomersAsync()\r\n        {\r\n            return await _readModelRepository.GetCustomers();\r\n        }\r\n        public async Task&lt;Customer&gt; GetCustomerAsync(Guid orderId)\r\n        {\r\n            return await _readModelRepository.GetCustomer(orderId);\r\n        }\r\n        public async Task&lt;List&lt;Customer&gt;&gt; GetCustomersByEmailAsync(string email)\r\n        {\r\n            return await _readModelRepository.GetCustomerByEmail(email);\r\n        }\r\n    }\r\n}<\/pre>\n<p>There are two main parts in this class, identifying the queries and commands. The method <code>IssueCommandAsync()<\/code> takes as a parameter a command itself to run in the correspondent command handler. Here, it\u2019s making use of reflection once more to inflect flexibility, similarly to what was done before in the write model.<\/p>\n<p>The rest of the methods are all query related. They access the Mongo repository directly to ensure they\u2019ll get the stored information, i.e., the data that\u2019s not guaranteed to be the most recent, which is fine given the CQRS nature of the API.<\/p>\n<p>Here\u2019s how the interface of the service <code>ICustomerService<\/code> should look:<\/p>\n<pre class=\"lang:c# theme:vs2012\">using System;\r\nusing System.Collections.Generic;\r\nusing System.ServiceModel;\r\nusing System.Threading.Tasks;\r\nusing CustomerApi.ReadModels;\r\nusing CustomerApi.WriteModels.Commands;\r\nnamespace CustomerApi.Services\r\n{\r\n    [ServiceContract]\r\n    public interface ICustomerService\r\n    {\r\n        [OperationContract]\r\n        Task&lt;bool&gt; IssueCommandAsync(Command cmd);\r\n        [OperationContract]\r\n        Task&lt;List&lt;Customer&gt;&gt; GetAllCustomersAsync();\r\n        [OperationContract]\r\n        Task&lt;Customer&gt; GetCustomerAsync(Guid custId);\r\n        [OperationContract]\r\n        Task&lt;List&lt;Customer&gt;&gt; GetCustomersByEmailAsync(string email);\r\n    }\r\n}<\/pre>\n<h2>Controller and Startup<\/h2>\n<p>The controller class also remains almost intact. Regarding the structure, method signatures, etc. nothing has suffered changes. However, now it\u2019s async by the core, and so, the methods should be too. See at Listing 12 the new code.<\/p>\n<p class=\"caption\">Listing 12. New CustomersController class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using CustomerApi.Services;\r\nusing CustomerApi.WriteModels.Commands;\r\nusing Microsoft.AspNetCore.Mvc;\r\nusing System;\r\nusing System.Threading.Tasks;\r\nnamespace CustomerApi.Controllers\r\n{\r\n    [Route(\"api\/[controller]\")]\r\n    public class CustomersController : Controller\r\n    {\r\n        private readonly ICustomerService _customerService;\r\n        public CustomersController(ICustomerService customerService)\r\n        {\r\n            _customerService = customerService;\r\n        }\r\n        [HttpGet]\r\n        public Task&lt;IActionResult&gt; Get([FromQuery] string email)\r\n        {\r\n            return Task.Run(async () =&gt;\r\n            {\r\n                if (email != null)\r\n                {\r\n                    IActionResult result = NotFound();\r\n                    var customer = await _customerService.GetCustomersByEmailAsync(email);\r\n                    if (customer != null)\r\n                    {\r\n                        result = new ObjectResult(customer);\r\n                    }\r\n                    return result;\r\n                }\r\n                else\r\n                {\r\n                    return new ObjectResult(await _customerService.GetAllCustomersAsync());\r\n                }\r\n            });\r\n        }\r\n        [HttpGet(\"{id}\", Name = \"GetCustomer\")]\r\n        public Task&lt;IActionResult&gt; GetById(Guid id)\r\n        {\r\n            return Task.Run(async () =&gt;\r\n            {\r\n                IActionResult result = NotFound();\r\n                var customer = await _customerService.GetCustomerAsync(id);\r\n                if (customer != null)\r\n                {\r\n                    result = new ObjectResult(customer);\r\n                }\r\n                return result;\r\n            });\r\n        }\r\n        [HttpPost]\r\n        public Task&lt;IActionResult&gt; Post([FromBody] CreateCustomerCommand customer)\r\n        {\r\n            customer.Id = Guid.NewGuid();\r\n            return Task.Run(async () =&gt;\r\n            {\r\n                IActionResult result = NotFound();\r\n                bool created = await _customerService.IssueCommandAsync(customer);\r\n                if (created)\r\n                {\r\n                    result = CreatedAtRoute(\"GetCustomer\", new { id = customer.Id }, customer);\r\n                }\r\n                return result;\r\n            });\r\n        }\r\n        [HttpPut(\"{id}\")]\r\n        public Task&lt;IActionResult&gt; Put(Guid id, [FromBody] UpdateCustomerCommand customer)\r\n        {\r\n            return Task.Run(async () =&gt;\r\n            {\r\n                IActionResult result = NotFound();\r\n                var record = await _customerService.GetCustomerAsync(id);\r\n                if (record != null)\r\n                {\r\n                    customer.Id = id;\r\n                    bool updated = await _customerService.IssueCommandAsync(customer);\r\n                    if (updated)\r\n                    {\r\n                        result = NoContent();\r\n                    }\r\n                }\r\n                return result;\r\n            });\r\n        }\r\n        [HttpDelete(\"{id}\")]\r\n        public Task&lt;IActionResult&gt; Delete(Guid id)\r\n        {\r\n            return Task.Run(async () =&gt;\r\n            {\r\n                IActionResult result = NotFound();\r\n                var record = await _customerService.GetCustomerAsync(id);\r\n                if (record != null)\r\n                {\r\n                    bool updated = await _customerService.IssueCommandAsync(new DeleteCustomerCommand()\r\n                    {\r\n                        Id = id\r\n                    });\r\n                    if (updated)\r\n                    {\r\n                        result = NoContent();\r\n                    }\r\n                }\r\n                return result;\r\n            });\r\n        }\r\n    }\r\n}<\/pre>\n<p>The changes are very straightforward, so there is not much to do here.<\/p>\n<p>The Startup class, in turn, changed a bit more, since you\u2019re adding a new library to manage the registration of the services to the <code>IServiceProvider<\/code>. Look at Listing 13 for that.<\/p>\n<p class=\"caption\">Listing 13. Startup configuration class.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using Castle.Facilities.AspNetCore;\r\nusing Castle.MicroKernel.Registration;\r\nusing Castle.Windsor;\r\nusing CQRSlite.Cache;\r\nusing CQRSlite.Domain;\r\nusing CQRSlite.Events;\r\nusing CustomerApi.Controllers;\r\nusing CustomerApi.ReadModels.Repositories;\r\nusing CustomerApi.Services;\r\nusing CustomerApi.WriteModels.Commands.Handlers;\r\nusing CustomerApi.WriteModels.Domain.Bus;\r\nusing CustomerApi.WriteModels.Events.Handlers;\r\nusing CustomerApi.WriteModels.EventStore;\r\nusing Microsoft.AspNetCore.Builder;\r\nusing Microsoft.AspNetCore.Hosting;\r\nusing Microsoft.AspNetCore.Http;\r\nusing Microsoft.Extensions.Configuration;\r\nusing Microsoft.Extensions.DependencyInjection;\r\nusing System;\r\nusing System.Threading.Tasks;\r\nnamespace CustomerApi\r\n{\r\n    public class Startup\r\n    {\r\n        private IHostingEnvironment _env;\r\n        private static readonly WindsorContainer Container = new WindsorContainer();\r\n        public Startup(IHostingEnvironment env, IConfiguration config)\r\n        {\r\n            _env = env;\r\n            Configuration = config;\r\n        }\r\n        public IConfiguration Configuration { get; }\r\n        \/\/ This method gets called by the runtime. Use this method to add services to the container.\r\n        public IServiceProvider ConfigureServices(IServiceCollection services)\r\n        {\r\n            services.AddMvc();\r\n            \/\/ Setup component model contributors for making windsor services available to IServiceProvider\r\n            Container.AddFacility&lt;AspNetCoreFacility&gt;(f =&gt; f.CrossWiresInto(services));\r\n            \/\/ Custom application component registrations, ordering is important here\r\n            RegisterApplicationComponents(services);\r\n            \/\/ Castle Windsor integration, controllers, tag helpers and view components, this should always come after RegisterApplicationComponents\r\n            return services.AddWindsor(Container,\r\n                opts =&gt; opts.UseEntryAssembly(typeof(CustomersController).Assembly), \/\/ &lt;- Recommended\r\n                () =&gt; services.BuildServiceProvider(validateScopes: false)); \/\/ &lt;- Optional\r\n        }\r\n        private void RegisterApplicationComponents(IServiceCollection services)\r\n        {\r\n            \/\/ Application components\r\n            Container.Register(\r\n                    Component.For&lt;IEventPublisher&gt;().ImplementedBy&lt;AMQPEventPublisher&gt;().LifeStyle.Singleton,\r\n                    Component.For&lt;AMQPEventSubscriber&gt;().LifeStyle.Singleton,\r\n                    Component.For&lt;CustomerCommandHandler&gt;().LifeStyle.Transient,\r\n                    Component.For&lt;CustomerReadModelRepository&gt;().LifeStyle.Singleton,\r\n                    Component.For&lt;ICustomerService&gt;().ImplementedBy&lt;CustomerService&gt;().LifeStyle.Transient,\r\n                    Component.For&lt;CQRSlite.Domain.ISession&gt;().ImplementedBy&lt;Session&gt;(),\r\n                    Component.For&lt;IEventStore&gt;().ImplementedBy&lt;CustomerEventStore&gt;().LifeStyle.Singleton,\r\n                    Component.For&lt;IBusEventHandler&gt;().ImplementedBy&lt;CustomerCreatedEventHandler&gt;()\r\n                        .Named(\"CustomerCreatedEventHandler\").LifeStyle.Singleton,\r\n                    Component.For&lt;IBusEventHandler&gt;().ImplementedBy&lt;CustomerUpdatedEventHandler&gt;()\r\n                        .Named(\"CustomerUpdatedEventHandler\").LifeStyle.Singleton,\r\n                    Component.For&lt;IBusEventHandler&gt;().ImplementedBy&lt;CustomerDeletedEventHandler&gt;()\r\n                        .Named(\"CustomerDeletedEventHandler\").LifeStyle.Singleton,\r\n                    Component.For&lt;IRepository&gt;().UsingFactoryMethod(\r\n                        kernel =&gt;\r\n                        {\r\n                            return new CacheRepository(new Repository( \/\/\r\n                                kernel.Resolve&lt;IEventStore&gt;(), kernel.Resolve&lt;IEventPublisher&gt;()), kernel.Resolve&lt;IEventStore&gt;());\r\n                        }));\r\n        }\r\n        public void Configure(IApplicationBuilder app, IHostingEnvironment env)\r\n        {\r\n            if (env.IsDevelopment())\r\n            {\r\n                app.UseDeveloperExceptionPage();\t\r\n            }\r\n            \/\/ For making component registrations of middleware easier\r\n            Container.GetFacility&lt;AspNetCoreFacility&gt;().RegistersMiddlewareInto(app);\r\n            app.UseMvc();\r\n        }\r\n    }\r\n    \/\/ Example of framework configured middleware component, can't consume types registered in Windsor\r\n    public class FrameworkMiddleware : IMiddleware\r\n    {\r\n        public async Task InvokeAsync(HttpContext context, RequestDelegate next)\r\n        {\r\n            await next(context);\r\n        }\r\n    }\r\n}<\/pre>\n<p>First, an instance of <code>WindsorContainer<\/code> needs to be created on the top of the class, to provide the methods for registering the services and adding facilities. The code itself is commented, so you can understand the context while following the configs down the lines.<\/p>\n<p>Don\u2019t forget to add any of the services, repositories and handlers, since that\u2019ll lead to errors.<\/p>\n<p>That\u2019s it. It was a long way to the implementation, and now\u2019s time to test it.<\/p>\n<p>As a warning, remember that you\u2019ve been using the same Rabbit queues created in parts 1 and 2 of this series. Before testing, you need to make sure that those queues are empty; otherwise, old messages stuck in there could cause problems with the new event subscriber.<\/p>\n<p>For that, just go to the Rabbit management panel, click in <em>Queues<\/em> tab and locate the option <em>Purge<\/em> at the end of the screen. Click the button and confirm the action. Then, do the same with the <em>Delete<\/em> option, right above. See Figure 3 for reference. Don\u2019t worry, every time the app starts up, it checks for the existence of the queues and, if they\u2019re not there, the app recreates them.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"397\" height=\"189\" class=\"wp-image-86817\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/04\/word-image-2.png\" \/><\/p>\n<p class=\"caption\">Figure 3. Purging and deleting queues.<\/p>\n<p>Now, run the application, open Postman, and run the same requests configured in Part 2 of this tutorial. You don\u2019t have to change anything to send a POST request, for example.<\/p>\n<p>The result should look like:<\/p>\n<pre class=\"lang:c# theme:vs2012\">{\r\n\u00a0\u00a0\u00a0\u00a0\"name\":\u00a0\"John\u00a0Paul\u00a02\",\r\n\u00a0\u00a0\u00a0\u00a0\"email\":\u00a0\"j@paul.com\",\r\n\u00a0\u00a0\u00a0\u00a0\"age\":\u00a023,\r\n\u00a0\u00a0\u00a0\u00a0\"phones\":\u00a0[\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"type\":\u00a00,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"areaCode\":\u00a0321,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\"number\":\u00a01544\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\r\n\u00a0\u00a0\u00a0\u00a0],\r\n\u00a0\u00a0\u00a0\u00a0\"id\":\u00a0\"8ef5a99f-72c7-4876-9970-5cc83a0c3f04\",\r\n\u00a0\u00a0\u00a0\u00a0\"expectedVersion\":\u00a00\r\n}<\/pre>\n<p>Note that now there is a different <code>id<\/code> coming up, and the attribute <code>expectedVersion<\/code> from the CQRSLite framework. The creation is faster than before since you don\u2019t have to wait for a database during the creation of a record.<\/p>\n<p>The same <code>id<\/code> must be used to the other endpoints to update and delete a customer. Be careful to provide the same <code>expectedVersion<\/code> when updating a value. You can update the framework not to need this, and always upgrade the latest data in the store when the version is not provided.<\/p>\n<p>Don\u2019t forget to check the database after each operation has finished. The same goes for your RabbitMQ queues. You can also debug each part of the application when running it, to see how each step goes on, in which class and order.<\/p>\n<h2>Conclusion<\/h2>\n<p>This is it for the series. I hope it\u2019s been fruitful and helpful, at least for you to have a better understating of these patterns, since they\u2019re quite confusing, even for experienced developers.<\/p>\n<p>I highly recommend reading over Martin Fowler\u2019s articles about <a href=\"https:\/\/martinfowler.com\/bliki\/CQRS.html\">CQRS<\/a> and <a href=\"https:\/\/martinfowler.com\/tags\/event%20architectures.html\">Event-driven<\/a> architectures, a reference to the community for years. Also, take a look at Greg Young\u2019s first <a href=\"https:\/\/github.com\/gregoryyoung\/m-r\">CQRS sample repo at GitHub<\/a>.<\/p>\n<p>If you still feel adventurous, what about implementing the example over the <a href=\"https:\/\/eventstore.com\/\">Event Store<\/a> stream database? It\u2019s free and open source. And its usage is very simple and clean. Good luck!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Diogo Souza completes his series on CQRS. He demonstrates Event Sourcing to capture the data as it flows through the system which can then be analyzed without affecting the source. &hellip;<\/p>\n","protected":false},"author":320401,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538,53],"tags":[5134],"coauthors":[60461],"class_list":["post-86814","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","category-featured","tag-sql-prompt"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/86814","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/320401"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=86814"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/86814\/revisions"}],"predecessor-version":[{"id":86820,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/86814\/revisions\/86820"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=86814"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=86814"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=86814"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=86814"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}