{"id":36583767,"url":"https://github.com/breuhq/durex","last_synced_at":"2026-01-12T08:00:13.903Z","repository":{"id":191039707,"uuid":"683467348","full_name":"breuHQ/durex","owner":"breuHQ","description":"Tools to make it easy to work with Temporal Go SDK.","archived":false,"fork":false,"pushed_at":"2025-12-15T09:17:34.000Z","size":154,"stargazers_count":6,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-12-25T15:22:58.734Z","etag":null,"topics":["sdk-go","temporal"],"latest_commit_sha":null,"homepage":"https://go.breu.io","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/breuHQ.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":"2023-08-26T17:03:15.000Z","updated_at":"2025-12-15T09:16:16.000Z","dependencies_parsed_at":"2024-09-12T14:20:00.614Z","dependency_job_id":"a64efbc6-1a06-4a03-9c61-6fc987b369c3","html_url":"https://github.com/breuHQ/durex","commit_stats":null,"previous_names":["breuhq/go-temporal-tools"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/breuHQ/durex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/breuHQ%2Fdurex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/breuHQ%2Fdurex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/breuHQ%2Fdurex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/breuHQ%2Fdurex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/breuHQ","download_url":"https://codeload.github.com/breuHQ/durex/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/breuHQ%2Fdurex/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28336957,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T06:09:07.588Z","status":"ssl_error","status_checked_at":"2026-01-12T06:05:18.301Z","response_time":98,"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":["sdk-go","temporal"],"created_at":"2026-01-12T08:00:11.667Z","updated_at":"2026-01-12T08:00:13.806Z","avatar_url":"https://github.com/breuHQ.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-temporal-tools: utilities for temporal.io go sdk\n\n![GitHub release (with filter)](https://img.shields.io/github/v/release/breuHQ/go-temporal-tools)\n![GitHub go.mod Go version (subdirectory of monorepo)](https://img.shields.io/github/go-mod/go-version/breuHQ/go-temporal-tools)\n![Tests](https://img.shields.io/github/actions/workflow/status/breuHQ/go-temporal-tools/test.yml?label=test)\n[![License](https://img.shields.io/github/license/breuHQ/go-temporal-tools)](./LICENSE)\n![GitHub contributors](https://img.shields.io/github/contributors/breuHQ/go-temporal-tools)\n\n\u003e [!WARNING]\n\u003e This is a work in porgress. The surface API can change without warning.\n\n## 🚀 Install\n\n```sh\ngo get go.breu.io/durex\n```\n\n**Compatibility**: go \u003e= 1.23\n\n## Why?\n\nAfter working with temporal.io across multiple projects, we have standarized a set of best practices across our projects.\n\nFor example, to create a unique \u0026 identifiable workflow id for debugging, we have found that following\nthe [block, element, modifier](https://getbem.com/introduction/) method, a technique for writing maintainable CSS, makes it very readable and maintainable.\n\nWe also found that attaching the workflow to a queue makes it very easy. For us, `Queue` is where it all begins.\n\n## Getting Started\n\nWe start by creating a `Queue` first and then call `ExecuteWorkflow` or `ExecuteChildWorkflow` methods on the `Queue`\ninterface.\n\n### temporal.io setup\n\nGiven the `temporal.go` file below, we are going to setup our queues as global varibales like\n\n```go\npackage temporal\n\nimport (\n  \"fmt\"\n  \"log/slog\"\n  \"sync\"\n  \"time\"\n\n  \"github.com/avast/retry-go/v4\"\n  \"github.com/ilyakaznacheev/cleanenv\"\n  \"go.breu.io/slog-utils/calldepth\"\n  \"go.temporal.io/sdk/client\"\n)\n\ntype (\n  Temporal interface {\n    Client() client.Client\n    ConnectionString() string\n  }\n\n  config struct {\n    Host   string `env:\"TEMPORAL_HOST\" env-default:\"localhost\"`\n    Port   int    `env:\"TEMPORAL_PORT\" env-default:\"7233\"`\n    client client.Client\n    logger *slog.Logger\n  }\n\n  Option func(*config)\n)\n\nvar (\n  once sync.Once\n)\n\nconst (\n  MaxAttempts = 10\n)\n\nfunc (c *config) ConnectionString() string {\n  return fmt.Sprintf(\"%s:%d\", c.Host, c.Port)\n}\n\nfunc (c *config) Client() client.Client {\n  if c.client == nil {\n  once.Do(func() {\n    slog.Info(\n      \"initializing temporal connection\",\n      slog.String(\"connection_string\", c.ConnectionString()),\n    )\n\n    options := client.Options{\n      HostPort: c.ConnectionString(),\n      Logger: c.logger,\n    }\n\n    retryTemporal := func() error {\n      clt, err := client.Dial(options)\n      if err != nil {\n        return err\n      }\n\n      c.client = clt\n\n      slog.Info(\"temporal: connected\")\n\n      return nil\n    }\n\n    if err := retry.Do(\n      retryTemporal,\n      retry.Attempts(MaxAttempts),\n      retry.Delay(1*time.Second),\n      retry.OnRetry(func(n uint, err error) {\n        slog.Info(\n          \"temporal: failed to connect. retrying connection ...\",\n          slog.Any(\"attempt\", n+1),\n          slog.Any(\"max attempts\", MaxAttempts),\n          slog.String(\"error\", err.Error()),\n        )\n      }),\n      ); err != nil {\n        panic(fmt.Errorf(\"failed to connect to temporal: %w\", err))\n      }\n    })\n  }\n\n  return c.client\n}\n\nfunc WithHost(host string) Option {\n  return func(c *config) {\n  c.Host = host\n  }\n}\n\nfunc WithPort(port int) Option {\n  return func(c *config) {\n  c.Port = port\n  }\n}\n\nfunc WithLogger(logger *slog.Logger) Option {\n  return func(c *config) {\n  c.logger = logger\n  }\n}\n\nfunc FromEnvironment() Option {\n  return func(c *config) {\n  if err := cleanenv.ReadEnv(c); err != nil {\n  panic(fmt.Errorf(\"failed to read environment: %w\", err))\n  }\n  }\n}\n\nfunc New(opts ...Option) Temporal {\n  c := \u0026config{}\n\n  for _, opt := range opts {\n  opt(c)\n  }\n\n  return c\n}\n```\n\n### `Temporal` singleton\n\n```go\npackage shared\n\nimport (\n  \"os\"\n  \"sync\"\n\n  \"shared/temporal\"\n\n  \"go.breu.io/slog-utils/calldepth\"\n)\n\nvar (\n  tmprl     temporal.Temporal // Global temporal instance.\n  tmprlOnce sync.Once         // Global temporal initialization state.\n)\n\n// Temporal returns the global temporal instance.\nfunc Temporal() temporal.Temporal {\n  tmprlOnce.Do(func() {\n    tmprl = temporal.New(\n      temporal.FromEnvironment(),\n      temporal.WithLogger(\n        calldepth.NewAdapter(\n          calldepth.NewLogger(slog.NewJsonLogger()),\n          calldepth.WithCallDepth(5), // 5 for activities, 6 for workflows.\n         ).WithGroup(\"temporal\")\n      ),\n    )\n  })\n\n  return tmprl\n}\n```\n\n### `Queue` setup\n\n```go\npackage shared\n\nimport (\n  \"sync\"\n\n  \"go.breu.io/durex/queues\"\n)\n\nvar (\n  coreq queues.Queue\n  coreqone sync.once\n)\n\n// The default value right now is \"io.breu\".\n// Change this as per your requirements. A good practice is to use java's package notation.\nfunc init() {\n  queues.SetDefaultPrefix(\"com.company.pkg\")\n}\n\nfunc CoreQueue(\n  once.Do(func() {\n    queues.New(\n      WithName(\"core\"),\n      // We have already setup temporal as a signleton in the shared package. We leverage the instantiatilized client.\n      WithClient(\n        Temporal().Client(),\n      ),\n    )\n  })\n)\n```\n\n## Usage for Workflows\n\nWe make the assumptions that a workflow is tied to a queue. So by calling the Queue.ExecuteWorkflow() to start a workflow.\n\n```go\npackage main\n\nimport (\n  \"context\"\n  \"shared\"\n\n  \"github.com/google/uuid\"\n  \"go.temporal.io/sdk/workflow\"\n\n  \"go.breu.io/durex/workflows\"\n)\n\nfunc main() {\n  worker := shared.CoreQueue().CreateWorker()\n  defer worker.Stop()\n\n  worker.RegisterWorkflow(Workflow)\n  worker.RegisterWorkflow(ChildWorkflow)\n\n  if err != worker.Start(); err != nil {\n    // handler error\n  }\n\n\n  opts, err := workflows.NewOptions(\n    workflows.WithBlock(\"block\"),\n    workflows.WithBlockID(uuid.New()), // d5e012df-5b9e-41cf-9ed5-3439eeafd8e4\n  )\n  if err != nil {\n    // handle error\n  }\n\n  exe, err := shared.CoreQueue().ExecuteWorkflow(\n    context.Background(),\n    // The workflow id created in this case would be\n    //  \"com.company.pkg.block.d5e012df-5b9e-41cf-9ed5-3439eeafd8e4\"\n    opts,\n    Workflow, // or workflow function name\n    payload,\n  )\n  if err != nil {\n    // handle error\n  }\n}\n\nfunc Workflow(ctx context.Context, payload any) error {\n  childopts, err := workflow.NewOptions(\n    WithParent(ctx),\n    WithBlock(\"child\"),\n    WithBlockID(uuid.New()), // bb42d90a-d3f3-4022-bc09-aeed6e9db659\n  )\n  if err != nil {\n    return err\n  }\n\n  future := shared.CoreQueue().ExecuteChildWorkflow(\n    ctx,\n    // By passing WithParent(ctx), the helper method automagically picks up the right workflow to create the child id i.e.\n    // \"com.company.pkg.block.d5e012df-5b9e-41cf-9ed5-3439eeafd8e4.child.bb42d90a-d3f3-4022-bc09-aeed6e9db659\"\n    opts,\n    ChildWorkflow,\n    payload,\n  )\n\n  // Do Something\n\n  return nil\n}\n\nfunc ChildWorkflow(ctx workflow.Context, payload any) {\n  // Child workflow\n}\n```\n\n## 👤 Contributors\n\n![Contributors](https://contrib.rocks/image?repo=breuHQ/go-temporal-tools)\n\n## 📝 License\n\nCopyright © 2023 [Breu Inc.](https://github.com/breuHQ)\n\nThis project is [MIT](./LICENSE) licensed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbreuhq%2Fdurex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbreuhq%2Fdurex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbreuhq%2Fdurex/lists"}