{"id":45593889,"url":"https://github.com/kerlenton/kata","last_synced_at":"2026-02-27T17:01:58.375Z","repository":{"id":339784528,"uuid":"1163362956","full_name":"Kerlenton/kata","owner":"Kerlenton","description":"Library for orchestrating multi-step operations with automatic compensation on failure","archived":false,"fork":false,"pushed_at":"2026-02-23T11:14:32.000Z","size":27,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-26T20:23:05.652Z","etag":null,"topics":["compensation","distributed-systems","go","golang","orchestration","rollback","saga","saga-pattern","workflow"],"latest_commit_sha":null,"homepage":"","language":"Go","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/Kerlenton.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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":"2026-02-21T14:15:28.000Z","updated_at":"2026-02-25T10:53:59.000Z","dependencies_parsed_at":"2026-02-24T14:00:38.305Z","dependency_job_id":null,"html_url":"https://github.com/Kerlenton/kata","commit_stats":null,"previous_names":["kerlenton/kata"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/Kerlenton/kata","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kerlenton%2Fkata","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kerlenton%2Fkata/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kerlenton%2Fkata/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kerlenton%2Fkata/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Kerlenton","download_url":"https://codeload.github.com/Kerlenton/kata/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Kerlenton%2Fkata/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29905554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-27T14:46:13.553Z","status":"ssl_error","status_checked_at":"2026-02-27T14:46:10.522Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5: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":["compensation","distributed-systems","go","golang","orchestration","rollback","saga","saga-pattern","workflow"],"created_at":"2026-02-23T13:19:42.094Z","updated_at":"2026-02-27T17:01:58.355Z","avatar_url":"https://github.com/Kerlenton.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# kata\n\n\u003e *In martial arts, a kata is a precise sequence of movements - executed with full commitment, or not at all. If you break the form, you return to the beginning.*\n\n**kata** is an embedded Go library for orchestrating multi-step operations with automatic compensation on failure. No external services, no databases, no brokers - just import and use.\n\n```go\nrunner := kata.New(\n    kata.Step(\"charge-card\",   chargeCard).Compensate(refundCard).Retry(3, kata.Exponential(100*time.Millisecond)),\n    kata.Step(\"reserve-stock\", reserveStock).Compensate(releaseStock),\n    kata.Step(\"create-shipment\", createShipment),\n)\n\nif err := runner.Run(ctx, \u0026OrderState{CardToken: \"tok_123\", Amount: 9900}); err != nil {\n    // all compensations already ran automatically\n}\n```\n\nIf `create-shipment` fails, kata automatically calls `releaseStock` then `refundCard` - in reverse order, with the full state available.\n\n---\n\n## Why kata?\n\nEvery non-trivial service has operations that span multiple steps: charge a card, reserve inventory, create a shipment. When step 3 fails, you need to undo steps 1 and 2. Most teams write this rollback logic by hand - scattered `defer` calls, nested `if err != nil` blocks, easy to get wrong.\n\nThe alternatives are either too heavy (Temporal, Cadence require a dedicated server cluster) or too primitive (existing Go saga libraries have no generics, no retry, no parallel execution).\n\nkata sits in the middle: **zero dependencies**, idiomatic Go, production-ready features.\n\n---\n\n## Installation\n\n```bash\ngo get github.com/kerlenton/kata\n```\n\nRequires Go 1.22+.\n\n---\n\n## Core concepts\n\n### Steps\n\nA `Step` is a named operation that reads from and writes to your shared state. Each step can optionally define a compensation (rollback) function.\n\n```go\nkata.Step(\"charge-card\", func(ctx context.Context, s *OrderState) error {\n    id, err := stripe.Charge(s.CardToken, s.Amount)\n    if err != nil {\n        return err\n    }\n    s.ChargeID = id // store result for later steps (and compensation)\n    return nil\n}).Compensate(func(ctx context.Context, s *OrderState) error {\n    return stripe.Refund(s.ChargeID)\n})\n```\n\n### Retry\n\nSteps can be retried with configurable backoff:\n\n```go\nkata.Step(\"call-flaky-api\", callAPI).\n    Retry(3, kata.Exponential(100*time.Millisecond))\n    // attempts: immediate -\u003e 100ms -\u003e 200ms -\u003e 400ms\n\nkata.Step(\"call-another\", callOther).\n    Retry(5, kata.Fixed(1*time.Second))\n\nkata.Step(\"call-fast\", callFast).\n    Retry(2, kata.NoDelay)\n```\n\n### Timeout\n\n```go\nkata.Step(\"slow-step\", doWork).\n    Timeout(5 * time.Second)\n```\n\nIf the step exceeds the timeout, the context is cancelled and the step fails with `context.DeadlineExceeded`. Compensations are triggered normally.\n\n### Parallel steps\n\nRun multiple steps concurrently within a group. If any step in the group fails, the others are cancelled and the successful ones are compensated.\n\n```go\nkata.Parallel(\"notify-customer\",\n    kata.Step(\"send-email\", sendEmail),\n    kata.Step(\"send-sms\",   sendSMS).Compensate(cancelSMS),\n    kata.Step(\"send-push\",  sendPush),\n)\n```\n\nIf a later sequential step fails after the parallel group succeeds, all steps in the group are compensated in reverse order.\n\n### Runner\n\n`New` creates a reusable runner - define it once, call `Run` per request:\n\n```go\n// define once (e.g. at startup or in a constructor)\nvar orderRunner = kata.New(\n    kata.Step(\"charge\",  chargeCard).Compensate(refundCard),\n    kata.Step(\"reserve\", reserveStock).Compensate(releaseStock),\n    kata.Parallel(\"notify\",\n        kata.Step(\"email\", sendEmail),\n        kata.Step(\"sms\",   sendSMS),\n    ),\n)\n\n// call per request\nfunc (s *OrderService) PlaceOrder(ctx context.Context, req *PlaceOrderRequest) error {\n    state := \u0026OrderState{CardToken: req.CardToken, ItemID: req.ItemID}\n    return orderRunner.Run(ctx, state)\n}\n```\n\n---\n\n## Error handling\n\nkata distinguishes between two failure modes:\n\n```go\nerr := runner.Run(ctx, state)\n\nvar stepErr *kata.StepError\nvar compErr *kata.CompensationError\n\nswitch {\ncase err == nil:\n    // all steps succeeded\n\ncase errors.As(err, \u0026stepErr):\n    // a step failed, all compensations ran successfully\n    // stepErr.StepName - which step failed\n    // stepErr.Cause   - the original error\n    log.Printf(\"rolled back cleanly after %q: %v\", stepErr.StepName, stepErr.Cause)\n\ncase errors.As(err, \u0026compErr):\n    // a step failed AND one or more compensations also failed\n    // the system may be in a partially inconsistent state\n    // manual intervention may be required\n    log.Printf(\"ALERT: step %q failed, compensations also failed:\", compErr.StepName)\n    for _, f := range compErr.Failed {\n        log.Printf(\"  - %q: %v\", f.StepName, f.Err)\n    }\n}\n```\n\n---\n\n## Observability\n\nAttach hooks for logging, metrics, or tracing - no changes to step code required:\n\n```go\nrunner := kata.New(steps...).WithOptions(\n    kata.WithHooks(kata.Hooks{\n        OnStepStart: func(ctx context.Context, name string) {\n            metrics.Inc(\"kata.step.started\", name)\n        },\n        OnStepDone: func(ctx context.Context, name string, d time.Duration) {\n            metrics.Histogram(\"kata.step.duration\", d, name)\n        },\n        OnStepFailed: func(ctx context.Context, name string, err error) {\n            log.Errorf(\"step %q failed: %v\", name, err)\n        },\n        OnCompensationStart: func(ctx context.Context, name string) {\n            log.Warnf(\"compensating %q\", name)\n        },\n        OnCompensationFailed: func(ctx context.Context, name string, err error) {\n            alerts.Fire(\"compensation_failed\", name, err)\n        },\n    }),\n)\n```\n\nAvailable hooks:\n\n| Hook | When |\n|---|---|\n| `OnStepStart` | Before a step begins |\n| `OnStepDone` | After a step succeeds |\n| `OnStepFailed` | After a step exhausts all retries and fails |\n| `OnCompensationStart` | Before a compensation begins |\n| `OnCompensationDone` | After a compensation succeeds |\n| `OnCompensationFailed` | After a compensation fails |\n\n---\n\n## Full example\n\n```go\ntype OrderState struct {\n    // inputs\n    CardToken string\n    ItemID    string\n    UserEmail string\n    Amount    int64\n\n    // filled in by steps\n    ChargeID    string\n    ReservationID string\n}\n\nvar orderRunner = kata.New(\n    kata.Step(\"charge-card\", func(ctx context.Context, s *OrderState) error {\n        id, err := payments.Charge(ctx, s.CardToken, s.Amount)\n        s.ChargeID = id\n        return err\n    }).Compensate(func(ctx context.Context, s *OrderState) error {\n        return payments.Refund(ctx, s.ChargeID)\n    }).Retry(3, kata.Exponential(100*time.Millisecond)).Timeout(10*time.Second),\n\n    kata.Step(\"reserve-stock\", func(ctx context.Context, s *OrderState) error {\n        id, err := warehouse.Reserve(ctx, s.ItemID)\n        s.ReservationID = id\n        return err\n    }).Compensate(func(ctx context.Context, s *OrderState) error {\n        return warehouse.Release(ctx, s.ReservationID)\n    }),\n\n    kata.Step(\"create-shipment\", func(ctx context.Context, s *OrderState) error {\n        return shipping.Create(ctx, s.ReservationID)\n    }),\n\n    kata.Parallel(\"notify\",\n        kata.Step(\"email\", func(ctx context.Context, s *OrderState) error {\n            return mailer.Send(ctx, s.UserEmail, \"Your order is confirmed!\")\n        }),\n        kata.Step(\"analytics\", func(ctx context.Context, s *OrderState) error {\n            return analytics.Track(ctx, \"order_placed\", s.ItemID)\n        }),\n    ),\n)\n\nfunc PlaceOrder(ctx context.Context, req *Request) error {\n    state := \u0026OrderState{\n        CardToken: req.CardToken,\n        ItemID:    req.ItemID,\n        UserEmail: req.UserEmail,\n        Amount:    req.Amount,\n    }\n\n    err := orderRunner.Run(ctx, state)\n    if err != nil {\n        var compErr *kata.CompensationError\n        if errors.As(err, \u0026compErr) {\n            // compensation failed - alert on-call\n            pagerduty.Fire(compErr)\n        }\n        return err\n    }\n    return nil\n}\n```\n\n---\n\n## Comparison\n\n| | kata | Temporal/Cadence | floxy | go-saga |\n|---|---|---|---|---|\n| External service required | ✗ | ✓ (server cluster) | ✗ | ✗ |\n| Persistent state | plug-in | ✓ | PostgreSQL | ✗ |\n| Generics (typed state) | ✓ | ✗ | ✗ | ✗ |\n| Parallel steps | ✓ | ✓ | ✓ | ✗ |\n| Per-step retry + backoff | ✓ | ✓ | ✓ | ✗ |\n| Per-step timeout | ✓ | ✓ | ✗ | ✗ |\n| Observability hooks | ✓ | ✓ | ✗ | ✗ |\n| Zero dependencies | ✓ | ✗ | ✗ | ✓ |\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkerlenton%2Fkata","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkerlenton%2Fkata","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkerlenton%2Fkata/lists"}