Live data from Hacker News

How I write HTTP services in Go after 13 years

grafana.com

51–60 of 259 posts

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

#54
post #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...

out of curiosity, why no sort-of-established pkg and internal dirs? What do you think of https://github.com/photoprism/photoprism structure?

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

#55
post #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 ab…

It means the object created by NewServer is dealing with too much. Probably has too many data types coupled to it and too much behavior. Simple example is adding a logger. If you add it as a dependency to the constructor, the object starts doing a bit more than initial simple implementation. It's fine to do it, but shame to not figure out how to log without editing the implementation of a simple thing. Higher order f…

I agree that too many arguments to the constructor may have the smell of too much coupling.

But if I really feel I can't avoid the need to pass a good amount of external context, I create a dedicated "options" struct and pass that into the constructor as a pointer. The purpose of the pointer (rather than pass by value) is if I want default arguments, I can pass nil.

    type ServerOptions struct {
        logger    *magic.Logger
        secretKey string
    }

    func NewServer(options *ServerOptions) (*Server, error) {
        ...
    }

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

#56

Earlier quoted context omitted.

Except Username is not exactly the same as string, and that's important. Username is a subset of string. If they were equivalent, we wouldn't need to parse/validate. The often misinterpreted part of DRY is conflating "these are the same words, so they are the same", with "these are the same concept, so they are the same". A Username and a String are conceptually different.

DRY is just "Do not repeat yourself". And a LOT of devs take that literally. It's not "Do not repeat concepts" (which is what it SHOULD be but DRC isn't a fun acronym). Unfortunately "This is the same character string" is all a DRY purist needs to start messing up the code base. I honestly believe that "DRY" is an anti-pattern because of how often I see this exact behavior trotted out or espoused. It's a cargo cult t…

That's why I like to tell people to always remember to stay MOIST - the Most Optimal is Implicitly the Simplest Thing.

When you add complexity to DRY out your code, you're adding a readability regression. DRY matters in very few context beyond readability, and simplicity and low cognitive load need to be in charge. Everything else you do code-style wise should be in service of those two things.

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

#57
post #25

> The Valid method takes a context (which is optional but has been useful for me in the past) and returns a map. If there is a problem with a field, its name is used as the key, and a human-readable explanation of the issue is set as the value. I used to do this, but ever since reading Lexi Lambda's "Parse, Don't Validate," [0] I've found validators to be much more error-prone than leveraging Go's built-in type check…

Crazy that actually using your type system leads to better code. Stop passing everything around as `string`. Parse them, and type them.

There's a name for this anti-pattern: "Stringly typed"

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

#58
post #20

Earlier quoted context omitted.

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

out of curiosity, why no sort-of-established pkg and internal dirs? What do you think of https://github.com/photoprism/photoprism structure?

I'm not familiar with that package structure, unfortunately. It might be good, but I'm not sure what the reasons are for structuring the project that way.

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

#59

Earlier quoted context omitted.

Except Username is not exactly the same as string, and that's important. Username is a subset of string. If they were equivalent, we wouldn't need to parse/validate. The often misinterpreted part of DRY is conflating "these are the same words, so they are the same", with "these are the same concept, so they are the same". A Username and a String are conceptually different.

DRY is just "Do not repeat yourself". And a LOT of devs take that literally. It's not "Do not repeat concepts" (which is what it SHOULD be but DRC isn't a fun acronym). Unfortunately "This is the same character string" is all a DRY purist needs to start messing up the code base. I honestly believe that "DRY" is an anti-pattern because of how often I see this exact behavior trotted out or espoused. It's a cargo cult t…

This seems less about DRY and more a story about a hypothetical junior dev making a dumb mistake masquerading as commentary about “DRY purism”.

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

#60
The encode example contains a bug and a lint issue. Firstly, calling w.Header().Set after w.WriteHeader is likely a bug, as the w.WriteHeader method call should occur after setting the headers.

The second issue involves passing an unused *http.Request, which will likely cause the linter to flag it.

Post reply on HN