Mat Ryer Profile picture
Oct 4, 2021 13 tweets 4 min read
How I write HTTP services in #golang has changed over the years... here's my current style.

(Please consider sharing this with somebody you know who's learning Go.)

It's a yarn... 🧶

I make a server type that holds the dependencies.

all routing goes in routes.go (makes it easy to find stuff, given a URL)

The handlers are methods on that server type. They can access the dependencies via the receiver.

My methods aren't actually http.Handler or http.HandlerFunc types, but they do return one when called.

You can also have handler-specific arguments with all the delicious type safety we get with Go.

It also means you can't get this handler without providing some values for the arguments.

I tend to use http.HandlerFunc a lot more than http.Handler, because I find the anonymous functions to be so useful.

I often want to run code before/after my handlers (like for auth, instrumentation, or other common api features.)

If I'm not using the request/response payload types anywhere else in the case, I put them inside the methods to keep them out of the way...

... this also means I will sometimes have different types in test code.

I mostly use integration tests for these handlers, so am rarely ever tempted to move the request/response types into package space.

I use sync.Once to do handler-specific initialisation in a concurrent safe way.

(The pros say we should measure before we optimise, so be sure to check if this is worth doing in your particular case.)

Since our server type is just a struct, and our handlers are just methods - it makes code quite easy to test.

Follow for more classic #golang content, if you love content and who doesn't love content? #content


• • •

Missing some Tweet in this thread? You can try to force a refresh

Keep Current with Mat Ryer

Mat Ryer Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!


Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @matryer

Oct 5, 2020
I agree with @ScribblingOn

It's time to get rid of estimates in software engineering. They don't work, do more harm than good, and drive people to act in dishonest ways.

Buckle up people, it's a thread.
"How long is this going to take?" feels like a completely reasonable question, but how come "I don't know" isn't a
reasonable answer?

"We just need a ballpark figure, you won't be held to it," they lie.

Well how come we just had to work the weekend?
Estimates are an effort to try and predict _when_ something is going to be ready. This is necessary so the rest of the business (sales, other dev teams, cult leaders, etc.) can line up and coordinate their efforts.

The reason for estimates is entirely respectable.
Read 19 tweets
Sep 15, 2020
I always call into a run function from main in #golang passing in any system dependencies as arguments.

If I am going to parse flags, use arguments, and write to stdout, my run func looks like this:

func run(args []string, stdout io.Writer) error…

This means I can write normal Go tests that can call the run function, and there's no global state to mess around with.

func Test(t *testing.T) {
args := []string{"tool", "--debug=true", "./testdata"}
var stdout bytes.Buffer
err := run(args, stdout)

This is nice because I can use any `io.Writer` as the `stdout`. In my tests, I use bytes.Buffer - which allows me to capture (and make assertions about) what the tool outputs.

I can also play around with any flags or arguments without having to do anything weird :)

Read 6 tweets
Sep 13, 2020
A bit more context on how we organise our #golang services at Pace.
Oto has a very simple definition format:

type Service interface {
Method(Request) Response

Which turns into:

func (s *Service) Method(context.Context, Request) (*Response, error) {
// todo: implementation
The pointer return is simply because I like returning nil in the error case:

return nil, errors.New("not today mate")
Read 8 tweets
Apr 30, 2020
This is the best PR I've ever opened. Someone has a PDF of my book in their GitHub repo. Image
UPDATE: the PR got merged 😂🥳

To celebrate I am going to BUY five copies of my own book (Go Programming Blueprints: Second Edition) for anyone who wants, it but can’t get it themselves.

Reply if interested and I’ll abuse a Go map to randomly pick five winners. Image
@goinggodotnet @ellenkorbes remember when we did this to pick lightning speakers? 😂
Read 4 tweets
Mar 20, 2020
#golang tip

You can use documented consts for simple values (like bools) to make code much easier to read and understand AT A GLANCE.

I wouldn't do this _all_ the time, but when I notice code is tricky to read, I do it.

Example follows... 1/4
Consider this:

Greet("Mat", true)

I'd have to look up the method signature to figure out what that 'true' was for.

If we had these consts:

const (
// AnimationOn will greet with a flourish.
AnimationOn = true

// AnimationOff will greet without animations.
AnimationOff = false

Glanceability goes up:

Greet("Mat", AnimationOn)

Read 5 tweets

Did Thread Reader help you today?

Support us! We are indie developers!

This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!


0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy


3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us on Twitter!