# Go Is the Right Choice for Network Tools. Here Is the Proof

> Six months into Go, I rewrote a network scanner and built a custom event bus. The language is simpler than expected, with one big limitation on IBM i.

- Author: Brian Kimball
- Published: 2024-01-20
- Tags: development, golang
- Canonical: https://brian-kimball.com/blog/learning-go-pt2/
- Markdown: https://brian-kimball.com/blog/learning-go-pt2/index.md

## Six Months In: Lessons Learned

I really like Go. It's easy to pick up, the standard library is comprehensive, and the syntax is intentionally simple. I've been rewriting my network-mapper app, and Go is the right choice for the server.

Unfortunately, Go (and Rust) does not run natively on IBM i. Despite the AIX-based PASE environment, issues with `mmap` and the IFS prevent execution (see this [GitHub issue](https://github.com/golang/go/issues/45017)). For now, I'll use Go in Linux environments and stick to Node or Python on IBM i.

### The App: Gatherer

I renamed the app to **Gatherer**. It's designed for quick network and MSP client evaluations.

**Architecture**

- **Server:** [Echo framework](https://echo.labstack.com/) (Go).
- **Background Tasks:** Uses tools like `nmap` and `whois`.
- **Real-time:** Event-driven architecture with WebSockets.
- **Database:** GORM (ORM) for easy filtering.
- **Pub/Sub:** Custom implementation using Go channels.

**Frontend**

I initially tried Solid.js but reverted to React due to ecosystem maturity and IDE support.

- **Stack:** React, React Query, React Router, React Hook Form.
- **UI:** [react-aria-components](https://react-spectrum.adobe.com/react-aria/index.html) + [daisyUI](https://daisyui.com/). This combination offers accessible components with clean CSS.

### Event Bus Implementation

Here is the custom Event Bus I implemented, inspired by the Revel framework. It supports separate "rooms" for broadcasting and subscribing.

```go
package events

import (
	"container/list"
	"time"
)

var rooms = make(map[string]Room)

type Room struct {
	Name        string `json:"name"`
	subscribe   chan (chan<- Subscription)
	unsubscribe chan (<-chan Event)
	publish     chan Event
	Subscription
}

func NewRoom(name string) Room {
	r, ok := rooms[name]

	if ok {
		return r
	}

	var (
		subscribe   = make(chan (chan<- Subscription), archiveSize)
		unsubscribe = make(chan (<-chan Event), archiveSize)
		publish     = make(chan Event, archiveSize)
	)
	room := Room{Name: name, subscribe: subscribe, unsubscribe: unsubscribe, publish: publish}
	room.init()
	rooms[name] = room
	return rooms[name]
}

func (r *Room) drain(ch <-chan Event) {
	for {
		select {
		case _, ok := <-ch:
			if !ok {
				return
			}
		default:
			return
		}
	}
}

func (r *Room) Cancel() {
	r.unsubscribe <- r.Subscription.New
	r.drain(r.Subscription.New)
}

func (r *Room) Broadcast(typ string, data interface{}) {
	r.publish <- Event{Type: typ, User: "go", Timestamp: int(time.Now().Unix()), Data: data}
}

func (r *Room) Subscribe() Subscription {
	resp := make(chan Subscription)
	r.subscribe <- resp
	return <-resp
}

func (r *Room) work() {
	archive := list.New()
	subscribers := list.New()

	for {
		select {
		case ch := <-r.subscribe:
			var events []Event
			for e := archive.Front(); e != nil; e = e.Next() {
				events = append(events, e.Value.(Event))
			}
			subscriber := make(chan Event, archiveSize)
			subscribers.PushBack(subscriber)
			ch <- Subscription{events, subscriber}

		case event := <-r.publish:
			for ch := subscribers.Front(); ch != nil; ch = ch.Next() {
				ch.Value.(chan Event) <- event
			}
			if archive.Len() >= archiveSize {
				archive.Remove(archive.Front())
			}
			archive.PushBack(event)

		case unsub := <-r.unsubscribe:
			for ch := subscribers.Front(); ch != nil; ch = ch.Next() {
				if ch.Value.(chan Event) == unsub {
					subscribers.Remove(ch)
					break
				}
			}
		}
	}
}

func (r *Room) init() {
	go r.work()
}
```

The event bus works well enough for my use case, though I would reach for a proper message queue like NATS if the traffic grew significantly. If you spot a bug or have a cleaner pattern for Go channel-based pub/sub, I am interested.
