{"id":20168726,"url":"https://github.com/kostiantyn-matsebora/streamstore","last_synced_at":"2025-04-10T02:03:43.768Z","repository":{"id":257805544,"uuid":"863145677","full_name":"kostiantyn-matsebora/streamstore","owner":"kostiantyn-matsebora","description":"Asynchronous event sourcing.","archived":false,"fork":false,"pushed_at":"2024-11-29T22:09:54.000Z","size":399,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-31T09:38:23.320Z","etag":null,"topics":["amazon","amazon-s3","async","asynchronous","backblaze-b2","cosmosdb","database-abstraction-layer","event-sourcing","eventsourcing","multitenancy","optimistic-concurrency-control","pessimistic-concurrency","postgres","postgresql","s3","s3-storage","sqlite","streams"],"latest_commit_sha":null,"homepage":"","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/kostiantyn-matsebora.png","metadata":{"files":{"readme":"docs/README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-25T19:43:54.000Z","updated_at":"2024-11-29T22:10:55.000Z","dependencies_parsed_at":null,"dependency_job_id":"95b69fd7-1518-4171-ab80-f3d3d887fb85","html_url":"https://github.com/kostiantyn-matsebora/streamstore","commit_stats":null,"previous_names":["kostiantyn-matsebora/streamstore"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kostiantyn-matsebora%2Fstreamstore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kostiantyn-matsebora%2Fstreamstore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kostiantyn-matsebora%2Fstreamstore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kostiantyn-matsebora%2Fstreamstore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kostiantyn-matsebora","download_url":"https://codeload.github.com/kostiantyn-matsebora/streamstore/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248142961,"owners_count":21054671,"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":["amazon","amazon-s3","async","asynchronous","backblaze-b2","cosmosdb","database-abstraction-layer","event-sourcing","eventsourcing","multitenancy","optimistic-concurrency-control","pessimistic-concurrency","postgres","postgresql","s3","s3-storage","sqlite","streams"],"created_at":"2024-11-14T01:09:35.494Z","updated_at":"2025-04-10T02:03:43.757Z","avatar_url":"https://github.com/kostiantyn-matsebora.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# StreamStore\n\n[![Build](https://github.com/kostiantyn-matsebora/streamstore/actions/workflows/build.yml/badge.svg)](https://github.com/kostiantyn-matsebora/streamstore/actions/workflows/build.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=kostiantyn-matsebora_streamstore\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=kostiantyn-matsebora_streamstore)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=kostiantyn-matsebora_streamstore\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=kostiantyn-matsebora_streamstore)\n[![NuGet version (StreamStore)](https://img.shields.io/nuget/v/StreamStore.svg?style=flat-square)](https://www.nuget.org/packages/StreamStore/)\n\nAsynchronous event sourcing.\n\nLibrary provides a logical layer for storing and querying events as a stream.\n\nHeavily inspired by Greg Young's Event Store and [`Streamstone`](https://github.com/yevhen/Streamstone) solutions.\n\n## Overview\n\nDesigned to be easily extended with custom storage backends.\nDespite the fact that component implements a logical layer for storing and querying events as a stream,\n `it does not provide functionality of DDD aggregate`, such as state mutation, conflict resolution etc., but serves more as `persistence layer`  for it.\n\n## Storage packages\n\n  | Package                | Description                                                                            |        Multitenancy        |  Package   |\n  | ---------------------------- | ------------------------------------------------------------------------------------ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |\n  | [StreamStore.NoSql.Cassandra] | [`Apache Cassandra`] and [`Azure Cosmos DB for Apache Cassandra`] port | :white_check_mark: | [![NuGet version (StreamStore.NoSql.Cassandra)](https://img.shields.io/nuget/v/StreamStore.NoSql.Cassandra.svg?style=flat-square)](https://www.nuget.org/packages/StreamStore.NoSql.Cassandra/)  \n  | [StreamStore.Sql.PostgreSql] | [`PostgreSQL`](https://www.postgresql.org/) port | :white_check_mark: | [![NuGet version (StreamStore.Sql.PostgreSql)](https://img.shields.io/nuget/v/StreamStore.Sql.PostgreSql.svg?style=flat-square)](https://www.nuget.org/packages/StreamStore.Sql.PostgreSql/)\n  | [StreamStore.Sql.Sqlite]     | [`SQLite`](https://www.sqlite.org/index.html) port | :white_check_mark: | [![NuGet version (StreamStore.Sql.Sqlite)](https://img.shields.io/nuget/v/StreamStore.Sql.Sqlite.svg?style=flat-square)](https://www.nuget.org/packages/StreamStore.Sql.Sqlite/)\n  | [StreamStore.InMemory]       | `In-memory` port is provided **for testing and educational purposes only** | :white_check_mark: | [![NuGet version (StreamStore.InMemory)](https://img.shields.io/nuget/v/StreamStore.InMemory.svg?style=flat-square)](https://www.nuget.org/packages/StreamStore.InMemory/) |\n  | [StreamStore.S3.AWS]         | [`Amazon S3`] port                                                         | :x: |[![NuGet version (StreamStore.S3.AWS)](https://img.shields.io/nuget/v/StreamStore.S3.AWS.svg?style=flat-square)](https://www.nuget.org/packages/StreamStore.S3.AWS/)       |\n  | [StreamStore.S3.B2]          | [`Backblaze B2`] port                                                      | :x: |[![NuGet version (StreamStore.S3.B2)](https://img.shields.io/nuget/v/StreamStore.S3.B2.svg?style=flat-square)](https://www.nuget.org/packages/StreamStore.S3.B2/)          |\n\n## Concepts\n\nAbout basic concepts you can read in [CONCEPTS.md](../docs/CONCEPTS.md).\n\n## Features\n\nThe general idea is to highlight the common characteristics and features of event sourcing storage:\n\n- [x] Asynchronous read and write operations.\n- [x] Multitenancy support.\n- [x] Automatic provisioning of storage schema.\n- [x] Event ordering.\n- [x] Serialization/deserialization of events.\n- [x] Optimistic concurrency control.\n- [x] Event duplication detection based on event ID.\n- [x] Storage agnostic test framework.\n- [x] Binary serialization support.\n\n## Storages\n\nAlso add implementations of particular storage, such as:\n\n- [x] [`In-Memory`] - for testing purposes.\n- [x] [`Binary Object`] storages:\n  - [x] [`Backblaze B2`] - Backblaze B2.\n  - [x] [`Amazon S3`] - Amazon S3.\n- [x] [`SQL`](https://github.com/DapperLib/Dapper) based DBMS:\n  - [x] [`SQLite`]\n  - [x] [`PostgreSQL`](https://www.postgresql.org/)\n  - [ ] [`Azure SQL`](https://azure.microsoft.com/en-us/services/sql-database/)\n  - [ ] [`MySQL`](https://www.mysql.com/)\n- [x]  [`NoSQL`] based DBMS:\n  - [x] [`Apache Cassandra`]\n  - [x] [`Azure Cosmos DB for Apache Cassandra`]\n\n## Roadmap\n\n- [ ] Composite stream identifier\n- [ ] Custom event properties (?).\n- [ ] External transaction support (?).\n- [ ] Transactional outbox pattern implementation (?).\n\n## Installation\n\nTo install the package, you can use the following command from the command line:\n\n```dotnetcli\n  # Install StreamStore package\n  \n  dotnet add package StreamStore\n\n  # Install package of particular storage implementation, for instance InMemory\n\n  dotnet add package StreamStore.InMemory\n```\n\nor from NuGet Package Manager Console:\n\n```powershell\n   # Install StreamStore package\n  Install-Package StreamStore\n\n   # Install package of particular storage implementation, for instance SQLite storage backend\n  Install-Package StreamStore.Sql.Sqlite\n```\n\n## Usage\n\n- Register store in DI container\n  \n```csharp\n    services.ConfigureStreamStore(x =\u003e              // Register StreamStore\n      x.EnableSchemaProvisioning()                  // Optional. Enable schema provisioning, default: false.\n      \n      // Register single storage implementation, see details in documentation for particular storage\n      x.WithSingleStorage(c =\u003e                 \n          c.UseSqliteStorage(x =\u003e                  // For instance, SQLite storage backend\n              x.ConfigureStorage(c =\u003e\n                c.WithConnectionString(connectionString)\n              )\n          )\n      )\n      // Or enable multitenancy, see details in documentation for particular storage.\n      x.WithMultitenancy(c =\u003e \n          c.UseInMemoryStorage()                   // For instance, InMemory storage backend\n           .UseTenantProvider\u003cMyTenantProvider\u003e()   // Optional. Register your  ITenantProvider implementation.\n                                                    // Required if you want schema to be provisioned for each tenant.\n      )\n    ); \n```\n\n- Use store in your application\n\n```csharp\n   // Inject IStreamStore in your service or controller for single storage implementation\n    public class MyService\n    {\n        private readonly IStreamStore store;\n  \n        public MyService(IStreamStore store)\n        {\n            this.store = store;\n        }\n    }\n \n  // Or IStreamStoreFactory for multitenancy\n    public class MyService\n    {\n        private readonly IStreamStoreFactory storeFactory;\n  \n        public MyService(IStreamStoreFactory storeFactory)\n        {\n            this.storeFactory = storeFactory;\n        }\n    }\n\n  // Append events to stream or create a new stream if it does not exist\n  // EventObject property is where you store your event\n  var events = new Event[]  {\n        new Event { Id = \"event-1\", Timestamp = DateTime.Now, EventObject = eventObject } \n        ...\n      };\n\n  try {\n    store\n      .BeginWriteAsync(\"stream-1\")       // Open stream like new since revision is not provided\n         .AppendEventAsync(x =\u003e          // Append events one by one using fluent API\n            x.WithId(\"event-3\")\n             .Dated(DateTime.Now)\n             .WithEvent(eventObject)\n         )\n        ...\n        .AppendRangeAsync(events)  // Or append range of events by passing IEnumerable\u003cEvent\u003e\n      .SaveChangesAsync(token);\n\n  } catch (StreamConcurrencyException ex) {\n\n    // Read from stream and implement your logic for handling optimistic concurrency exception\n    await foreach(var @event in await store.BeginReadAsync(\"stream-1\", token)) {\n        ...\n    }\n    \n    // Push result to the end of stream\n    store\n        .BeginWriteAsync(\"stream-1\", ex.ActualRevision)\n           .AppendEventAsync(x =\u003e                // Append events one by one using fluent API\n            x.WithId( \"event-4\")\n             .Dated(DateTime.Now)\n             .WithEvent(yourEventObject)\n           )\n        ...\n        .SaveChangesAsync(streamId);\n  } catch (StreamLockedException ex) {\n    // Some storage backends like S3 do not support optimistic concurrency control\n    // So, the only way to handle concurrency is to lock the stream\n  }\n```\n\nMore examples of reading and writing events you can find in test scenarios of [StreamStore.Testing](../src/StreamStore.Testing/StreamStore/Scenarios/) project.\n\n## Example\n\nEach type of storage has its own example project, for instance, you can find an example of usage in the [StreamStore.Sql.Example](../src/StreamStore.Sql.Example) project.\n\nExample projects provides a simple console application that demonstrates how to **configure and use** [`StreamStore`] in your application as single storage or multitenancy.\n\n`Single storage` examples demonstrates:\n\n- optimistic concurrency control\n- asynchronous reading and writing operations\n  \n`Multitenancy` examples, in turn, demonstrates asynchronous reading and writing operations in **isolated tenant storage**.\n\nFor getting all running options simply run the application with `--help` argument.\n\nFor configuring application via configuration file, create `appsettings.Development.json` file.\n\n## Create your own storage implementation\n\nHow to create your own storage implementation you can find in [CUSTOMIZATION.md](CUSTOMIZATION.md).\n\n## Contributing\n\nIf you experience any issues, have a question or a suggestion, or if you wish\nto contribute, feel free to [open an issue][issues] or\n[start a discussion][discussions].\n\n## License\n\n[`MIT License`](../LICENSE)\n\n[`StreamStore`]: https://github.com/kostiantyn-matsebora/streamstore/\n[issues]: https://github.com/kostiantyn-matsebora/streamstore/issues\n[discussions]: https://github.com/kostiantyn-matsebora/streamstore/discussions\n[StreamStore.S3.B2]: https://github.com/kostiantyn-matsebora/streamstore/tree/master/src/StreamStore.S3.B2\n[StreamStore.S3.AWS]: https://github.com/kostiantyn-matsebora/streamstore/tree/master/src/StreamStore.S3.AWS\n[StreamStore.InMemory]: https://github.com/kostiantyn-matsebora/streamstore/tree/master/src/StreamStore.InMemory\n[StreamStore.Sql.Sqlite]: https://github.com/kostiantyn-matsebora/streamstore/tree/master/src/StreamStore.Sql.Sqlite\n[StreamStore.Sql.PostgreSql]: https://github.com/kostiantyn-matsebora/streamstore/tree/master/src/StreamStore.Sql.PostgreSql\n[StreamStore.NoSql.Cassandra]: https://github.com/kostiantyn-matsebora/streamstore/tree/master/src/StreamStore.NoSql.Cassandra\n[`In-Memory`]: https://github.com/kostiantyn-matsebora/streamstore/tree/master/src/StreamStore.InMemory\n[`Backblaze B2`]: https://www.backblaze.com/b2/cloud-storage.html\n[`Amazon S3`]: https://aws.amazon.com/s3/\n[`SQLite`]: https://www.sqlite.org/index.html\n[`NoSQL`]: https://en.wikipedia.org/wiki/NoSQL\n[`Binary Object`]: https://en.wikipedia.org/wiki/Object_storage\n[`Apache Cassandra`]: https://cassandra.apache.org/_/index.html\n[`Azure Cosmos DB for Apache Cassandra`]: https://learn.microsoft.com/en-us/azure/cosmos-db/cassandra/introduction\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkostiantyn-matsebora%2Fstreamstore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkostiantyn-matsebora%2Fstreamstore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkostiantyn-matsebora%2Fstreamstore/lists"}