Live data from Hacker News

How I write HTTP services in Go after 13 years

grafana.com

251–259 of 259 posts

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

#251
post #181

Earlier quoted context omitted.

I don’t get what you’re about. The root comment clearly presents a structure of a separate type. The fact that it happens to contain a single string field is completely irrelevant (what type an actual username should be, a float?). “Stringly typed” is about stringifying non-string values to save typing work and is not applicable here in the slightest.

I wasn't replying to the root comment, I was replying in the context of the subsequent three comments, specifically: > > > Crazy that actually using your type system leads to better code. > > There's a name for this anti-pattern: "Stringly typed" > I don't think a reasonable person would consider storing a username in a string to be "stringly typed". #1 was saying that the root comment shows better code using the typ…

I see, my apologies!

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

#252
post #172

Earlier quoted context omitted.

I used that pattern for a while but stopped using it. I first encountered it from this blog post: https://commandcenter.blogspot.com/2014/01/self-referential-... It's a lot of boilerplate to create something that's not actually immutable. It also makes it harder to figure out which options are available, since now you can't just look at the documentation of the type, you have to look at the whole module package to fi…

> It's a lot of boilerplate to create something that's not actually immutable How is not actually immutable? How could cfg.opts.name be modified after New() returns? > It also makes it harder to figure out which options are available I find it easier, the go tooling helps a lot. For example, all the options are grouped together at https://pkg.go.dev/github.com/go-kit/log/level#Option It's also easy to use "Find Usage…

It could be mutated by anything in the package that contains the type. The only thing Go can make truly immutable - as in a compiler error if you try - is a constant primitive.

Functional options have some niceties - largely their ability to evolve without breaking changes - but as the GP points out, completely break discoverability with intellisense. Having to do some dance with filtering usage to find the options available is just worse than a static config struct with zero values that are meaningful for anything not set.

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

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

Maybe this is heresy in the Go community, but this is a problem that automatic dependency injection solves. https://dagger.dev/hilt/testing-philosophy.html The biggest problem with the "pass nil for unused dependencies" approach is that when you modify some code to actually use one of those dependencies when it didn't before, you have to go back through every test and populate it.

Automatic dependency injection doesn’t solve that, it masks it and makes the onset of pain from poor design appear much later than it would without - by which time it is harder to fix.

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

#254

Earlier quoted context omitted.

what happens if a language has zero-values, is that you can't "parse, don't validate". Maybe it's time for you to finally try rust? Or any other language without zero-values, since rust seems to irritate you in particular.

Don't worry, I have tried languages without zero-values. But they have nothing to do with the discussion that was taking place before the ad break. Now back to the show, you cannot prevent library consumers from doing things you don't intend without a compete type system. Rust does not have a complete type system. It leaves holes open for library consumers to do unexpected things and as such it has no relevance here.…

> Now back to the show,

The original claim was that with go, doing certain pattern "[...] guarantees that you can never forget to validate the username through any codepath". Which is not true. It is not true, because go has its own billion-dollar-mistake called zero values.

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

#255

Earlier quoted context omitted.

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.

We use this pattern extensively in a large Java app. As long as you establish these patterns early on in the project, the team adapts to the conventions. It's worked well for us and the lack of language support doesn't get in the way much.

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

#256
post #217

Earlier quoted context omitted.

I’ve refactored a large js code base into ts. Found one such bug for every ~2kloc. The obvious ones are found quickly in untyped code, the problem is in rare cases where you e.g. check truthiness on something that ends up always true.

Of those bugs I wondered how much a type would help. For example is it a misunderstanding of business requirements (nosurcharge bool = iscash bool) or a “typo” / copy paste error. If the former types don’t help. The latter they might.

It definitely helps in larger applications where things are named similarly, especially if you're dealing with a massive DB schema. If you have some method to update some data where all the PRs are longs and it has the signature "update(long,long)", passing the wrong long value would be disastrous. Even if this type of error is 1:10k LOC, using wrapper classes pretty much eliminates this bug.

In our codebase, we use wrapper classes and the only time we had a defect with this is when one developer got lazy and used 3 primitive Strings in a class instead of wrapper classes. Another developer needed to update the code and populated the wrong wrapper class as they were not as familiar with that part of the codebase. Had the original developer simply used wrapper classes, the person maintaining the code wouldn't have had that confusion.

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

#257

Earlier quoted context omitted.

Don't worry, I have tried languages without zero-values. But they have nothing to do with the discussion that was taking place before the ad break. Now back to the show, you cannot prevent library consumers from doing things you don't intend without a compete type system. Rust does not have a complete type system. It leaves holes open for library consumers to do unexpected things and as such it has no relevance here.…

> Now back to the show, The original claim was that with go, doing certain pattern "[...] guarantees that you can never forget to validate the username through any codepath". Which is not true. It is not true, because go has its own billion-dollar-mistake called zero values.

If you go way back there was talk about that, but the discussion had long shifted to "Sometimes it's nice to work with a type system where designers of libraries can actually prevent you from writing bugs."

I get it: You were in such a rush to fill your marketing quotas that you didn't bother to read the entire thread. Maybe the lesson here is don't use HN as an advertising platform next time? You should have known better from the get go.

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

#258
post #253

Earlier quoted context omitted.

Maybe this is heresy in the Go community, but this is a problem that automatic dependency injection solves. https://dagger.dev/hilt/testing-philosophy.html The biggest problem with the "pass nil for unused dependencies" approach is that when you modify some code to actually use one of those dependencies when it didn't before, you have to go back through every test and populate it.

Automatic dependency injection doesn’t solve that, it masks it and makes the onset of pain from poor design appear much later than it would without - by which time it is harder to fix.

My experience has been the opposite - we've been maintaining large (tens of millions loc, thousands of developers) mobile app codebases with this approach for about 8 years now, and results have been much better than what was done before.

A number of bad decisions were made and later discovered over time, and it was much easier to address them with automatic dependency injection available than without. Using as "big" of a test as you can without regressing speed or flakiness gives you really solid, non-fragile tests, which makes large changes tractable to do safely.

I don't know whether certain poor designs would have been spotted sooner, but this certainly made the code easier to change and resulted in more useful and lower maintenance tests.

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

#259

This is 100% not how I write it. Only thing I agree on is putting all the paths in one file. In most other programming languages I've done a lot of research how to make it nice and clean. Was hoping this was it for Go because I'm cleaning up a big project. But my very basic no nonsense current setup seems better to me than this in many ways. If anybody has another example that is a lot better (and I don't mean comple…

Don't worry, you will get there with more experience.
Post reply on HN