Live data from Hacker News

How I write HTTP services in Go after 13 years

grafana.com

121–130 of 259 posts

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

#121

Earlier quoted context omitted.

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"

This term is typically used to refer to things like data structures and numerical values all being passed as strings. I don't think a reasonable person would consider storing a username in a string to be "stringly typed".

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

#123
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/

I read through that piece and strongly disagree with the premise that their insight is somehow at odds with leaning into the type system for correctness.

The legitimate insight that they have is that anchoring the state as close as possible to the user input is valuable—I think that that is a great insight with a lot of good applications.

However, there's nothing that says you can't take that user-centric state and put it in a strongly typed data structure as soon as possible, with a set of clearly defined and well-typed transitions mapping the user-centric state to the derived states.

Edit: looks like there was discussion on this the other day, with a number of people making similar observations—https://news.ycombinator.com/item?id=39269886

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

#124
post #95
post #45

Earlier quoted context omitted.

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.

Why? You can easily call NewUsername inside NewAccount for example, just return the error. Or did I misunderstood?

Because go doesn’t have exhaustiveness checking when initialising structs. Instead it encourages “make the zero value meaningful” which is not always possible nor desirable. I usually use a linter to catch this kind of problem https://github.com/GaijinEntertainment/go-exhaustruct

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

#125

Earlier quoted context omitted.

> Initialize your struct fields and you won't have to consider their zero state. “Just do the right thing everywhere and you don’t have to worry!” You can’t stop consumers of your libraries from creating zero-valued instances.

Then the zero value is their problem, not yours. You have no reason to be worried about that any more than you are worried about them not getting enough sleep, or eating unhealthy food. What are you doing to stop them from doing that? Nothing, of course. Not your problem. Coq exists if you really feel you need a complete type system. But there is probably good reason why almost nobody uses it.

> Then the zero value is their problem, not yours.

Except for all those times you're the consumer of someone else's library and there's no way for them to indicate that creating a zero-valued struct is a bug.

Again, it's the philosophy of "Just do the right thing everywhere and you don’t have to worry!" Sometimes it's nice to work with a type system where designers of libraries can actually prevent you from writing bugs.

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

#127
post #39

I found fx( https://github.com/uber-go/fx ) to be a super simple yet versatile tool to design my application around. All the advice in the article is still helpful, but it takes the "how do I make sure X is initialized when Y needs it" part completely out of the equation and reduces it from an N*M problem to an N problem, ie I only have to worry about how to initialize individual pieces, not about how to synchronize…

>All the advice in the article is still helpful, but it takes the "how do I make sure X is initialized when Y needs it" part completely out of the equation and reduces it from an N*M problem to an N problem, ie I only have to worry about how to initialize individual pieces, not about how to synchronize initialization between them. I gotta say, I hate these dependency injection frameworks. In a well designed system th…

If you have ever topologically sorted 100 components connected in a complex graph by hand or found the right spot to insert the 101st, you'd quickly appreciate more help than a compiler check.

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

#128
post #118
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…

But surely this is just another way of doing validation and not fundamentally "parsing"? If at the end you've just stored the input exactly as you got it, the only parsing you're potentially doing is in the validation step and then it gets thrown away.

Implementation-wise, yes, but the interface you're exposing is indistinguishable from that of a parser. For all your consumers know, you could be storing the username as a sequence of a 254-valued enum (one for each byte, except the angle brackets) and reconstructing the string on each "get" call. For more complex data you would certainly be storing it piecewise; the only reasons this example gets a pass are 1) because it is so low in surface area that a human can reasonably validate the implementation as bug-free without further aid from the type checker, and 2) because Go's type system is so inexpressive that you can't encode complex requirements with it anyway.

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

#129
post #121

Earlier quoted context omitted.

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

This term is typically used to refer to things like data structures and numerical values all being passed as strings. I don't think a reasonable person would consider storing a username in a string to be "stringly typed".

The One True Wiki[0] says "Used to describe an implementation that needlessly relies on strings when programmer & refactor friendly options are available."

Which is exactly what's going on here. A username has a string as a payload, but that payload has restrictions (not every string will do) and methods which expect a username should get a username, not any old string.

[0]: https://wiki.c2.com/?StringlyTyped

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

#130

Earlier quoted context omitted.

You can use new types with validation too. In fact the approaches seem to be duals. Parse, don't validate: string ParsedString untrusted source -------> parse --------------> rest of system Validate, don't parse: UnvalidatedString string untrusted source ------------------> validate -------> rest of system

The problem is that pattern "fails open." If anyone on the team forgets to define an untrusted string as UnvalidatedString, the data skips validation. If you default to treating primitive types as untrusted, it's hard for someone to accidentally convert an untrusted type to a trusted type without using the correct parse method.

The dual problem would be any function which forgets to accept a ParsedString instead of a string can skip parsing.

Both cases appear to depend on there being a "checkpoint" all data must go through to cross over to the rest of the system, either at parsing or at UnvalidatedString construction.

Post reply on HN