{"id":40809930,"url":"https://github.com/ngnhng/durablefuture","last_synced_at":"2026-01-21T21:14:47.393Z","repository":{"id":313663072,"uuid":"1052200392","full_name":"ngnhng/durablefuture","owner":"ngnhng","description":"Git for application logic","archived":false,"fork":false,"pushed_at":"2025-12-02T01:45:26.000Z","size":849,"stargazers_count":3,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-04T17:45:06.430Z","etag":null,"topics":["durable-execution","workflow-engine"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ngnhng.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-07T16:01:04.000Z","updated_at":"2025-12-02T01:48:08.000Z","dependencies_parsed_at":"2025-09-07T18:14:12.724Z","dependency_job_id":"923e3211-f453-4888-b627-350d74c70767","html_url":"https://github.com/ngnhng/durablefuture","commit_stats":null,"previous_names":["ngnhng/durablefuture"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ngnhng/durablefuture","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngnhng%2Fdurablefuture","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngnhng%2Fdurablefuture/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngnhng%2Fdurablefuture/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngnhng%2Fdurablefuture/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ngnhng","download_url":"https://codeload.github.com/ngnhng/durablefuture/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ngnhng%2Fdurablefuture/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28643213,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-21T18:04:35.752Z","status":"ssl_error","status_checked_at":"2026-01-21T18:03:55.054Z","response_time":86,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["durable-execution","workflow-engine"],"created_at":"2026-01-21T21:14:46.321Z","updated_at":"2026-01-21T21:14:47.386Z","avatar_url":"https://github.com/ngnhng.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DurableFuture\n\nA lightweight workflow engine built on **NATS JetStream** for durable, distributed task orchestration.\n\nYou can focus on your business logic while the engine handles persistence, retries, and distributed execution.\n\n\u003e [!WARNING]\n\u003e Software is at pre-alpha stage and is just a concept demo, expect bugs.\n\n### Architecture\n\n- **Server** - Orchestrates workflow execution and state management\n- **Workflow Worker** - Executes workflow logic and decision-making\n- **Activity Worker** - Performs individual tasks (API calls, database operations, etc.)\n- **Client** - Triggers workflow execution and retrieves results\n\n## Why DurableFuture?\n\nTraditional workflow engines often require complex deployments with proprietary databases and clustering solutions. DurableFuture simplifies this by:\n\n- **Delegating persistence to NATS JetStream** - No custom database layer\n- **Minimal operational overhead** - Just NATS + your application\n- **Production-ready foundation** - NATS is proven in high-scale environments\n\n## Running the Examples\n\nDurableFuture ships with multiple runnable scenarios (see `examples/scenarios`). A single CLI (`examples/cmd`) loads the scenario you choose so Docker Compose does not have to change when switching examples.\n\n### 1. Pick a scenario\n\nList the available scenarios:\n\n```bash\ngo run ./examples/cmd -list-examples\n```\n\nAssume we want to run the recovery scenario:\n\n```bash\nexport EXAMPLE_SCENARIO=order-retries\n```\n\n### 2. Boot the infrastructure + workers\n\n`EXAMPLE_SCENARIO` is read by Docker Compose so the workflow and activity worker containers auto-load the correct workflows/activities.\n\n```bash\ndocker compose up --force-recreate --build -d\n```\n\n### 3. Visit the UI at [http://localhost:8080](http://localhost:8080)\n\n## Usage\n\n- Workflow: See full version at [/examples/scenarios/order/order.go](examples/scenarios/order/order.go)\n- Usage: See full version at [/examples/cmd/main.go](examples/cmd/main.go)\n\n### 1. Write a workflow\n\n```go\n// OrderWorkflow implements a simple order processing workflow\nfunc OrderWorkflow(ctx workflow.Context, customerId string, productId string, amount float64, quantity int) (any, error) {\n\n\tvar chargeResult ChargeResult\n\tif err := workflow.\n\t\tExecuteActivity(ctx, ChargeCreditCardActivity, customerId, amount).\n\t\tGet(ctx, \u0026chargeResult); err != nil {\n\t\treturn nil, fmt.Errorf(\"credit card charge failed: %w\", err)\n\t}\n\n\tvar shipResult ShipResult\n\tif err := workflow.\n\t\tExecuteActivity(ctx, ShipPackageActivity, chargeResult).\n\t\tGet(ctx, \u0026shipResult); err != nil {\n\t\t// In a real scenario, you would refund the charge here\n\t\treturn nil, fmt.Errorf(\"package shipping failed: %w\", err)\n\t}\n\n\tresult := map[string]any{\n\t\t\"tracking_id\":        shipResult.TrackingID,\n\t\t\"carrier\":            shipResult.Carrier,\n\t\t\"estimated_delivery\": shipResult.EstimatedDelivery,\n\t\t\"charge_id\":          chargeResult.ChargeID,\n\t}\n\n\treturn result, nil\n}\n```\n\n### 2. Create Workers and register said Workflow\n\n- First create Workflow Worker:\n\n```go\n\tctx := context.Background()\n\t// create a Workflow Worker\n\tworkerClient, err := worker.NewWorker()\n\tif err != nil {\n\t\tlog.Printf(\"err: %v\", err)\n\t\treturn\n\t}\n\t// register the Workflow to the Worker\n\terr = workerClient.RegisterWorkflow(examples.OrderWorkflow)\n\tif err != nil {\n\t\tlog.Printf(\"err: %v\", err)\n\t\treturn\n\t}\n\t// start the Workflow Worker\n\tif err := workerClient.Run(ctx); err != nil {\n\t\tlog.Printf(\"err: %v\", err)\n\t\treturn\n\t}\n```\n\n- Then create Activity Worker(s):\n\n```go\n\tctx := context.Background()\n\tworkerClient, err := worker.NewWorker()\n\tif err != nil {\n\t\treturn\n\t}\n\terr = workerClient.RegisterActivity(examples.AddActivity)\n\tif err != nil {\n\t\treturn\n\t}\n\terr = workerClient.RegisterActivity(examples.DelayedActivity)\n\tif err != nil {\n\t\treturn\n\t}\n\terr = workerClient.RegisterActivity(examples.ChargeCreditCardActivity)\n\tif err != nil {\n\t\treturn\n\t}\n\terr = workerClient.RegisterActivity(examples.ShipPackageActivity)\n\tif err != nil {\n\t\treturn\n\t}\n\tif err := workerClient.Run(ctx); err != nil {\n\t\treturn\n\t}\n```\n\n- Finally, the client code:\n\n```go\nfuture, err := workflowClient.ExecuteWorkflow(ctx, examples.OrderWorkflow,\n\t\"Bob\",\n\t\"widget-1000\",\n\t1000.0,\n\t2,\n)\nif err != nil {\n\tlog.Fatalf(\"Starting workflow failed: %v\", err)\n}\nvar result any\nerr = future.Get(ctx, \u0026result)\nif err != nil {\n\tlog.Fatalf(\"error: %v\", err)\n}\nlog.Printf(\"result: %v\", result)\n```\n\n## How it works\n\nDurableFuture works by leveraging the event sourcing pattern, recording the outcome of Activities within the Workflow. For example, results of operations such as making an API call or a database transaction will be persisted as Events in a NATS Jetstream. So in the event the current Workflow crashed or interrupted, it will be re-run on one of the available Workflow Workers. However, instead of executing the Activities that have already been done, it will return the result from the first successful execution that is being stored on the Event Stream.\n\nConsider the previous example:\n\nIf the workflow runs normally without being interrupted, then the event log at the end might look something like this:\n\n| seq | event type         | result                                       |\n| --- | ------------------ | -------------------------------------------- |\n| 0   | workflow started   | Order                                        |\n| 1   | activity scheduled | ChargeCreditCard                             |\n| 2   | activity started   | ChargeCreditCard                             |\n| 3   | activity completed | {charge_id: \"ch_123\"}                        |\n| 4   | activity scheduled | Shipping                                     |\n| 5   | activity started   | Shipping                                     |\n| 6   | activity completed | {tracking_id: \"tr_456\"}                      |\n| 7   | workflow completed | {charge_id: \"ch_123\", tracking_id: \"tr_456\"} |\n\nSuppose, now, that instead of running until the end, some failure occurs after the `ChargeCreditCard` activity has completed, but before the `Shipping` activity has completed. The event log might look like this:\n\n| seq | event type         | result                                       |\n| --- | ------------------ | -------------------------------------------- |\n| 0   | workflow started   | Order                                        |\n| 1   | activity scheduled | ChargeCreditCard                             |\n| 2   | activity started   | ChargeCreditCard                             |\n| 3   | activity completed | {charge_id: \"ch_123\"}                        |\n| 4   | activity scheduled | Shipping                                     |\n| 5   | activity started   | Shipping                                     |\n| 6   | activity failed    | (crashed before completion)                  |\n| 7   | activity scheduled | Shipping (retries)                           |\n| 8   | activity started   | Shipping (retries)                           |\n| 9   | activity completed | {tracking_id: \"tr_456\"}                      |\n| 10  | workflow completed | {charge_id: \"ch_123\", tracking_id: \"tr_456\"} |\n\nSo when a Worker picks up the Workflow, it is restarted and it will replay the events in the log, only executing the Activities that have not yet been completed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngnhng%2Fdurablefuture","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fngnhng%2Fdurablefuture","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fngnhng%2Fdurablefuture/lists"}