{"id":16686854,"url":"https://github.com/jacqueskang/eventsourcing","last_synced_at":"2025-04-04T20:14:58.820Z","repository":{"id":33261498,"uuid":"157179271","full_name":"jacqueskang/EventSourcing","owner":"jacqueskang","description":".NET Core event sourcing framework","archived":false,"fork":false,"pushed_at":"2022-12-08T10:01:43.000Z","size":1296,"stargazers_count":177,"open_issues_count":14,"forks_count":28,"subscribers_count":9,"default_branch":"develop","last_synced_at":"2024-05-01T12:18:14.084Z","etag":null,"topics":["cosmosdb","cqrs","dynamodb","event-sourcing"],"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/jacqueskang.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"jacqueskang","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2018-11-12T08:20:07.000Z","updated_at":"2024-05-01T12:18:14.085Z","dependencies_parsed_at":"2023-01-15T00:09:49.991Z","dependency_job_id":null,"html_url":"https://github.com/jacqueskang/EventSourcing","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacqueskang%2FEventSourcing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacqueskang%2FEventSourcing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacqueskang%2FEventSourcing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jacqueskang%2FEventSourcing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jacqueskang","download_url":"https://codeload.github.com/jacqueskang/EventSourcing/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247242681,"owners_count":20907134,"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":["cosmosdb","cqrs","dynamodb","event-sourcing"],"created_at":"2024-10-12T15:07:02.463Z","updated_at":"2025-04-04T20:14:58.803Z","avatar_url":"https://github.com/jacqueskang.png","language":"C#","funding_links":["https://github.com/sponsors/jacqueskang"],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://dev.azure.com/jacques-kang/EventSourcing/_apis/build/status/jacqueskang-eventsourcing-ci?branchName=develop)](https://dev.azure.com/jacques-kang/EventSourcing/_build/latest?definitionId=11\u0026branchName=develop)\n# EventSourcing\n\nA .NET Core event sourcing framework.\n\nEasy to be integrated in ASP.NET Core web application, Lambda function or Azure function.\n\nSupport various of event store:\n - in file system as plain text file (see [File system setup instructions](doc/FileSystemSetup.md))\n - in AWS DynamoDB (see [DynamoDB setup instructions](doc/DynamoDBSetup.md))\n - in Azure CosmosDB (see [CosmosDB setup instructions](doc/CosmosDBSetup.md))\n - in any relational database supported by EF Core, e.g., Microsoft SQL Server,MySQL, etc. (see [EF Core setup instructions](doc/EfCoreSetup.md))\n\n## NuGet packages\n - JKang.EventSourcing [![NuGet version](https://badge.fury.io/nu/JKang.EventSourcing.svg)](https://badge.fury.io/nu/JKang.EventSourcing)\n - JKang.EventSourcing.Persistence.FileSystem [![NuGet version](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.FileSystem.svg)](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.FileSystem)\n - JKang.EventSourcing.Persistence.EfCore [![NuGet version](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.EfCore.svg)](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.EfCore)\n - JKang.EventSourcing.Persistence.DynamoDB [![NuGet version](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.DynamoDB.svg)](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.DynamoDB)\n - JKang.EventSourcing.Persistence.CosmosDB [![NuGet version](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.CosmosDB.svg)](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.CosmosDB)\n - JKang.EventSourcing.Persistence.S3 [![NuGet version](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.S3.svg)](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.S3)\n - JKang.EventSourcing.Persistence.Caching [![NuGet version](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.Caching.svg)](https://badge.fury.io/nu/JKang.EventSourcing.Persistence.Caching)\n\n## Quick Start:\n\nLet's implement a simple gift card management system with the following use cases:\n * Create gift cards with initial credit\n * Debit the gift card specifying amount while overpaying is not allowed\n\nI'm adopting *DDD (Domain Driven Design)* approach and implement the *GiftCard* entity as an **Rich Domain Aggregate** which encapsulates/protects its internal data/state, and contains itself business logics ensuring data integrity.\n\n### Step 1 - Create aggregate events\n\n```csharp\npublic sealed class GiftCardCreated : AggregateCreatedEvent\u003cGuid\u003e\n{\n    public GiftCardCreated(Guid aggregateId, DateTime timestamp, decimal initialCredit)\n        : base(aggregateId, timestamp)\n    {\n        InitialCredit = initialCredit;\n    }\n\n    public decimal InitialCredit { get; }\n}\n```\n\n```csharp\npublic class GiftCardDebited : AggregateEvent\u003cGuid\u003e\n{\n    public GiftCardDebited(Guid aggregateId, int aggregateVersion, DateTime timestamp, decimal amount)\n        : base(aggregateId, aggregateVersion, timestamp)\n    {\n        Amount = amount;\n    }\n\n    public decimal Amount { get; }\n}\n```\n\nNotes: \n - It's recommended to implement aggregate event in an immutable way.\n - Inheriting from `AggregateEvent\u003cTKey\u003e` or `AggregateCreatedEvent\u003cTKey\u003e` is not mandatory, but an aggreagte event must at least implement  `IAggregateEvent\u003cTKey\u003e` interface.\n - In order to use built-in event stores, please make sure event can be properly serialized using [Json.NET](https://www.newtonsoft.com/json).\n\n### Step 2 - Create domain aggregate\n\n```csharp\npublic class GiftCard : Aggregate\u003cGuid\u003e\n{\n    /// \u003csummary\u003e\n    /// Constructor for creating an new gift card from scratch\n    /// \u003c/summary\u003e\n    public GiftCard(decimal initialCredit)\n        : base(new GiftCardCreated(Guid.NewGuid(), DateTime.UtcNow, initialCredit))\n    { }\n\n    /// \u003csummary\u003e\n    /// Constructor for rehydrating gift card from historical events\n    /// \u003c/summary\u003e\n    public GiftCard(Guid id, IEnumerable\u003cIAggregateEvent\u003cGuid\u003e\u003e savedEvents)\n        : base(id, savedEvents)\n    { }\n\n    /// \u003csummary\u003e\n    /// Constructor for rehydrating gift card from a snapshot + historical events after the snapshot\n    /// \u003c/summary\u003e\n    public GiftCard(Guid id, IAggregateSnapshot\u003cGuid\u003e snapshot, IEnumerable\u003cIAggregateEvent\u003cGuid\u003e\u003e savedEvents)\n        : base(id, snapshot, savedEvents)\n    { }\n\n    public decimal Balance { get; private set; }\n\n    public void Debit(decimal amout)\n        =\u003e ReceiveEvent(new GiftCardDebited(Id, GetNextVersion(), DateTime.UtcNow, amout));\n\n    protected override void ApplyEvent(IAggregateEvent\u003cGuid\u003e @event)\n    {\n        if (@event is GiftCardCreated created)\n        {\n            Balance = created.InitialCredit;\n        }\n        else if (@event is GiftCardDebited debited)\n        {\n            if (debited.Amount \u003c 0)\n            {\n                throw new InvalidOperationException(\"Negative debit amout is not allowed.\");\n            }\n\n            if (Balance \u003c debited.Amount)\n            {\n                throw new InvalidOperationException(\"Not enough credit\");\n            }\n\n            Balance -= debited.Amount;\n        }\n    }\n}\n```\n\nNotes:\n - Please ensure that state of domain aggregate can only be changed by applying aggregate events. \n - Inheriting from `Aggregate\u003cTKey\u003e` is not mandatory, but the minimum requirements for implementing a domain aggregate are:\n   - Implement `IAggregate\u003cTKey\u003e` interface\n   - Have a public constructor with signature `MyAggregate(TKey id, IEnumerable\u003cIAggregateEvent\u003cTKey\u003e\u003e savedEvents)`\n   - Have a public constructor with signature `MyAggregate(TKey id, IAggregateSnapshot\u003cTKey\u003e snapshot, IEnumerable\u003cIAggregateEvent\u003cTKey\u003e\u003e savedEvents)`\n\n\n### Step 3 - Implement repository\n\nBy definition of Event Sourcing, persisting an aggregate insists on persisting all historical events.\n\n```csharp\npublic interface IGiftCardRepository\n{\n    Task SaveGiftCardAsync(GiftCard giftCard);\n    Task\u003cGiftCard\u003e FindGiftCardAsync(Guid id);\n}\n```\n    \n```csharp\npublic class GiftCardRepository : AggregateRepository\u003cGiftCard, Guid\u003e, \n    IGiftCardRepository\n{\n    public GiftCardRepository(IEventStore\u003cGiftCard, Guid\u003e eventStore)\n        : base(eventStore)\n    { }\n\n    public Task SaveGiftCardAsync(GiftCard giftCard) =\u003e\n        SaveAggregateAsync(giftCard);\n\n    public Task\u003cGiftCard\u003e FindGiftCardAsync(Guid id) =\u003e\n        FindAggregateAsync(id);\n}\n```\n\n### Step 4 - Register your repository interface and configure event store in dependency injection framework\n\n```csharp\nservices\n    .AddScoped\u003cIGiftCardRepository, GiftCardRepository\u003e();\n\nservices\n    .AddEventSourcing(builder =\u003e\n    {\n        builder.UseTextFileEventStore\u003cGiftCard, Guid\u003e(x =\u003e\n            x.Folder = \"C:/Temp/GiftcardEvents\");\n    });\n```\n\nNotes:\n - You can choose other persistence store provided such as [CosmosDB](doc/CosmosDBSetup.md) or [DynamoDB](doc/DynamoDBSetup) etc.\n\n### Step 5 - implmement use cases\n\n```csharp\n// create a new gift card with initial credit 100\nvar giftCard = new GiftCard(100);\n\n// persist the gift card\nawait _repository.SaveGiftCardAsync(giftCard);\n\n// rehydrate the giftcard\ngiftCard = await _repository.FindGiftCardAsync(giftCard.Id);\n\n// payments\ngiftCard.Debit(40); // ==\u003e balance: 60\ngiftCard.Debit(50); // ==\u003e balance: 10\ngiftCard.Debit(20); // ==\u003e invalid operation exception\n```\n\n## FAQs\n\n### How to programmatically initialize event store?\n\nSee [this page](doc/StoreInitialization.md).\n\n### How to use snapshots to optimize performance?\n\nSee [this page](doc/Snapshots.md).\n\n### How to improve performance using caching?\n\nConsider install the nuget package `JKang.EventSourcing.Persistence.Caching` and inherit the `CachedAggregateRepository` class.\nIt leverages `Microsoft.Extensions.Caching.Distributed.IDistributedCache` to cache aggregate every time after loaded from or saved into repository.\n\nConsider configuring a short sliding expiration (e.g., 5 sec) to reduce the chance of having cache out of date.\n\n---\n__Please feel free to download, fork and/or provide any feedback!__\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacqueskang%2Feventsourcing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjacqueskang%2Feventsourcing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacqueskang%2Feventsourcing/lists"}