Browse by type
Having the following event:
package events
type InterestingEvent struct {
}
a listener can registering a handler by calling Sub method:
package subscriber
import "github.com/badu/bus"
// ... somewhere in a setup function / constructor
bus.Sub(OnMyEventOccurred)
where the handler is having the following signature:
func OnMyEventOccurred(event InterestingEvent){
// do something with the event here
}
The event producer / dispatcher will simply:
package dispatcher
import "github.com/badu/bus"
// ...somewhere in a dispatching function
bus.Pub(InterestingEvent{})
Usage : go get github.com/badu/bus
Subscribing returns access to the Cancel method
package subscriber
// ... somewhere in a setup function / constructor
subscription := bus.Sub(OnMyEventOccurred)
// when needed, calling cancel of subscription, so function OnMyEventOccurred won't be called anymore
subscription.Cancel()
Yes! The event handler has to return true.
package subscriber
// ... somewhere in a setup function / constructor
bus.SubCancel( func (event InterestingEvent) bool {
// do something with the event here
return true // returning true will cancel the subscription
})
The events mapper is a sync.Map, so iterate using Range
bus.Range(func (k, v any)bool{
fmt.Printf("%#v %#v\n", k, v)
})
Yes! You have to implement the following interface:
package events
func (e InterestingEvent) EventID() string {
return "YourInterestingEventName"
}
The event name is the key of the mapper, which means that implementing your own event names might cause panics if you have name collisions.
No. The package is concurrent safe.
Decoupling of components: publishers and subscribers can operate independently of each other, with no direct knowledge of each other's existence. This decoupling allows for greater flexibility and scalability, as new publishers and subscribers can be added to the system without disrupting existing components. Also, this facilitates testing by triggering or ignoring certain events in some scenarios.
Asynchronous messaging: messages are received asynchronously (by spinning up goroutines), which means that bus doesn't have to wait for consumers to consume their messages. This can improve performance and response times in a system.
Reliability: the message broker acts as a buffer between publishers and subscribers, ensuring that messages are delivered even if one or more components in the system are temporarily unavailable.
Modularity: the Pub-Sub pattern can be used to break a monolithic application into smaller, more modular components. Each component can then be developed and tested independently, making the overall system easier to maintain and update.
Inside the test_scenarios folder, you can find the following scenarios:
Imagine a system / application where we have three services : users, notifications (email and
SMS) and audit. When a user registers, we want to send welcoming messages via SMS and email, but we also want to
audit that registration for reporting purposes.
The UserRegisteredEvent
will carry the freshly registered username (which is also the email) and phone to the email and sms services. The
event is triggered by
the user service, which performs the creation of the user account. We're using the fire and forget technique here,
because the operation of registration should not depend on the fact that we've been able to
send a welcoming email or a sms, or the audit system malfunctions.
Simulating audit service malfunctions easy.
Instead of using Sub, we're using SubUnsub to register the listener
and return true to
unsubscribe on events of that kind.
Imagine a system / application where we need to communicate with different microservices, but in this case we don't want to bring them online, we're just wanting to stub the response as those services were alive.
This technique is useful when we need to test some complicated flows of business logic and facilitates the transformation of an integration test into a classic unit test.
The cart service requires two replies from two other microservices inventory and prices. In the past, I've been
using a closure function to provide the service with both real GRPC clients or with mocks and stubs. The service
signature gets complicated and large as one service would depend on a lot of GRPC clients to aggregate data.
As you can see the test here it's much more elegant and the service constructor is much slimmer.
Important to note that because a WaitGroup is being used in our event struct, we're forced to pass the events by
using a pointer, instead of passing them by value.
In this example, we wanted to achieve two things. First is that the service and the repository are decoupled by
events. More than that, we wanted that the events are generic on their own.
The orders service will dispatch a generic request event, one for placing an order, which will carry an Order (
model) struct with that request and another OrderStatus (model) struct using the same generic event.
We are using a channel inside the generic RequestEvent to signal the reply to the publisher, which in this case
is a callback function that returns the result as if the publisher would have called directly the listener.
I am sure that you will find this technique interesting and having a large number of applications.
Last but, not least, this is an example about providing context.Context along the publisher subscriber chain.
The repository is simulating a long database call, longer than the context's cancellation, so the service gets the
deadline exceeded error.
Note that this final example is not using a pointer to the event struct, but it contains two properties which have
pointers, so the service can access the altered reply.
events package, avoiding circular dependencies.request-reply scenarios, the events should be passed as pointers (even if it's somewhat slower),
because changing properties that represents the reply would not be reflected. Also, when using sync.WaitGroup
inside your event struct, always use method receivers and pass the event as a pointer — otherwise you will be passing
a lock by value (which is sync.Locker).request reply with cancellation for example). Same technique can be applied when you need sync.Waitgroup to be
passed around with an event that is being sent by value, not by pointer.fmt.Sprintf("%T", yourEvent)) you need to
implement EventID() string interface.