{"id":18880412,"url":"https://github.com/moimhossain/event-sourcing","last_synced_at":"2025-08-14T11:50:08.314Z","repository":{"id":75024795,"uuid":"111512505","full_name":"MoimHossain/event-sourcing","owner":"MoimHossain","description":"An implementation of Event Sourcing and CQRS based on Azure table storage (EventStore) and Document DB (Materialized Views)","archived":false,"fork":false,"pushed_at":"2023-02-07T14:55:43.000Z","size":57,"stargazers_count":11,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-31T10:02:11.214Z","etag":null,"topics":["azure-storage","cosmosdb","documentdb","event-sourcing","event-stream","eventstore","stream-processing","table-storage"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MoimHossain.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-21T07:09:27.000Z","updated_at":"2024-11-18T09:14:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"6bc4ea50-3e0f-4519-851b-4354d7cc4792","html_url":"https://github.com/MoimHossain/event-sourcing","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MoimHossain%2Fevent-sourcing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MoimHossain%2Fevent-sourcing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MoimHossain%2Fevent-sourcing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MoimHossain%2Fevent-sourcing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MoimHossain","download_url":"https://codeload.github.com/MoimHossain/event-sourcing/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248946064,"owners_count":21187440,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azure-storage","cosmosdb","documentdb","event-sourcing","event-stream","eventstore","stream-processing","table-storage"],"created_at":"2024-11-08T06:43:47.639Z","updated_at":"2025-04-14T19:32:18.794Z","avatar_url":"https://github.com/MoimHossain.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SuperNova Storage\n\nA lightweight **CQRS** supporting library with **Event Store** based on __Azure Table Storage__.\n\n## Quick start guide\n\n### Install\n\nInstall the [**SuperNova.Storage** Nuget](https://www.nuget.org/packages/SuperNova.Storage/) package into the project.\n\n```\nInstall-Package SuperNova.Storage -Version 1.0.0\n```\n\nThe dependencies of the package are:\n\n - .NETCoreApp 2.0\n - Microsoft.Azure.DocumentDB.Core (\u003e= 1.7.1)\n - Microsoft.Extensions.Logging.Debug (\u003e= 2.0.0)\n - SuperNova.Shared (\u003e= 1.0.0)\n - WindowsAzure.Storage (\u003e= 8.5.0)\n\n\n# Implemention guide\n\n## Write Side - Event Sourcing \n\nOnce the package is installed, we can start sourcing events in an application. For example, let's start with a canonical example of ``` UserController ``` in a **Web API** project.\n\nWe can use the dependency injection to make **EventStore** available in our controller. \n\nHere's an example where we register an instance of Event Store with DI framework in our _Startup.cs_\n\n```\n// Config object encapsulates the table storage connection string\nservices.AddSingleton\u003cIEventStore\u003e(new EventStore( ... provide config )); \n```\nNow the controller:\n\n```\n[Produces(\"application/json\")]\n[Route(\"users\")]\npublic class UsersController : Controller\n{   \n    public UsersController(IEventStore eventStore)\n    {\n        this.eventStore = eventStore; // Here capture the event store handle\n    }   \n    \n    ... other methods skipped here\n}\n```\n\n#### Aggregate\nImplementing event sourcing becomes way much handier, when it's fostered with  **Domain Driven Design** (aka DDD). We are going to assume that we are familiar with DDD concepts (especially **Aggregate Roots**).\n\nAn aggregate is our consistency boundary (read as __transactional boundary__) in Event Sourcing. (Technically, Aggregate ID's are our **partition keys** on Event Store table - therefore, we can only apply an **atomic** operation on a single aggregate root level.)\n\nLet's create an Aggregate for our User domain entity:\n\n```\nusing SuperNova.Shared.Messaging.Events.Users;\nusing SuperNova.Shared.Supports;\n\npublic class UserAggregate : AggregateRoot\n{\n    private string _userName;\n    private string _emailAddress;\n    private Guid _userId;\n    private bool _blocked;\n\n```\n\nOnce we have the aggregate class written, we should come up with the events that are relevant to this aggregate. We can use **Event storming** to come up with the relevant events.\n\nHere are the events that we will use for our example scenario:\n\n```\npublic class UserAggregate : AggregateRoot\n{\n\n    ... skipped other codes\n\n    #region Apply events\n    private void Apply(UserRegistered e)\n    {\n        this._userId = e.AggregateId;\n        this._userName = e.UserName;\n        this._emailAddress = e.Email;            \n    }\n\n    private void Apply(UserBlocked e)\n    {\n        this._blocked = true;\n    }\n\n    private void Apply(UserNameChanged e)\n    {\n        this._userName = e.NewName;\n    }\n    #endregion\n\n    ... skipped other codes\n}\n\n```\n\nNow that we have our business events defined, we will define our **commands** for the aggregate:\n\n```\npublic class UserAggregate : AggregateRoot\n{\n    #region Accept commands\n    public void RegisterNew(string userName, string emailAddress)\n    {\n        Ensure.ArgumentNotNullOrWhiteSpace(userName, nameof(userName));\n        Ensure.ArgumentNotNullOrWhiteSpace(emailAddress, nameof(emailAddress));\n\n        ApplyChange(new UserRegistered\n        {\n            AggregateId = Guid.NewGuid(),\n            Email = emailAddress,\n            UserName = userName                \n        });\n    }\n\n    public void BlockUser(Guid userId)\n    {            \n        ApplyChange(new UserBlocked\n        {\n            AggregateId = userId\n        });\n    }\n\n    public void RenameUser(Guid userId, string name)\n    {\n        Ensure.ArgumentNotNullOrWhiteSpace(name, nameof(name));\n\n        ApplyChange(new UserNameChanged\n        {\n            AggregateId = userId,\n            NewName = name\n        });\n    }\n    #endregion\n\n\n    ... skipped other codes\n}\n```\n\n\nSo far so good!\n\nNow we will modify the web api controller to send the correct command to the aggregate.\n\n```\npublic class UserPayload \n{  \n    public string UserName { get; set; } \n    public string Email { get; set; } \n}\n\n// POST: User\n[HttpPost]\npublic async Task\u003cJsonResult\u003e Post(Guid projectId, [FromBody]UserPayload user)\n{\n    Ensure.ArgumentNotNull(user, nameof(user));\n\n    var userId = Guid.NewGuid();    \n\n    await eventStore.ExecuteNewAsync(\n        Tenant, \"user_event_stream\", userId, async () =\u003e {\n\n        var aggregate = new UserAggregate();\n\n        aggregate.RegisterNew(user.UserName, user.Email);\n\n        return await Task.FromResult(aggregate);\n    });\n\n    return new JsonResult(new { id = userId });\n}\n\n```\n\nAnd another API to modify existing users into the system:\n```\n//PUT: User\n[HttpPut(\"{userId}\")]\npublic async Task\u003cJsonResult\u003e Put(Guid projectId, Guid userId, [FromBody]string name)\n{\n    Ensure.ArgumentNotNullOrWhiteSpace(name, nameof(name));\n\n    await eventStore.ExecuteEditAsync\u003cUserAggregate\u003e(\n        Tenant, \"user_event_stream\", userId,\n        async (aggregate) =\u003e\n        {\n            aggregate.RenameUser(userId, name);\n\n            await Task.CompletedTask;\n        }).ConfigureAwait(false);\n\n    return new JsonResult(new { id = userId });\n}\n```\n\nThat's it! We have our **WRITE** side completed. The event store is now contains the events for user event stream. \n\n![EventStore](https://i.imgur.com/cU3HT4l.png \"EventStore with events\")\n\n\n## Read Side - Materialized Views\n\nWe can consume the events in a seperate console worker process and generate the _materialized_ views for **READ** side.\n\nThe readers (the console application - **Azure Web Worker** for instance) are like feed processor and have their own **lease** collection that makes them fault tolerant and resilient. If crashes, it catches up form the last event version that was materialized successfully. It's doing a polling - instead of a message broker (**Service Bus** for instance) on purpose, to speed up and avoid latencies during event propagation. Scalabilities are ensured by means of dedicating lease per tenants and event streams - which provides pretty high scalability.\n\n### How to listen for events?\n\nIn a **worker application** (typically a console application) we will listen for events:\n\n```\nprivate static async Task Run()\n{\n    var eventConsumer = new EventStreamConsumer(        \n        ... skipped for simplicity\n        \"user-event-stream\", \n        \"user-event-stream-lease\");\n    \n    await eventConsumer.RunAndBlock((evts) =\u003e\n    {\n        foreach (var @evt in evts)\n        {\n            if (evt is UserRegistered userAddedEvent)\n            {\n                readModel.AddUserAsync(new UserDto\n                {\n                    UserId = userAddedEvent.AggregateId,\n                    Name = userAddedEvent.UserName,\n                    Email = userAddedEvent.Email\n                }, evt.Version);\n            }\n\n            else if (evt is UserNameChanged userChangedEvent)\n            {\n                readModel.UpdateUserAsync(new UserDto\n                {\n                    UserId = userChangedEvent.AggregateId,\n                    Name = userChangedEvent.NewName\n                }, evt.Version);\n            }\n        }\n\n    }, CancellationToken.None);\n}\n\nstatic void Main(string[] args)\n{\n    Run().Wait();\n}\n```\n\n\nNow we have a document collection (we are using Cosmos Document DB in this example for materialization but it could be any database essentially) that is being updated as we store events in event stream.\n\n## Conclusion\n\nThe library is very light weight and havily influenced by Greg's event store model and aggreagate model. Feel free to use/contribute.\n\nThank you!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoimhossain%2Fevent-sourcing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoimhossain%2Fevent-sourcing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoimhossain%2Fevent-sourcing/lists"}