Live data from Hacker News

How I write HTTP services in Go after 13 years

grafana.com

81–90 of 259 posts

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

#81

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"

Bash :(

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

#82

Earlier quoted context omitted.

I mean, you may end up just wanting something like, type UsernameError struct { name string reason string } func (e *UsernameError) Error() string { return fmt.Errorf("invalid username %q: %s", e.name, e.reason) } And reason can be "username cannot be empty" or "username may not contain ' This is fine for lots of different cases, because it’s likely that your code wants to know how to handle “username is invalid”, bu…

I write mostly frontends. Sometimes the APIs I talk to give back beautiful English error messages - that I can't just show to the user, because they are using a different language most of the time. And I don't want to write logic that depends on that sentence, far too brittle.

Right—I think the “error code” here is going to be the error type, i.e., UsernameError, or some qualified version of that.

It’s not perfect, but software evolves through many imperfect stages as it gets better, and this is one such imperfect stage that your software may evolve through.

Including a human-readable version of the error is useful because the developers / operators will want to read through the logs for it. Sometimes that is where you stop, because not all errors from all backends will need to be localized.

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

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

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?

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

#84
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 that a huge chunk of your code has a huge amount of unnecessary shared state.

Can you explain that a little more?

Which chunk of code has what shared state, and why is it unnecessary?

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

#85
post #72

Earlier quoted context omitted.

One of the major issues with a lot of the outdated concepts in programming is that we still teach them to young people. I work a side gig as an external examiner for CS students. Especially in the early years they are taught the same OOP content that I was taught some decades ago, stuff that I haven’t used (also) for some decades. Because while a lot of the concepts may work well in theory, they never work out in a w…

> It’s almost always better to repeat code. God no. Stop the copy pasta disease! It's horrible, mindless programming. When reviewing code, I'm astonished anything was accomplished by copy pasting so much old code (complete with bugs and comment typos). Incidentally, OOP encourages you to copy a lot. It's just an engine for generating code bloat. Want to serialize some objects? Here's your Object serializer and your o…

Countless man-centuries have been lost looking for the perfect abstraction to cover two (or an imagined future with two) cases which look deceptively similar, then teasing them apart again.

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

#86
post #72

Earlier quoted context omitted.

One of the major issues with a lot of the outdated concepts in programming is that we still teach them to young people. I work a side gig as an external examiner for CS students. Especially in the early years they are taught the same OOP content that I was taught some decades ago, stuff that I haven’t used (also) for some decades. Because while a lot of the concepts may work well in theory, they never work out in a w…

> It’s almost always better to repeat code. God no. Stop the copy pasta disease! It's horrible, mindless programming. When reviewing code, I'm astonished anything was accomplished by copy pasting so much old code (complete with bugs and comment typos). Incidentally, OOP encourages you to copy a lot. It's just an engine for generating code bloat. Want to serialize some objects? Here's your Object serializer and your o…

OOP and Dry are compatible! I’ve actually done the thing that the above commenter suggests - create a base object with created on/by so that I never have to think about it. Whether or not you actually care about that, if you implement a descended of that object you’re going to get some stuff for free, and you’re gonna like it!

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

#87
post #38

Earlier quoted context omitted.

It would need a getter here. Probably good to keep it immutable, if you want guarantees that it will never be changed to something that violates the username rules.

> need a getter Yeah, thats what I figured. Im not sure if I want the tradeoff of calling .GetValue in multiple places just to save calling validate in maybe 2 or 3 places. Not to mention I cant easily marshal/unmarshal into it and next week valid username is a username that doesnt already exist in the database. Maybe this approach appeals to people and Im hesitant to say “that’s not how Go is supposed to be written”…

> Yeah, thats what I figured. Im not sure if I want the tradeoff of calling .GetValue in multiple places just to save calling validate in maybe 2 or 3 places.

The tradeoff is not that you save calling validate, it’s that you avoid forgetting to call validate in the first place, because when you forget to validate, you get a type error.

IMO it’s a little more clear this way:

    type Ticket struct {
      requestor Username
      assignee  Username
    }
It lets you write code that is little more obvious.

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

#88
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 that a huge chunk of your code has a huge amount of unnecessary shared state. Can you explain that a little more? Which chunk of code has what shared state, and why is it unnecessary?

Basically, not all the handlers will use every dependency the server (which is the entire program in this pattern) has. Not every handler will use a database, for example.

While I may prefer a struct for this instead of separate arguments, I do agree it's useful to capture "the world" as the set of all dependencies, even if some handlers don't use them (yet).

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

#89

Earlier quoted context omitted.

> need a getter Yeah, thats what I figured. Im not sure if I want the tradeoff of calling .GetValue in multiple places just to save calling validate in maybe 2 or 3 places. Not to mention I cant easily marshal/unmarshal into it and next week valid username is a username that doesnt already exist in the database. Maybe this approach appeals to people and Im hesitant to say “that’s not how Go is supposed to be written”…

> Yeah, thats what I figured. Im not sure if I want the tradeoff of calling .GetValue in multiple places just to save calling validate in maybe 2 or 3 places. The tradeoff is not that you save calling validate, it’s that you avoid forgetting to call validate in the first place, because when you forget to validate, you get a type error. IMO it’s a little more clear this way: type Ticket struct { requestor Username ass…

I’m not sure I understand. In your example you’ve grouped related data in a struct and validating that it matches your system’s invariants, that feels good to me.

The original example was more “wrap a simple type in an object so it’s always validated when set” which looks beautiful when you don’t have the needed getters in the example nor show all the Get call sites opposed to the 1 or 2 New call sites. All in the name of “we don’t want to set the username without validation” but without private constructors Username{“invalid”} can be invoked, the validation circumvented and I’m not convinced the overhead we paid was worth it.

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

#90
post #23

I've recently been playing with ogen: https://github.com/ogen-go/ogen Write openapi definition, it'll do routing, definition of structs, validation of JSON schemas, etc. All I need to do is implement the service. Validating an integer range for a querystring parameter is just too boring. And too easy to mistype when writing it manually. Anyways, so far only been playing, so haven't found the bad parts yet.

Or, if you're more into publishing an Openapi spec from your Go code, I do like danielgtaylor/huma[1] and swaggest/rest[2].

[1] https://github.com/danielgtaylor/huma

[2] https://github.com/swaggest/rest

Post reply on HN