Mutate your locked state inside a closure

· 7 min

When multiple goroutines need to read and write the same value, you need a mutex to make sure they don’t step on each other. Without one, concurrent writes can corrupt the state - two goroutines might read the same value, both modify it, and one silently overwrites the other’s change. The usual approach is to put a sync.Mutex next to the fields it protects:

var (
    mu      sync.Mutex
    counter int
)

mu.Lock()
counter++
mu.Unlock()

This works, but nothing enforces it. The compiler won’t stop you from accessing counter without holding the lock. Forget to lock in one spot and you have a data race. One way to make this safer is to bundle the value and its mutex into a small generic wrapper that only exposes locked access through methods:

Your Go tests probably don't need a mocking library

· 16 min

There are frameworks that generate those kind of fakes, and one of them is called GoMock… they’re fine, but I find that on balance, the handwritten fakes tend to be easier to reason about and clearer to sort of see what’s going on. But I’m not an enterprise Go programmer. Maybe people do need that, so I don’t know, but that’s my advice.

– Andrew Gerrand, Testing Techniques (46:44)

No shade against mocking libraries like gomock or mockery. I use them all the time, both at work and outside. But one thing I’ve noticed is that generating mocks often leads to poorly designed tests and increases onboarding time for a codebase.

Revisiting interface segregation in Go

· 6 min

Object-oriented (OO) patterns get a lot of flak in the Go community, and often for good reason.

Still, I’ve found that principles like SOLID, despite their OO origin, can be useful guides when thinking about design in Go.

Recently, while chatting with a few colleagues new to Go, I noticed that some of them had spontaneously rediscovered the Interface Segregation Principle (the “I” in SOLID) without even realizing it. The benefits were obvious, but without a shared vocabulary, it was harder to talk about and generalize the idea.

Avoiding collisions in Go context keys

· 8 min

Along with propagating deadlines and cancellation signals, Go’s context package can also carry request-scoped values across API boundaries and processes.

There are only two public API constructs associated with context values:

func WithValue(parent Context, key, val any) Context
func (c Context) Value(key any) any

WithValue can take any comparable value as both the key and the value. The key defines how the stored value is identified, and the value can be any data you want to pass through the call chain.

Let the domain guide your application structure

· 6 min

I like to make the distinction between application structure and architecture. Structure is how you organize the directories and packages in your app while architecture is how different components talk to each other. The way your app talks to other services in a fleet can also be called architecture.

While structure often influences architecture and vice versa, this distinction is important. This post is strictly about application structure and not library structure. Library structure is often driven by different design pressures than their app counterparts. There are a ton of canonical examples of good library structure in the stdlib, but it’s app structure where things get a bit more muddy.

Lifecycle management in Go tests

· 8 min

Unlike pytest or JUnit, Go’s standard testing framework doesn’t give you as many knobs for tuning the lifecycle of your tests.

By lifecycle I mean the usual setup and teardown hooks or fixtures that are common in other languages. I think this is a good thing because you don’t need to pick up many different framework-specific workflows for something so fundamental.

Go gives you enough hooks to handle this with less ceremony. But it can still be tricky to figure out the right conventions for setup and teardown that don’t look odd to other Gophers, especially if you haven’t written Go for a while. This text explores some common ways to do lifecycle management in your Go tests.

You probably don't need a DI framework

· 8 min

When working with Go in an industrial programming context, I feel like dependency injection (DI) often gets a bad rep because of DI frameworks. But DI as a technique is quite useful. It just tends to get explained with too many OO jargons and triggers PTSD among those who came to Go to escape GoF theology.

Dependency Injection is a 25-dollar term for a 5-cent concept.

– James Shore

DI basically means passing values into a constructor instead of creating them inside it. That’s really it. Observe:

Deferred teardown closure in Go testing

· 6 min

While watching Mitchell Hashimoto’s Advanced Testing with Go talk, I came across this neat technique for deferring teardown to the caller. Let’s say you have a helper function in a test that needs to perform some cleanup afterward.

You can’t run the teardown inside the helper itself because the test still needs the setup. For example, in the following case, the helper runs its teardown immediately:

func TestFoo(t *testing.T) {
    helper(t)

    // Test logic here: resources may already be cleaned up!
}

func helper(t *testing.T) {
    t.Helper()

    // Setup code here.

    // Teardown code here.
    defer func() {
        // Clean up something.
    }()
}

When helper is called, it defers its teardown - which executes at the end of the helper function, not the test. But the test logic still depends on whatever the helper set up. So this approach doesn’t work.

Stacked middleware vs embedded delegation in Go

· 5 min

Middleware is usually the go-to pattern in Go HTTP servers for tweaking request behavior. Typically, you wrap your base handler with layers of middleware - one might log every request, while another intercepts specific routes like /special to serve a custom response.

However, I often find the indirections introduced by this pattern a bit hard to read and debug. I recently came across the embedded delegation pattern while browsing Gin’s HTTP router source code. Here, I explore both patterns and explain why I usually start with delegation whenever I need to modify HTTP requests in my Go services.

Function types and single-method interfaces in Go

· 6 min

People love single-method interfaces (SMIs) in Go. They’re simple to implement and easy to reason about. The standard library is packed with SMIs like io.Reader, io.Writer, io.Closer, io.Seeker, and more.

One cool thing about SMIs is that you don’t always need to create a full-blown struct with a method to satisfy the interface. You can define a function type, attach the interface method to it, and use it right away. This approach works well when there’s no state to maintain, so the extra struct becomes unnecessary. However, I find the syntax for this a bit abstruse. So, I’m jotting down a few examples here to reference later.