{"id":31377511,"url":"https://github.com/Raezil/GoEventBus","last_synced_at":"2025-09-28T05:02:06.874Z","repository":{"id":255361373,"uuid":"849363374","full_name":"Raezil/GoEventBus","owner":"Raezil","description":"A lock-free, ultra-fast event bus for Go that powers real-time pipelines, microservices","archived":false,"fork":false,"pushed_at":"2025-07-13T18:50:42.000Z","size":2692,"stargazers_count":27,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-13T20:41:43.714Z","etag":null,"topics":["concurrency","event-driven","event-driven-architecture","eventtbus","golang","inmemoryeventbus","library","lock-free","pubsub","realtime","ring-buffer"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"xhd2015/GOEventBus-fork","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Raezil.png","metadata":{"files":{"readme":"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":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-08-29T13:13:20.000Z","updated_at":"2025-07-13T18:50:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"4c0616bf-0ac4-481d-9ab8-36df71abcc80","html_url":"https://github.com/Raezil/GoEventBus","commit_stats":null,"previous_names":["raezil/goeventbus"],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/Raezil/GoEventBus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Raezil%2FGoEventBus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Raezil%2FGoEventBus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Raezil%2FGoEventBus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Raezil%2FGoEventBus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Raezil","download_url":"https://codeload.github.com/Raezil/GoEventBus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Raezil%2FGoEventBus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":277326294,"owners_count":25799445,"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-09-28T02:00:08.834Z","response_time":79,"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":["concurrency","event-driven","event-driven-architecture","eventtbus","golang","inmemoryeventbus","library","lock-free","pubsub","realtime","ring-buffer"],"created_at":"2025-09-28T05:00:48.068Z","updated_at":"2025-09-28T05:02:06.866Z","avatar_url":"https://github.com/Raezil.png","language":"Go","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://github.com/Raezil/GoEventBus/blob/main/logo2025.png?raw=true\"\u003e\n\u003c/p\u003e\n\n# GoEventBus\n\nA blazing‑fast, in‑memory, lock‑free event bus for Go—ideal for low‑latency pipelines, microservices, and game loops.  \n\n[![Go Report Card](https://goreportcard.com/badge/github.com/Raezil/GoEventBus)](...)\n\n## 📚 Table of Contents\n- [Features](#features)\n- [Why GoEventBus?](#why-goeventbus)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Transactions](#transactions)\n- [API Reference](#api-reference)\n- [Back-pressure and Overrun Policies](#back-pressure-and-overrun-policies)\n- [Use Cases](#-use-cases)\n- [Benchmarks](#benchmarks)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n- **Lock‑free ring buffer**: Efficient event dispatching using atomic operations and cache‑line padding.\n- **Context‑aware handlers**: Every handler now receives a `context.Context`, enabling deadlines, cancellation and tracing.\n- **Back‑pressure policies**: Choose whether to drop, block, or error when the buffer is full.\n- **Configurable buffer size**: Specify the ring buffer size (must be a power of two) when creating the store.\n- **Async or Sync processing**: Toggle between synchronous handling or async goroutine‑based processing via the `Async` flag.\n- **Metrics**: Track published, processed, and errored event counts with a simple API.\n- **Simple API**: Easy to subscribe, publish, and retrieve metrics.\n\n## Why GoEventBus?\n\nModern Go apps demand lightning‑fast, non‑blocking communication—but channels can bottleneck and external brokers add latency, complexity and ops overhead. GoEventBus is your in‑process, lock‑free solution:\n\n- **Micro‑latency dispatch**  \n  Atomic, cache‑aligned ring buffers deliver sub‑microsecond hand‑offs—no locks, no syscalls, zero garbage.  \n- **Sync or Async at will**  \n  Flip a switch to run handlers inline for predictability or in goroutines for massive parallelism.  \n- **Back‑pressure your way**  \n  Choose from _DropOldest_, _Block_ or _ReturnError_ to match your system’s tolerance for loss or latency.  \n- **Built‑in observability**  \n  Expose counters for published, processed and errored events out of the box—no extra instrumentation.  \n- **Drop‑in, zero deps**  \n  One import, no external services, no workers to manage—just Go 1.21+ and you’re off.  \n\nWhether you’re building real‑time analytics, high‑throughput microservices, or game engines, GoEventBus keeps your events moving at Go‑speed.\n\n## Installation\n\n```bash\ngo get github.com/Raezil/GoEventBus\n```\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/Raezil/GoEventBus\"\n)\n\n// Define a typed projection as a struct\ntype HouseWasSold struct{}\n\nfunc main() {\n\t// Create a dispatcher mapping projections (string or struct) to handlers\n\tdispatcher := GoEventBus.Dispatcher{\n\t\t\"user_created\": func(ctx context.Context, args map[string]any) (GoEventBus.Result, error) {\n\t\t\tuserID := args[\"id\"].(string)\n\t\t\tfmt.Println(\"User created with ID:\", userID)\n\t\t\treturn GoEventBus.Result{Message: \"handled user_created\"}, nil\n\t\t},\n\t\tHouseWasSold{}: func(ctx context.Context, args map[string]any) (GoEventBus.Result, error) {\n\t\t\taddress := args[\"address\"].(string)\n\t\t\tprice := args[\"price\"].(int)\n\t\t\tfmt.Printf(\"House sold at %s for $%d\\n\", address, price)\n\t\t\treturn GoEventBus.Result{Message: \"handled HouseWasSold\"}, nil\n\t\t},\n\t}\n\n\t// Initialise an EventStore with a 64K ring buffer and DropOldest overrun policy\n\tstore := GoEventBus.NewEventStore(\u0026dispatcher, 1\u003c\u003c16, GoEventBus.DropOldest)\n\n\t// Enable asynchronous processing\n\tstore.Async = true\n\n\t// Enqueue a string-based event\n\t_ = store.Subscribe(context.Background(), GoEventBus.Event{\n\t\tID:         \"evt1\",\n\t\tProjection: \"user_created\",\n\t\tArgs:       map[string]any{\"id\": \"12345\"},\n\t})\n\n\t// Enqueue a struct-based event\n\t_ = store.Subscribe(context.Background(), GoEventBus.Event{\n\t\tID:         \"evt2\",\n\t\tProjection: HouseWasSold{},\n\t\tArgs:       map[string]any{\"address\": \"123 Main St\", \"price\": 500000},\n\t})\n\n\t// Process pending events\n\tstore.Publish()\n\n\t// Wait for all async handlers to finish\n\tif err := store.Drain(context.Background()); err != nil {\n\t\tlog.Fatalf(\"Failed to drain EventStore: %v\", err)\n\t}\n\n\t// Retrieve metrics\n\tpublished, processed, errors := store.Metrics()\n\tfmt.Printf(\"published=%d processed=%d errors=%d\\n\", published, processed, errors)\n}\n```\n\n## Transactions\n\nGoEventBus now supports atomic transactions, allowing you to group multiple events and commit them together. This ensures that either all events are successfully published and handled, or none are.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\t\"github.com/Raezil/GoEventBus\"\n)\n\nfunc main() {\n\t// Begin a new transaction on the existing EventStore\n\n\t// Create a dispatcher mapping projections to handlers\n\tdispatcher := GoEventBus.Dispatcher{\n\t\t\"user_created\": func(ctx context.Context, args map[string]any) (GoEventBus.Result, error) {\n\t\t\treturn GoEventBus.Result{Message: \"handled user_created\"}, nil\n\t\t},\n\t\t\"send_email\": func(ctx context.Context, args map[string]any) (GoEventBus.Result, error) {\n\t\t\tlog.Println(\"Hello\")\n\t\t\treturn GoEventBus.Result{Message: \"handled send_email\"}, nil\n\t\t},\n\t}\n\n\t// Initialise an EventStore with a 64K ring buffer and DropOldest policy\n\tstore := GoEventBus.NewEventStore(\u0026dispatcher, 1\u003c\u003c16, GoEventBus.DropOldest)\n\ttx := store.BeginTransaction()\n\n\t// Buffer multiple events\n\ttx.Publish(GoEventBus.Event{\n\t\tID:         \"evtA\",\n\t\tProjection: \"user_created\",\n\t\tArgs:       map[string]any{\"id\": \"12345\"},\n\t})\n\ttx.Publish(GoEventBus.Event{\n\t\tID:         \"evtB\",\n\t\tProjection: \"send_email\",\n\t\tArgs:       map[string]any{\"template\": \"welcome\", \"userID\": \"12345\"},\n\t})\n\ttx.Rollback()\n\n\t// Commit the transaction atomically\n\tif err := tx.Commit(context.Background()); err != nil {\n\t\tlog.Fatalf(\"transaction failed: %v\", err)\n\t}\n}\n```\n\n## API Reference\n\n### `type Result`\n\n```go\ntype Result struct {\n    Message string // Outcome message from handler\n}\n```\n\n### `type Dispatcher`\n\n```go\ntype Dispatcher map[interface{}]func(context.Context, map[string]any) (Result, error)\n```\nA map from projection keys to handler functions. Handlers receive a `context.Context` and an argument map, and return a `Result` and an error.\n\n### `type Event`\n\n```go\ntype Event struct {\n    ID         string          // Unique identifier for the event\n    Projection interface{}     // Key to look up the handler in the dispatcher\n    Args       map[string]any  // Payload data for the event\n}\n```\n\n### `type Transaction`\n\n```go\ntype Transaction struct {\n    store  *EventStore\n    events []Event\n    startHead uint64 // head position when transaction began\n\n}\n```\n\n- `BeginTransaction() *Transaction`: Start a new transaction.\n- `Publish(e Event)`: Buffer an event within the transaction.\n- `Commit(ctx context.Context) error`: Atomically enqueue and process all buffered events, returning on the first error.\n- `Rollback()`: Discard buffered events without publishing.\n\n### `type OverrunPolicy`\n\n```go\ntype OverrunPolicy int\n\nconst (\n    DropOldest  OverrunPolicy = iota // Default: discard oldest events\n    Block                            // Block until space is available\n    ReturnError                      // Fail fast with ErrBufferFull\n)\n```\n\n### `type EventStore`\n\n```go\ntype EventStore struct {\n    dispatcher     *Dispatcher      // Pointer to the dispatcher map\n    size           uint64           // Buffer size (power of two)\n    buf            []unsafe.Pointer // Ring buffer of event pointers\n    events         []Event          // Backing slice for Event data\n    head           uint64           // Atomic write index\n    tail           uint64           // Atomic read index\n\n    // Config flags\n    Async          bool\n    OverrunPolicy  OverrunPolicy\n\n    // Counters\n    publishedCount uint64\n    processedCount uint64\n    errorCount     uint64\n}\n```\n\n#### `NewEventStore`\n\n```go\nfunc NewEventStore(dispatcher *Dispatcher, bufferSize uint64, policy OverrunPolicy) *EventStore\n```\nCreates a new `EventStore` with the provided dispatcher, ring buffer size, and overrun policy.\n\n#### `Subscribe`\n\n```go\nfunc (es *EventStore) Subscribe(ctx context.Context, e Event) error\n```\nAtomically enqueues an `Event` for later publication, applying back‑pressure according to `OverrunPolicy`. If `OverrunPolicy` is `ReturnError` and the buffer is full, the function returns `ErrBufferFull`.\n\n#### `Publish`\n\n```go\nfunc (es *EventStore) Publish()\n```\nDispatches all events from the last published position to the current head. If `Async` is true, handlers run in separate goroutines; otherwise they run in the caller's goroutine.\n\n#### `Drain`\n\n```go\nfunc (es *EventStore) Drain(ctx context.Context) error\n```\nBlocks until all in-flight asynchronous handlers complete, then stops the worker pool. Returns an error if the provided context.Context is canceled or its deadline is exceeded.\n\n#### `Close`\n\n```go\nfunc (es *EventStore) Close(ctx context.Context) error\n```\nDrains all pending asynchronous handlers and shuts down the EventStore. Blocks until all in-flight handlers complete or the provided context.Context is canceled. Returns an error if the context’s deadline is exceeded or it is otherwise canceled.\n\n#### `Metrics`\n\n```go\nfunc (es *EventStore) Metrics() (published, processed, errors uint64)\n```\nReturns the total count of published, processed, and errored events.\n\n### `Schedule`\n\n```go\nfunc (es *EventStore) Schedule(ctx context.Context, t time.Time, e Event) *time.Timer\n```\nSchedules an Event to be subscribed and published at a specific time `t`.\n\n- [x] If `t` is in the future, the function returns a `*time.Timer` that can be used to cancel the event before it fires by using `timer.Stop()`.\n- [x] If `t` is in the past or is the current time, the event is executed immediately and synchronously, bypassing the event queue, and the function returns `nil`.\n\n### `ScheduleAfter`\n\n```go\nfunc (es *EventStore) ScheduleAfter(ctx context.Context, d time.Duration, e Event) *time.Timer\n```\nA convenience wrapper around `Schedule` that fires an event after a specified duration `d`.\n\n- [x] If the duration `d` is greater than zero, it returns a cancellable `*time.Timer`.\n- [x] If `d` is zero or negative, the event is executed immediately and synchronously, bypassing the event queue, and the function returns `nil`.\n\n## Back-pressure and Overrun Policies\n\nGoEventBus provides three strategies for handling a saturated ring buffer:\n\n| Policy       | Behaviour                                                                                     | When to use                                  |\n|--------------|------------------------------------------------------------------------------------------------|----------------------------------------------|\n| `DropOldest` | Atomically advances the read index, discarding the oldest event to make room for the new one. | Low‑latency scenarios where the newest data is most valuable and occasional loss is acceptable. |\n| `Block`      | Causes `Subscribe` to block (respecting its context) until space becomes available.           | Workloads that must not lose events but can tolerate the latency of back‑pressure. |\n| `ReturnError`| `Subscribe` returns `ErrBufferFull` immediately, allowing the caller to decide what to do.    | Pipelines where upstream logic controls retries and failures explicitly. |\n\n`DropOldest` is the default behaviour and matches releases prior to April 2025.\n\n## 💡 Use Cases\n\nGoEventBus is ideal for scenarios where low‑latency, high‑throughput, and non‑blocking event dispatching is needed:\n\n- 🔄 Real‑time event pipelines (e.g. analytics, telemetry)\n- 📥 Background task execution or job queues\n- 🧩 Microservice communication using in‑process events\n- ⚙️ Observability/event sourcing in domain‑driven designs\n- 🔁 In‑memory pub/sub for small‑scale distributed systems\n- 🎮 Game loops or simulations requiring lock‑free dispatching\n\n## Benchmarks\n\nAll benchmarks were run with Go’s testing harness (`go test -bench .`) on an `-8` procs configuration. Numbers below are from the April 2025 release.\n\n| Benchmark                         | Iterations   | ns/op  |\n|-----------------------------------|-------------:|-------:|\n| `BenchmarkSubscribe-8`            | 27,080,376   | 40.37  |\n| `BenchmarkSubscribeParallel-8`    | 26,418,999   | 38.42  |\n| `BenchmarkPublish-8`              | 295,661,464  | 3.910  |\n| `BenchmarkPublishAfterPrefill-8`  | 252,943,526  | 4.585  |\n| `BenchmarkSubscribeLargePayload-8`| 1,613,017    | 771.5  |\n| `BenchmarkPublishLargePayload-8`  | 296,434,225  | 3.910  |\n| `BenchmarkEventStore_Async-8`     | 2,816,988    | 436.5  |\n| `BenchmarkEventStore_Sync-8`      | 2,638,519    | 428.5  |\n| `BenchmarkFastHTTPSync-8`         | 6,275,112    | 163.8  |\n| `BenchmarkFastHTTPAsync-8`        | 1,954,884    | 662.0  |\n| `BenchmarkFastHTTPParallel-8`     | 4,489,274    | 262.3  |\n\n## Contributing\n\nContributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://github.com/Raezil/GoEventBus/issues).\n\n## License\n\nDistributed under the MIT License. See `LICENSE` for more information.\n\n","funding_links":[],"categories":["Messaging","消息"],"sub_categories":["Search and Analytic Databases","检索及分析资料库"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRaezil%2FGoEventBus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRaezil%2FGoEventBus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRaezil%2FGoEventBus/lists"}