Live data from Hacker News

How I write HTTP services in Go after 13 years

grafana.com

151–160 of 259 posts

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

#151

Earlier quoted context omitted.

I've also seen it called primitive obsession, which is also applicable to other primitive types like using an integer in situations where an enum would be better.

Definitely use to fall for primitive obsession. It seemed so silly to wrap objects in an intermediary type. After playing with Rust, I changed my tune. The type system just forces you into the correct path, that a lot of code became boring because you no longer had to second guess what-if scenarios.

> Definitely use to fall for primitive obsession. It seemed so silly to wrap objects in an intermediary type.

A lot of languages certainly don't make it easy. You shouldn't have to make a Username struct/class with a string field to have a typed username. You should be able to declare a type Username which is just a string under the hood, but with different associated functions.

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

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

Related:

Parse, don't validate (2019) - https://news.ycombinator.com/item?id=35053118 - March 2023 (219 comments)

Parse, Don't Validate (2019) - https://news.ycombinator.com/item?id=27639890 - June 2021 (270 comments)

Parse, Don’t Validate - https://news.ycombinator.com/item?id=21476261 - Nov 2019 (230 comments)

Parse, Don't Validate - https://news.ycombinator.com/item?id=21471753 - Nov 2019 (4 comments)

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

#153
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 variation on one of my favorite software design principles: Make illegal states unrepresentable. I first learned about it through Scott Wlaschin[1].

[1]: https://fsharpforfunandprofit.com/posts/designing-with-types...

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

#154
post #121

Earlier quoted context omitted.

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

I don't agree that this example is more "programmer friendly". Anything you want to do with the username other than null check and passing an argument is going to be based directly on the string representation. Insert into a database? String. Display in a UI? String. Compare? String comparison. Sort? String sort. Is it really more "programmer friendly" to create wrapper types for individual strings all over your codebase that need to have passthrough methods for all the common string methods? One could argue that it's worth the tradeoff but this C2 definition is far from helpful in setting a clear boundary.

Meanwhile the real world usages of this term I've seen in the past have all been things like enums as strings, lists as strings, numbers as strings, etc... Not arbitrary textual inputs from the user.

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

#155

Earlier quoted context omitted.

I've also seen it called primitive obsession, which is also applicable to other primitive types like using an integer in situations where an enum would be better.

Definitely use to fall for primitive obsession. It seemed so silly to wrap objects in an intermediary type. After playing with Rust, I changed my tune. The type system just forces you into the correct path, that a lot of code became boring because you no longer had to second guess what-if scenarios.

Yeah, modern type systems are game changers. I've soured on Rust, but if Go had the full Ocaml type system with match statements I think it would be the perfect language.

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

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

So far I like the commonly used approach in the Typescript community best:

1. Create your Schema using https://zod.dev or https://github.com/sinclairzx81/typebox or one of the other many libs.

2. Generate your types from the schema. It's very simple to create partial or composite types, e.g. UpdateModel, InsertModels, Arrays of them, etc.

3. Most modern Frameworks have first class support for validation, like Fastify (with typebox). Just reuse your schema definition.

That is very easy, obvious and effective.

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

#157
post #154

Earlier quoted context omitted.

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

I don't agree that this example is more "programmer friendly". Anything you want to do with the username other than null check and passing an argument is going to be based directly on the string representation. Insert into a database? String. Display in a UI? String. Compare? String comparison. Sort? String sort. Is it really more "programmer friendly" to create wrapper types for individual strings all over your code…

You inherit some code. Is that string a username or a phone number? Who knows. Someone accidentally swapped two parameter values. Now the phone number is a username and you’ve got a headache of trying to figure out what’s wrong.

By having stronger types this won’t come up as a problem. You don’t have to rely on having the best programmers in the world that never make mistakes (tm) to be on your team and instead rely on the computer making guard rails for you so you can’t screw up minor things like that.

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

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

You can use Dependency Injection to solve this issue but in my view the added complexity is not really worth it.

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

#159

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…

I've always have my handlers individually set as a struct each with a method to handle the route/request. type CreateUser struct { store storage.Store cache caching.Cache logger logging.Logger pub events.Publisher // etc } func (op CreateUser) ServeHTTP(ctx, req, rw) {} // or if you have custom handlers func (op CreateUser) ServeHTTP(ctx, input) (output, error) {} And in my main.go, or where I set up my dependencies,…

This is kinda missing the point; each handler needs a lot of deps to do it's job, and the most obvious place to put them is in the parameters of the function. That is what I want. I do not want more indirection for aesthetics; I want clarity, even if it's brutal clarity.

Whether all the deps are in the method receiver (the parent struct) or in a struct that's a param; it's all just more indirection to hide all the "stuff" that we need cause we think it's ugly. I dream of a world where we don't do that.

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

#160

Earlier quoted context omitted.

It's not guaranteed at all, that's where go's zero-values come in. E.g. nested structs, un/marshaljson magic methods etc. How do you deal with that?

C++ constructors actually make the guarantee, but it comes with other pains

Lots of languages handle it just fine and don’t need the mess of C++ ctors.

GP is pointing out that go specifically makes it an issue.

Post reply on HN