Live data from Hacker News

How I write HTTP services in Go after 13 years

grafana.com

11–20 of 259 posts

Re: How I write HTTP services in Go after 13 years

#11

What is the value making main.go as small as possible? Whose dreams come true in this scenario?

The author goes on to explain a few scenarios where the pattern is helpful. It's not to keep main.go as small as possible, it's so that you can test parts of your main.go file properly. In my experience, if all of my logic is stuffed into `func main() {}`, then I can't actually test it. If I have a helper method(like run in this case), I can test out specific scenarios and ensure the application handles it properly. Some of the examples Mat gave were handling context cancellations properly.

Re: How I write HTTP services in Go after 13 years

#12

What is the value making main.go as small as possible? Whose dreams come true in this scenario?

I've never been a fan of making main.go one line. I create the logger, parse the flags, create objects from the flags, and call Run() or something. In the tests, you aren't ever going to do those things in the same way, so there is really no point in putting them in some other file.

Re: How I write HTTP services in Go after 13 years

#14
post #8

What is the value making main.go as small as possible? Whose dreams come true in this scenario?

For bespoke internal services, I like to keep main.go as flat as reasonable, like a "script". Handlers can have their own files but the bulk of the control flow and moving parts should be apparent from reading the main file. Abstracting things away from main makes it less readable and is general pointless for bespoke services that will be deployed in exactly one configuration.

That's a nice way of putting it. When exploring a new codebase for the first time it can be very helpful to have main.go give you a high level idea about the overall structure of the program.

Re: How I write HTTP services in Go after 13 years

#15
I want to see a greater acceptance of this idea:

> My handlers used to be methods hanging off a server struct, but I no longer do this. If a handler function wants a dependency, it can bloody well ask for it as an argument. No more surprise dependencies when you’re just trying to test a single handler.

For HTTP services in any language, your handlers will usually end up with a lot of business logic, logic which probably has many dependencies. I see single handlers using all of the following on a regular basis: DB, cache, blob storage, some kind of special authz thing specific to your endpoints, maybe some fancy licensing checker, a queue or two, a specialized logger, and specialized metrics client. Many of those (metrics, request/response logging) can live in middlewares most of the time, but in every code base there will be times where you need to do something custom with one or the other. As time passes, the more I wonder "why aren't these all just function parameters?"

Yes, that would be a lot of function parameters (9+ for a single handler, before even getting into the request or custom params themselves), and we all have many rules of thumb and linter rules which try to keep us from having lots of function parameters. But it's not like we're not writing code which depends on all those dependencies, instead we're just sticking them on the "server" class/struct and pretending that because the method signature is shorter, we have fewer dependencies!

As time passes, I find myself wishing more and more for code that takes all its dependencies in the function/method signature, even if there's 20 of them; at least then we wouldn't be lying about how complex the code's getting...

Re: How I write HTTP services in Go after 13 years

#18

I want to see a greater acceptance of this idea: > My handlers used to be methods hanging off a server struct, but I no longer do this. If a handler function wants a dependency, it can bloody well ask for it as an argument. No more surprise dependencies when you’re just trying to test a single handler. For HTTP services in any language, your handlers will usually end up with a lot of business logic, logic which proba…

It doesn't have to be 9+ separate arguments, in some languages it can be a single 'context' or 'env' object that contains just what the handler needs, something like `handleHello({ db, cache, blobStore, authz }, req, res)`. That way, if two handlers use the exact same context you can reuse, but it's also easy enough to declare a per-handler context at the call site.

Re: How I write HTTP services in Go after 13 years

#19
I really like Mat Ryer's work, and I've applied most of the ideas in the 2018 version of this article to all of my Go projects since then.

The one weak spot for me is this aspect:

>NewServer is a big constructor that takes in all dependencies as arguments... In test cases that don’t need all of the dependencies, I pass in nil as a signal that it won’t be used.

This has always felt wrong to me, but I've never been able to figure out a better solution.

It means that a huge chunk of your code has a huge amount of unnecessary shared state.

I often end up writing HTTP handlers that only need access to a tiny amount of the shared state. Like the HTTP handler needs to check if the requesting user has access to a resource, and then it needs to call one function on the datastore.

I'd love to write tests where I only mock out those two methods, but I can't write simple tests because the handler is part of this giant glob where it has access to all of the datastore and every object the parent server has access to because it's all one giant object.

Nothing against Mat Ryer, as his pattern is the best I've found, but I still feel like there's some better solution out there.

Re: How I write HTTP services in Go after 13 years

#20

is there a git repo with example code?

Not OP, but I design my Go projects with a very similar pattern that I learned from OP's 2018 post.

I think this is a pretty good example of a real-world implementation:

https://github.com/mtlynch/picoshare

Particularly these files:

https://github.com/mtlynch/picoshare/blob/2cd9979dab084ca781...

https://github.com/mtlynch/picoshare/blob/2cd9979dab084ca781...

Post reply on HN