{"id":34837064,"url":"https://github.com/modelingevolution/plumberd","last_synced_at":"2025-12-25T16:08:39.609Z","repository":{"id":44461760,"uuid":"308576811","full_name":"modelingevolution/plumberd","owner":"modelingevolution","description":"A lightweight project that prepares grounds for abstractions represented in EventStorming and EventModeling","archived":false,"fork":false,"pushed_at":"2024-03-20T15:03:54.000Z","size":12871,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-11-23T14:12:08.333Z","etag":null,"topics":["controller","cqrs","ddd","eventmodeling","eventsouring","eventstore","eventstorming","stream"],"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/modelingevolution.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}},"created_at":"2020-10-30T09:03:29.000Z","updated_at":"2022-05-18T23:43:04.000Z","dependencies_parsed_at":"2023-02-17T10:30:30.116Z","dependency_job_id":"6cecf75d-a521-408a-8f1c-c497791a3a7a","html_url":"https://github.com/modelingevolution/plumberd","commit_stats":{"total_commits":179,"total_committers":7,"mean_commits":"25.571428571428573","dds":0.3910614525139665,"last_synced_commit":"ae504d0a38d22378a49ff99190490ac83b22d330"},"previous_names":[],"tags_count":48,"template":false,"template_full_name":null,"purl":"pkg:github/modelingevolution/plumberd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelingevolution%2Fplumberd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelingevolution%2Fplumberd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelingevolution%2Fplumberd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelingevolution%2Fplumberd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/modelingevolution","download_url":"https://codeload.github.com/modelingevolution/plumberd/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/modelingevolution%2Fplumberd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28032374,"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","status":"online","status_checked_at":"2025-12-25T02:00:05.988Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["controller","cqrs","ddd","eventmodeling","eventsouring","eventstore","eventstorming","stream"],"created_at":"2025-12-25T16:06:59.261Z","updated_at":"2025-12-25T16:08:39.600Z","avatar_url":"https://github.com/modelingevolution.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](logo.png)\n\n\n## INTRODUCTION\n\nUser Voice: [Vote for features here](https://feedback-plumberd.modelingevolution.com/)\n\nPlumberd is a lightweight library to make working with Event driven apps easy. It unifies abstractions and thus combines Event Drive Architecture (messageing) and Event Sourcing (persistance) together.\nIt's makes it super easy for everyone familiar with ASP Controllers or WCF to jump into CQRS and EventSourcing. All the plumbing is automated. \nCurrent version is aimed to support your **Handlers** (CommandHandlers, EventHandlers) to work on server-side or client-side without changing the logic. \nThis becomes important especially because **Blazor** and WebAssembly is getting more traction. \nSupported plumbing:\n\n* EventStore [Done]\n* gRPC Web [Done]\n\n## GETTING STARTED\n\n**Prepare your environment** - run EventStore. You can run it in docker (or however you like):\n\n```bash\ndocker run --name esdb-node -it -p 2113:2113 -p 1113:1113 eventstore/eventstore:latest --insecure --run-projections=All\n```\n\nYou can find more on EventStore from documentation [there](https://developers.eventstore.com/server/20.6/server/installation/docker.html).\n\n**Install nugets**\n```bash\ndotnet add package Plumberd\ndotnet add package Plumberd.EventStore\n```\n\n**Write your command-handlers, projections, processors and other handlers**:\n\n\n```C#\npublic class MyCreazyCommandHandler \n{\n\t// Command Handler Method Signature\n\tpublic async Task When(Guid id, MyCreazyCommand cmd) \n\t{\n\t\t// do some stuff\n\t}\n}\n\n[StreamName(\"Creazy\")]  \t\t\t\t\t// Category - optional.\npublic class MyCreazyCommand : Command { }  // you can implement ICommand instead.\n```\nand add bit of **configuration**:\n```C#\nvar p = new PlumberBuilder()\n\t.WithDefaultEventStore(x =\u003e x.InSecure())\n\t.Build()\n\t.RegisterController(new MyCreazyCommandHandler());  // or RegisterController\u003cMyCreazyCommandHandler\u003e();\nawait p.StartAsync();\n\n```\nNow it would make sense to send a command:\n\n```C#\nawait p.DefaultCommandInvoker.Execute(Guid.NewGuid(), new MyCreazyCommand());\n```\n\n\n**DONE** You ready to run the app. F5!\nThis will save 'MyCreazyCommand' to a stream named Creazy-{id}. Persistent projection will be created under-the-hood and command-handler will be invoked. You can modify *When* method to return events. Then those will be saved to the stream. This can remove dependensies on command-handlers.\n\n### Handlers or Controllers\n\nCommandHandlers, EventHandlers are just processing units. They contain methods that correspond to certain signatures. \nYou may have pure command-handers and event-handlers. Or you can flex your design and have controllers with same methods. It's all up to you. \nPlumberd supports out of the box quite a few signatures. \nLet's see a more complex example:\n\n\n```C#\npublic class HappyController\n{\n\tpublic async IEnumerable\u003cIEvent\u003e When(Guid id, MyCreazyCommand cmd) \n\t{\n\t\tyield return MyCreazyEvent();\n\t}\n}\n\n[MyProjectionConfiguration]\npublic class MyProjection\n{\n\tpublic async Task Given(IMetadata m, MyCreazyEvent e) { }\n\tpublic async MyDerivedEvent Given(IMetadata m, MyCreazyEvent e) { }\n\t/* ... */\n}\n\n[MyProcessorConfiguration]\npublic class MyProcessor\n{\n\tpublic async IAsyncEnumerable\u003cICommand\u003e When(IMetadata m, MyCreazyEvent2 e) { }\n\tpublic async Task\u003c(Guid, MyCreazyCommand3)\u003e When(IMetadata m, MyCreazyEvent2 e) { }\n\t/* ... */\n}\n```\n\nConfiguration can be done in attributes or in Fluent API:\n\n```C#\npublic class MyProcessorAttribute : ProcessingUnitConfigAttribute\n{\n    public MyProcessorAttribute()\n    {\n        SubscribesFromBeginning = false;\t// Subscribe to stream from beginning or from now.\n        IsEventEmitEnabled = true;    // Methods can return events or (Guid,TEvent)\n        IsCommandEmitEnabled = true;  // Methods can return commands or (Guid, TCommand)\n        IsPersistent = true;\t\t  // Cursor is saved in EventStore are persistant subscription\n        BindingFlags = BindingFlags.ProcessEvents |\t\t// A flag that is used to narrow the scope of binding in 'Controllers'\n                                  BindingFlags.ReturnEvents |\n                                  BindingFlags.ReturnCommands;\n        }\n    }\n}\n```\n\n## Dependencies\n\nUsing the library requires using 3 interfaces:\n* ICommand (all your commands need to implement it)\n* IEvent (all your events need to implement it)\n* IMatadata (most likely you won't derive from it.)\n\nThe interface is very simple requireing you only to have **Id** property of type **Guid**.\n\n## Projections\n\nProcessing events in good-enough order is very important. That's why out of the box Plumberd is:\n\n* Creating projection for every Controller (except one method ones). \n* The projection emits a new stream that links to orignal events.\n* This way you can track what events where processed by projection. \n* CorrelationId and CausationId are appended automatically. \n\n## Metadata\n\nMetadata in EventStore is like a headers in WebAPI. Plumberd makes it easy to write your own Enricher, that can append new properties to metadata. \n\n\n# ROADMAP\n\nCurrent version is **Alfa**. \n\n**pre-Alfa**\nIn this version we are experimenting with new features as fast as possible. It's well known that eventsourcing libraries are to complex and do not fit every case. \nWe want to check if we can came up with fix number of features that satisfy many usecases. To make it happen, we need to iterate fast and experiment with \nthe set of features. \nFrom QA perspective this means that few unit-tests are there, however you will find integration-tests and api-tests (if we can achive something though expressing it using lib.).\n\n**Alfa**\nIn this version we might introduce new breaking features. The library might be unstable and is not suitable to production environment - unless you know what you do ;)\nAlfa testing will spread testing upon interested people in closed team.\n\n**Beta** \nIn this version we'll be encouraging everybody to test the library and give feedback. Some breaking changes might be still introduces.\n\n**Release candidate** will be published as soon as we are satisfied with the scope of features.\nIn this stage will focus on performance. \n\n**Release 1.0** yet we don't know when :('.\n\n## NEXT VERSION RELEASE:\n\nAbstractions over processing units seems to workout. Next version will:\n- Provide migration tools for rolling up next eventstore\n- Make LiveQueryExecutor simpler\n- Provide FluentConfiguration as an alternative to attributes.\n- Get rid of ICommand, IEvent and IMetadata interfaces. We will build binders and thus all your hander logic will be fully independent on Plumberd\n- Rewrite all the spagetti code that is there in PlumberdRuntime :)\n- Support for snapshots\n- Better support for session\n\n*If anyone would like to take part in design or help with the library please DM on Twitter/Li or through e-mail: rafal.maciag [at] modelingevolution.com*\n\n## WEB GRPC \u0026 Error handling\n- Each client creates a session-id - which identified the client's client. One running client == one session id. \n- When invoking a command, command handler might throw exception. This exceptions need to be propagated to the app and dispatched. \n- CommandInvoker can \"chain\" exception-handler, that will be invoked when exception is propagated back to the client. It will use 'correlation-id' to dispatch right handler. \n\n-- Under the hood:\n- When invoking a command 'session-id' will be appended to metadata. \n- When invoking a command handler, when exception is rised and Exception event will be emited in 'command-stream', that starts typically with '\u003e'. \n- A projection with copy 'error-exceptions' responses to 'session-stream'.\n- Each grpc client can subscribe to this stream and thus receive exceptions.\n\n### Usage\n1. Register approparite error to be copied to session-stream using SessionProjection\n2. Index Exception in TypeRegister: **register.Index(typeof(EventException\u003cCommandType, ErrorType\u003e));**\n3. In command handler raise **new ProcessingException\u003cErrorType\u003e**\n4. In your .razor file invoke the command using ICommandManager.Execute - with appropriate overload. ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodelingevolution%2Fplumberd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmodelingevolution%2Fplumberd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmodelingevolution%2Fplumberd/lists"}