{"id":20877210,"url":"https://github.com/badu/bus","last_synced_at":"2025-10-26T12:38:22.180Z","repository":{"id":142775238,"uuid":"613261702","full_name":"badu/bus","owner":"badu","description":"A PubSub / EventBus using Go language generics (v1.20 required)","archived":false,"fork":false,"pushed_at":"2024-01-17T10:19:14.000Z","size":60,"stargazers_count":25,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-01T07:53:21.416Z","etag":null,"topics":["bus","event","eventbus","events","generics","go","golang","pubsub"],"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/badu.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}},"created_at":"2023-03-13T08:22:53.000Z","updated_at":"2025-02-18T09:17:29.000Z","dependencies_parsed_at":"2024-11-18T06:57:28.047Z","dependency_job_id":"a7dd02de-8ab2-4c01-947e-652089e1c517","html_url":"https://github.com/badu/bus","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badu%2Fbus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badu%2Fbus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badu%2Fbus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/badu%2Fbus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/badu","download_url":"https://codeload.github.com/badu/bus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253766062,"owners_count":21960840,"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","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":["bus","event","eventbus","events","generics","go","golang","pubsub"],"created_at":"2024-11-18T06:56:19.768Z","updated_at":"2025-10-26T12:38:17.134Z","avatar_url":"https://github.com/badu.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bus\n\n- **Independent**: has no external dependencies\n- **Probably Fast**: no reflection\n- **Type Safe**: built on generics\n- **Small and Simple**: can be used as following:\n\nHaving the following event:\n\n```go\n    package events\n\n    type InterestingEvent struct {\n    }\n```\n\na listener can registering a handler by calling `Sub` method:\n\n```go\n    package subscriber\n\n   import \"github.com/badu/bus\"\n   \n   // ... somewhere in a setup function / constructor    \n   bus.Sub(OnMyEventOccurred)\n```\n\nwhere the handler is having the following signature:\n\n```go\n    func OnMyEventOccurred(event InterestingEvent){\n      // do something with the event here\n\t}   \n```\n\nThe event producer / dispatcher will simply:\n\n```go\n    package dispatcher\n\n   import \"github.com/badu/bus\"\n   \n   // ...somewhere in a dispatching function\n   \n   bus.Pub(InterestingEvent{})\n```\n\nIf the event needs to go async, in the sense that the bus package will spin up a goroutine for the caller, just\nimplement the following interface:\n\n```go\n    package events\n\n    func (e InterestingEvent) Async() bool{ return true }\n``` \n\nif the handler has the signature declared above, or\n\n```go\n    package events\n\n    func (e *InterestingEvent) Async() bool{ return true }\n``` \n\nif the handler has the signature as following:\n\n```go\n    func OnMyEventOccurred(event *InterestingEvent){\n      // do something with the event here\n\t}   \n```\n\nAnother way to publish an event async, is to use `PubAsync` method that package exposes.\n\nBy default, the bus is using sync events, which means that it waits for listeners to complete their jobs before calling\nthe next listener.\n\nUsage : `go get github.com/badu/bus`\n\n## F.A.Q.\n\n1. I want to cancel subscription at some point. How do I do that?\n\nSubscribing returns access to the `Cancel` method\n\n```go\npackage subscriber\n\n// ... somewhere in a setup function / constructor\nsubscription := bus.Sub(OnMyEventOccurred)\n// when needed, calling cancel of subscription, so function OnMyEventOccurred won't be called anymore\nsubscription.Cancel()\n```\n\n2. Can I subscribe once?\n\nYes! The event handler has to return true.\n\n```go\npackage subscriber\n// ... somewhere in a setup function / constructor\n\nbus.SubCancel( func(event InterestingEvent) bool {\n    // do something with the event here\n\treturn true // returning true will cancel the subscription\n})\n```\n\n3. I want to inspect registered events. How do I do that?\n\nThe events mapper is a `sync.Map`, so iterate using `Range`\n\n```go\nbus.Range(func(k, v any)bool{\n\tfmt.Printf(\"%#v %#v\\n\", k, v)\n})\n```\n\n4. I want to use my own event names. Is that possible?\n\nYes! You have to implement the following interface:\n\n```go\npackage events\n\nfunc (e InterestingEvent) EventID() string{\n\treturn \"YourInterestingEventName\"\n}\n```\n\nThe event name is the key of the mapper, which means that implementing your own event names might cause panics \nif you have name collisions.\n\n5. Will I have race conditions?\n\nNo. The package is concurrent safe.\n\n## What Problem Does It Solve?\n\nDecoupling of components: publishers and subscribers can operate independently of each other, with no direct knowledge\nof each other's existence. This decoupling allows for greater flexibility and scalability, as new publishers and\nsubscribers can be added to the system without disrupting existing components. Also, this facilitates testing by\ntriggering or ignoring certain events in some scenarios.\n\nAsynchronous messaging: messages can be sent and received asynchronously (by spinning up goroutines), which means that\npublishers and subscribers don't have to wait for each other to consume their messages. This can improve performance and\nresponse times in a system.\n\nReliability: the message broker acts as a buffer between publishers and subscribers, ensuring that messages are\ndelivered even if one or more components in the system are temporarily unavailable.\n\nModularity: the Pub-Sub pattern can be used to break a monolithic application into smaller, more modular components.\nEach component can then be developed and tested independently, making the overall system easier to maintain and update.\n\n## Scenarios of Usage\n\nInside the `test_scenarios` folder, you can find the following scenarios:\n\n1. Fire and Forget.\n\n   Imagine a system / application where we have three services : `users`, `notifications` (email and\n   SMS) and `audit`. When a user registers, we want to send welcoming messages via SMS and email, but we also want to\n   audit that registration for reporting purposes.\n\n   The [UserRegisteredEvent](https://github.com/badu/bus/blob/master/test_scenarios/fire-and-forget/events/main.go#L3)\n   will carry the freshly registered username (which is also the email) and phone to the email and sms services. The\n   event is [triggered](https://github.com/badu/bus/blob/master/test_scenarios/fire-and-forget/users/service.go#L23) by\n   the user service, which performs the creation of the user account. We're using the `fire and forget` technique here,\n   because the operation of registration should not depend on the fact that we've been able to\n   send a welcoming email or a sms, or the audit system malfunctions.\n\n   Simulating audit service malfunctions easy.\n   Instead of using `Sub`, we're using `SubUnsub` to register the listener\n   and return [`true`](https://github.com/badu/bus/blob/master/test_scenarios/fire-and-forget/audit/service.go#L36) to\n   unsubscribe on events of that kind.\n\n2. Factory Request Reply\n\n   Imagine a system / application where we need to communicate with different microservices, but in this case we don't\n   want to bring them online, we're just wanting to stub the response as those services were alive.\n\n   This technique is useful when we need to test some complicated flows of business logic and facilitates the\n   transformation of an integration test into a classic unit test.\n\n   The `cart` service requires two replies from two other microservices `inventory` and `prices`. In the past, I've been\n   using a closure function to provide the service with both real GRPC clients or with mocks and stubs. The service\n   signature gets complicated and large as one service would depend on a lot of GRPC clients to aggregate data.\n\n   As you can see\n   the [test here](https://github.com/badu/bus/blob/master/test_scenarios/factory-request-reply/main_test.go) it's much\n   more elegant and the service constructor is much slimmer.\n\n   Events are one sync and one async, just to check it works in both scenarios.\n\n   Important to note that because a `WaitGroup` is being used in our event struct, we're forced to pass the events by\n   using a pointer, instead of passing them by value.\n\n3. Request Reply with Callback\n\n   In this example, we wanted to achieve two things. First is that the `service` and the `repository` are decoupled by\n   events. More than that, we wanted that the events are generic on their own.\n\n   The `orders` service will dispatch a generic request event, one for placing an order, which will carry an `Order` (\n   model) struct with that request and another `OrderStatus` (model) struct using the same generic event.\n\n   We are using a channel inside the generic `RequestEvent` to signal the `reply` to the publisher, which in this case\n   is a callback function that returns the result as if the publisher would have called directly the listener.\n\n   I am sure that you will find this technique interesting and having a large number of applications.\n\n4. Request Reply with Cancellation\n\n   Last but, not least, this is an example about providing `context.Context` along the publisher subscriber chain.\n   The `repository` is simulating a long database call, longer than the context's cancellation, so the service gets the\n   deadline exceeded error.\n\n   Note that this final example is not using a pointer to the event struct, but it contains two properties which have\n   pointers, so the `service` can access the altered `reply`.\n\n## Recommendations\n\n1. always place your events inside a separate `events` package, avoiding circular dependencies.\n2. in general, in `request-reply` scenarios, the events should be passed as pointers (even if it's somewhat slower),\n   because changing properties that represents the `reply` would not be reflected. Also, when using `sync.WaitGroup`\n   inside your event struct, always use method receivers and pass the event as a pointer — otherwise you will be passing\n   a lock by value (which is `sync.Locker`).\n3. be careful if you don't want to use pointers for events, but you still need to pass values from the listener to the\n   dispatcher. You should still have at least one property of that event that is a pointer (see events\n   in `request reply with cancellation` for example). Same technique can be applied when you need `sync.Waitgroup` to be\n   passed around with an event that is being sent by value, not by pointer.\n4. you can override the event name (which is by default, built using `fmt.Sprintf(\"%T\", yourEvent)`) you need to\n   implement `EventID() string` interface.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbadu%2Fbus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbadu%2Fbus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbadu%2Fbus/lists"}