Is passing user ID through context an antipattern?

Paweł Grzybek reached out after reading What belongs in Go’s context values? with a question about their auth middleware and the handler that reads the user ID from context: I validate the session in middleware, and the session record in the DB holds the user ID, which I put in the context for handlers to use later. According to your post, this is an antipattern because the handler can’t work without that value. But if I don’t use context here, I’d have to hit the sessions table again in the handler. Is this actually wrong, and if so, how do I avoid the double DB lookup? ...

March 18, 2026

What belongs in Go's context values?

Another common question popped up in r/golang: I’ve been reading mixed opinions lately about using context to pass values like request IDs, auth info, or tenant IDs through middleware layers. Some people argue it’s fine and exactly what context was extended for after 1.7. Others say it’s a code smell that leads to hidden dependencies and untestable code. I see both sides. On one hand it keeps function signatures clean. On the other hand you lose compile-time safety and it’s not obvious what a function needs from ctx. ...

March 17, 2026

Wrapping a gRPC client in Go

Yesterday I wrote a shard on exploring the etcd codebase. One of the things that stood out was how the clientv3 package abstracts out the underlying gRPC machinery. etcd is a distributed key-value store where the server and client communicate over gRPC. But if you’ve only ever used clientv3 and never peeked into the internals, you wouldn’t know that. You call resp, err := client.Put(ctx, "key", "value") and get back a *PutResponse. It feels like a regular Go library. The fact that gRPC and protobuf are involved is an implementation detail that the client wrapper keeps away from you. ...

March 15, 2026

Mutate your locked state inside a closure

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: ...

March 5, 2026

Your Go tests probably don't need a mocking library

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. ...

January 23, 2026

Revisiting interface segregation in Go

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. ...

November 1, 2025

Avoiding collisions in Go context keys

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. ...

October 22, 2025

Lifecycle management in Go tests

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. ...

August 30, 2025

You probably don't need a DI framework

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: ...

May 24, 2025

Deferred teardown closure in Go testing

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. ...

March 28, 2025

Stacked middleware vs embedded delegation in Go

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. ...

March 6, 2025

Function types and single-method interfaces in Go

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. ...

December 22, 2024

Shades of testing HTTP requests in Python

Here’s a Python snippet that makes an HTTP POST request: # script.py import httpx from typing import Any async def make_request(url: str) -> dict[str, Any]: headers = {"Content-Type": "application/json"} async with httpx.AsyncClient(headers=headers) as client: response = await client.post( url, json={"key_1": "value_1", "key_2": "value_2"}, ) return response.json() The function make_request makes an async HTTP request with the HTTPx library. Running this with asyncio.run(make_request("https://httpbin.org/post")) gives us the following output: { "args": {}, "data": "{\"key_1\": \"value_1\", \"key_2\": \"value_2\"}", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "40", "Content-Type": "application/json", "Host": "httpbin.org", "User-Agent": "python-httpx/0.27.2", "X-Amzn-Trace-Id": "Root=1-66d5f7b0-2ed0ddc57241f0960f28bc91" }, "json": { "key_1": "value_1", "key_2": "value_2" }, "origin": "95.90.238.240", "url": "https://httpbin.org/post" } We’re only interested in the json field and want to assert in our test that making the HTTP call returns the expected values. ...

September 2, 2024

Log context propagation in Python ASGI apps

Let’s say you have a web app that emits log messages from different layers. Your log shipper collects and sends these messages to a destination like Datadog where you can query them. One common requirement is to tag the log messages with some common attributes, which you can use later to query them. In distributed tracing, this tagging is usually known as context propagation, where you’re attaching some contextual information to your log messages that you can use later for query purposes. However, if you have to collect the context at each layer of your application and pass it manually to the downstream ones, that’d make the whole process quite painful. ...

August 6, 2024

Protobuffed contracts

People typically associate Google’s Protocol Buffer with gRPC services, and rightfully so. But things often get confusing when discussing protobufs because the term can mean different things: A binary protocol for efficiently serializing structured data. A language used to specify how this data should be structured. In gRPC services, you usually use both: the protobuf language in proto files defines the service interface, and then the clients use the same proto files to communicate with the services. ...

May 10, 2024