Go is very powerful language for software development with its simplicity, concurrency, first-class functions and tools.
In Go, usually, a package is responsible only for one thing. Applications grows with several packages with their own responsibilities. When the number of packages in the project and responsibilities of the project increase, it is possible to mess up on communication with other packages. (In here, the communication means calling functions on different packages/modules on the flow with/without waiting result. Like incrementing counter metric when an order received/full-filled)
One of the known ways of decoupling on communication is the event driven software designs. Usually, using an external event/message-bus server is the way distributing the responsibility to internal/external services. But this also brings another external component to your stack. And each stack comes with its own problems.
For small services, packages/libraries, and embedded systems adding an external bus could be unnecessary addition. To overcome this problem, adding an internal message-bus package to your package may help. And when things getting bigger, it is possible to communicate with this internal message-bus to external message-bus services.
Assume that, a project needs to do several things on user registration like; incrementing counters, sending email, logging, and/or sending webhook to external service; instead of directly calling each of the functionalities directly, a message-bus can help to execute multiple functionalities asynchronously without knowing the caller.
Decoupled packages help us to write cleaner code, focus on only one thing at a time. With bus Go package, you can get benefit of real message bus system and write decoupled packages easily. The bus
package allows any handler to listen any events, without knowing who generates the event. Thus, packages can communicate without depending on each other. Moreover, it is very easy to substitute a consumer function. As long as the new function understands the Event
struct that are being sent and received, the other functions will never know.
Get Started With Bus
Go Package in 4 Steps
Configure
The bus
package requires a unique id generator to assign ids to events. You can write your own function to generate unique ids or use a package that provides unique id generation functionality. Here is a sample configuration using monoton id generator:
import (
"github.com/mustafaturan/bus"
"github.com/mustafaturan/monoton"
"github.com/mustafaturan/monoton/sequencer"
)
func init() {
// configure id generator (it doesn't have to be monoton)
node := uint(1)
initialTime := uint(0)
monoton.Configure(sequencer.NewMillisecond(), node, initialTime)
// configure bus
if err := bus.Configure(bus.Config{Next: monoton.Next}); err != nil {
panic("whoops")
}
// ...
}
Register Event Topics
To emit events to the topics, topic names need to be registered first:
func init() {
// ...
// register topics
bus.RegisterTopics("order.received", "order.fulfilled")
// ...
}
Register Event Handlers
To receive topic events you need to register handlers; A handler basically requires two values which are a Handle
function and topic Matcher
regex pattern.
handler := bus.Handler{
Handle: func(e *Event){
// fmt.Printf("Event: %+v %+v\n", e, e.Topic)
// do something
// NOTE: Highly recommended to process the event in an async way
},
Matcher: ".*", // regex pattern that matches all topics
}
bus.RegisterHandler("a unique key for the handler", &handler)
Emitting Events
The bus
package provides a generic event struct to standardize what an event is for all of its handlers. Before delivering an event, it is good to know what an event is for the bus
package. If this is your fist moments with the library, it is highly recommended to check the data structure of the Event
struct.
txID := "some-transaction-id-if-exists" // if it is blank, bus will generate one
topic := "order.received" // event topic name (must be registered before)
order := make(map[string]string) // interface{} data for event
order["orderID"] = "123456"
order["orderAmount"] = "112.20"
order["currency"] = "USD"
bus.Emit(topic, order, txID) // emit the event for the topic
Congratulations!!! You emitted your first event with the bus
package. Now, the all matched handlers will receive the same event. Please try yourself and share your experience as comment.
Also a sample project using bus
package can be found on bus-sample-project Github.
mustafaturan / bus
🔊Minimalist message bus implementation for internal communication with zero-allocation magic on Emit
🔊 Bus
Bus is a minimalist event/message bus implementation for internal communication It is heavily inspired from my event_bus package for Elixir language.
API
The method names and arities/args are stable now. No change should be expected
on the package for the version 3.x.x
except any bug fixes.
Installation
Via go packages:
go get github.com/mustafaturan/bus/v3
Usage
Configure
The package requires a unique id generator to assign ids to events. You can write your own function to generate unique ids or use a package that provides unique id generation functionality.
The bus
package respect to software design choice of the packages/projects. It
supports both singleton and dependency injection to init a bus
instance.
Hint: Check the demo project for the singleton configuration.
Here is a sample initilization using monoton
id generator:
import (
"github.com/mustafaturan/bus/v3"
"github.com/mustafaturan/monoton/v2"
"github.com/mustafaturan/monoton/v2/sequencer"
)
func NewBus() *bus.Bus {
// configure id generator (it doesn't have
…
Top comments (0)