Live data from Hacker News

How I write HTTP services in Go after 13 years

grafana.com

241–250 of 259 posts

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

#241
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 just use a closure. So instead of defining a handler as:

  func HandleX(w http.ResponseWriter, req *http.Request) {
    // code
  }
I use:

  func HandleX(store *DataStore, dep1 Foo, dep2 Bar, commonDep Common) http.HandlerFunc {
    //
    // maybe some initialization
    //
    return func(w http.ResponseWriter, req *http.Request) {
      // code
    }
  }
and initialize them all once in whatever entrypoint there is.

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

#242

Earlier quoted context omitted.

This doesn’t solve the problem. If I have a user type, inferred from a Zod schema: > { username: string; email: string } And a function which takes that type: > storeUser(user: User) There is absolutely nothing that guarantees that the user object has been parsed by Zod. You can simply: > storeUser({ username: “”, email: “no” }) And Typescript will not shout at you. The only way to comparably solve it with Typescript…

Well, I use ajv and they have ways of applying format validation, so not just saying: "this is a string", but rather, "this is a string and must be a valid domain name". Now, if your complaint is rather that you can call whatever method and pass in your bogus data, I don't see the point in arguing that. It's your code, the only person who can stop you is you.

> Now, if your complaint is rather that you can call whatever method and pass in your bogus data

This entire comment thread is a discussion about how to prevent that from being a possibility. The person I responded to threw their hat in with a Typescript solution that doesn’t achieve the goal being discussed. I was simply pointing this out.

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

#243
post #158

Earlier quoted context omitted.

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

Is this a Go thing? In C# land this is trivial.

There are Go DI frameworks overall DI is not that popular in Go community.

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

#244
post #181

Earlier quoted context omitted.

It definitely is stringly typed. It's just that it's a very normalized example of it, that people don't think of as being an antipattern. If you want to implement what Yaron Minsky described as "make illegal states unrepresentable", then you use a username type, not a string. That rules out multiple entire classes of illegal states. If you do that, then when you compile your program, the typechecker can provide a muc…

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 type system.

#2 was clearly referring to the case where you don't do this as being an anti-pattern.

#3 is saying that storing a username in a string, without wrapping defining a distinct type for it, was not stringly typed. But as I pointed out, it certainly is.

If you doubt my interpretation of #3, the same commenter said this in another comment: "Is it really more 'programmer friendly' to create wrapper types for individual strings all over your codebase?"

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

#245
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 sure who was right. I’ll tie break with https://wiki.c2.com/?StringlyTyped= which pretty much says what you just said

The commenter you're replying to misunderstood the discussion. See my sibling reply.

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

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

> 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?

That can be handled transparently in languages that have good support for strong type systems, like Rust or Haskell, using traits or type classes.

What you're saying is essentially that addressing stringly typing can only be taken so far in weakly typed languages, without becoming inconvenient.

> 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.

The definitional question is not that interesting. The point is that the concept applies just as much to a username represented as a string as it does to any other kind of value being represented as a string.

The reason is simple, which is just that "string" is a general type that can represent anything, whereas "username" is a subset of all possible strings. If you're trying to use your type system to ensure correct code, you want to be able to type check a function signature like `f(user, company, motto)`, just to take a simple example.

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

#247
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.

But if you then create a constructor / factory method for that struct, not setting it would trigger an error. But this is one of the problem with Go and other languages that have nil or no "you have to set this" built into their type system: it relies on people's self-discipline, checked by the author, reviewer, and unit test, and ensuring there's not a problem like you describe takes up a lot of diligence.

It only relies on unit tests. The people can fail all day long and the unit tests will catch it every single time. Not special unit tests that attempt to seek out such issues, the same unit tests you are writing in languages that have a stricter type system.

If you forget to initialize a field and the tests don't notice, you didn't need the field in the first place, so it won't matter if it is left in an invalid state.

You just don't get the squiggly lines in your text editor. That's the tradeoff.

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

#248

Earlier quoted context omitted.

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…

This doesn’t solve the problem. If I have a user type, inferred from a Zod schema: > { username: string; email: string } And a function which takes that type: > storeUser(user: User) There is absolutely nothing that guarantees that the user object has been parsed by Zod. You can simply: > storeUser({ username: “”, email: “no” }) And Typescript will not shout at you. The only way to comparably solve it with Typescript…

Depends on what the problem definition is. If it's having an as bullet proof solution as possible, you're of course right.

But there are simpler and cheaper solutions that might be good enough.

I guess my intention was also to point out that there're mature frameworks for many languages, but somehow most people in the Go community keep reinventing the wheel and unfortunately more often worse than better. Some years ago I wrote a Go web service. Of course I found the first two versions of OPs series. They're great to read and even greater to watch on YT, but I preferred the approach ardanlabs (Bill Kennedy). It was for sure interesting going through all of this, but incredible time consuming.

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

#249

Earlier quoted context omitted.

Well, I use ajv and they have ways of applying format validation, so not just saying: "this is a string", but rather, "this is a string and must be a valid domain name". Now, if your complaint is rather that you can call whatever method and pass in your bogus data, I don't see the point in arguing that. It's your code, the only person who can stop you is you.

> Now, if your complaint is rather that you can call whatever method and pass in your bogus data This entire comment thread is a discussion about how to prevent that from being a possibility. The person I responded to threw their hat in with a Typescript solution that doesn’t achieve the goal being discussed. I was simply pointing this out.

>> I've found validators to be much more error-prone than leveraging Go's built-in type checker.

>This entire comment thread is a discussion about how to prevent that from being a possibility.

No, this thread is also about how much you need to invest to be safe enough, when time and resources are limited.

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

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

> 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.

How do you create immutable structs in Go? I didn't think you could, which makes this more a Go problem than a `passing around Config object` problem.

(One of my pet peeves, coming to Go from C, is how little of stronger typing there actually is. In C, I pass and return const objects everywhere I can, my enums are not just ints because the compiler can warn when I forget one in a switch statement, etc).

Post reply on HN