Live data from Hacker News

How I write HTTP services in Go after 13 years

grafana.com

41–50 of 259 posts

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

#41
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…

The issue is DRY often comes to wreck this sort of thing. Some devs will see "Hmm, Username is exactly the same as just a string so let's just use a string as Username is just added complexity". I've tried it with constructs like `Data` and `ValidatedData` and it definitely works, but you do end up with duplicate fields between the two objects or worse an ever growing inheritance tree and fields unrelated to either o…

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.

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

#42
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…

This is a good design pattern, but be wary of doing validation too early. The design pattern allows you to do it as early or late as you like, but doesn't tell you when to do it. Often it's best to do it as part of parsing/validating some larger object.

See Steven Witten's "I is for Intent" [1] for some ideas about the use of unvalidated data in a UI context.

[1] https://acko.net/blog/i-is-for-intent/

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

#43
I agree with a lot of this, I'll add my own opinions:

* I would pass a waitgroup with the app context to service structs. This way the interrupt can trigger the app shutdown via the context and the main goroutine can wait on the waitgroup before actually killing the app.

* If writing a CLI program, then testing stdout, stdin, stderr, args, env, etc. is useful. But for an http server, this is less true. I would pass structured config to the run function to let those tests be more focused.

* I disagree with parsing templates using sync.Once in a handler because I don't think handlers should do template parsing at all. I would do this when the app starts: if the template cannot be parsed, the app should not become ready to receive any requests and should rather exit with a non-zero exit code.

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

#44
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…

[deleted]

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

#45
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…

I’ve found it hard to apply this pattern in Go since, if Username is embedded in a struct, and you forget to set it, you’ll get Username’s zero value, which may violate your constraints.

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

#46
post #24

> func NewServer(... config *Config ...) http.Handler one of my biggest pet peeves is when people take a Config object, which represents the configuration of an entire system, and pass it around mutably. When you do that, you're coupling everything together through the config object. I've worked on systems where you had to configure the parts in a specific order in order for things to work, because someone decided to…

I've tended to create a Config struct for each package and then a configs.Config struct that's just made up of each package's Config. It might not be a Go best practice but I like that I can setup the entire system's configuration on startup as one entity but then I only pass in the minimally required dependencies for each package. It also makes testing a little easier because I don't have to fake out the entire configuration for testing one package.

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

#47
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…

AKA 'Value Object' from DDD or a similar 'Quantity' accounting pattern. Another angle is that this fixes 'Primitive Obsession' code smell.

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

#48

Earlier quoted context omitted.

The issue is DRY often comes to wreck this sort of thing. Some devs will see "Hmm, Username is exactly the same as just a string so let's just use a string as Username is just added complexity". I've tried it with constructs like `Data` and `ValidatedData` and it definitely works, but you do end up with duplicate fields between the two objects or worse an ever growing inheritance tree and fields unrelated to either o…

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 thing to some devs.

Post reply on HN