Live data from Hacker News

How I write HTTP services in Go after 13 years

grafana.com

71–80 of 259 posts

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

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

My favorite way to prevent this is to make the config truly immutable, but still configurable with something like this:

  package config

  type options struct {
    name string
  }

  type Option func(o *options)

  func Name(name string) Option {
    return func(o *options) {
      o.name = name
    }
  }

  type Config struct {
    opts *options
  }

  func New(opts ...Option) *Config {
    o := &options{}
    for _, option := range opts {
      option(o)
    }
    return &Config{opts: o}
  }

  func (c *Config) Name() string {
    return c.opts.name
  }
Use it with:

  cfg := config.New(config.Name("Emanon"))
  fmt.Println(cfg.Name())

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

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

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 world where programmers have to write code on a Thursday afternoon after a terrible week.

It’s almost always better to repeat code. It’s obviously not something that is completely black and white, even if I prefer to never really do any form of inheritance or mutability, it’s not like I wouldn’t want you to create a “base” class with “created by” “updated by” and so on for your data classes and if you have some functions that do universal stuff for you and never change, then by all means use them in different places. But for the most part, repeating code will keep your code much cleaner. Maybe not today or the next month, but five years down the line nobody is going to want to touch that shared code which is now so complicated you may as well close your business before you let anyone touch it. Again, not because the theoretical concepts that lead to this are necessarily flawed, but because they require too much “correctness” to be useful.

Academia hasn’t really caught on though. I still grade first semester students who have the whole “Animal” -> “duck”, “dog”, “cat” or whatever they use into their heads as the “correct way” to do things. Similar to how they are often taught other processes than agile, but are taught that agile is the “only” way, even though we’ve seen just how wrong that is.

I’m not sure what we can really do about it. I’ve always championed strongly opinionated dev setups where I work. Some of the things we’ve done, and are going to do, aren’t going to be great, but what we try to do is to build an environment where it’s as easy as possible for every developer to build code the most maintainable way. We want to help them get there, even when it’s 15:45 on a Thursday that has been full of shit meetings in a week that’s been full of screaming children and an angry spouse and a car that exploded. And things like DRY just aren’t useful.

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

#73
I just run Go servers under fcgi. You get orchestration and crash recovery with a very simple interface. Fcgi will launch server processes as needed, feed them events, and shut it down when there's no traffic. Performance is good, and you can run on cheap hosting.

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

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

I think a useful iteration of that pattern is one called Functional Options:

* https://dave.cheney.net/2014/10/17/functional-options-for-fr...

* https://github.com/uber-go/guide/blob/master/style.md#functi...

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

#75
post #69
post #61

Earlier quoted context omitted.

Just do type Username string And replace return Username{username} with return Username(username)

The problem there is that you lose the guarantee that the parser validated the string value. A caller can just say: // This is returning an error for some reason, so let's do it directly. // username, err := parsers.NewUsername(raw) username := parsers.Username(raw) You also get implicit conversions in ways you probably don't want: var u Username u = " " // Implicitly converts from string to Username

That's true I did not think of that.

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

#76

Earlier quoted context omitted.

> and a human-readable explanation of the issue is set as the value. This is annoying to translate later. At least also include some error code string that is documented somewhere and isn't prone to change randomly.

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.

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

#77
post #72

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…

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 overloaded Car serialize and your overloaded Boat serializer, with only a few different fields to justify the difference!

OOP is bad. Copy pasta is bad. DRY is good. All hail DRY, forever, at any cost.

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

#78

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…

Like everything, it depends is the right answer.

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

#79
I did write my own HTTP stuff in C (and more generally internet stuff), on linux (sometimes without a libc, namely direct syscalls), running on ARM64 and x86_64.

And I plan to move to rv64 assembly once I can get reasonably performant hardware (it is already here, but it extremely hard to get some where I am from and how I operate). I dunno if it will be bare metal or with a linux kernel first (coze a minimal TCP stack is already a big thingy).

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

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

em ai have a problem from cars
Post reply on HN