DEV Community

Mustafa Turan
Mustafa Turan

Posted on • Edited on • Originally published at dev.to

Decoupled Package Communication in Go

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.

event bus

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")
    }
    // ...
}
Enter fullscreen mode Exit fullscreen mode

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")
    // ...
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.


GitHub logo mustafaturan / bus

🔊Minimalist message bus implementation for internal communication with zero-allocation magic on Emit

🔊 Bus

GoDoc Build Status Coverage Status Go Report Card GitHub license

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
Enter fullscreen mode Exit fullscreen mode

Top comments (0)