I agree with a lot of this, I'll add my own opinions: * I would pass a waitgroup with the app context to service structs. This way the interrupt can trigger the app shutdown via the context and the main goroutine can wait on the waitgroup before actually killing the app. * If writing a CLI program, then testing stdout, stdin, stderr, args, env, etc. is useful. But for an http server, this is less true. I would pass s…
I find your first point interesting, wouldn’t be that solved by context propagation and waiting for the server to shutdown? Thanks!
How I write HTTP services in Go after 13 years
231–240 of 259 posts
Re: How I write HTTP services in Go after 13 years
#232> 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…
Re: How I write HTTP services in Go after 13 years
#233I 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…
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.
Re: How I write HTTP services in Go after 13 years
#234> 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…
Re: How I write HTTP services in Go after 13 years
#235Earlier quoted context omitted.
> 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
#236> 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…
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 is to inject a symbol into the object during parsing which confirms it has been passed through the correct parser function.
Personally, I just do basic type parsing on input data (usually request data) and more strict parsing where constraints like “is this a valid username, is this a valid email” during output (usually sending to the database). What happens in between I/O doesn’t matter much in many projects (CRUD), and in the places it does you can enforce more rigidity.
Re: How I write HTTP services in Go after 13 years
#237Earlier quoted context omitted.
Top of the hour again? Time for another Rust advertisement? The topic at hand is about preventing library users from doing things the library author didn't intended using the type system, not "what happens if a language has zero-values". Perhaps you are not able to comprehend this because you are hungry? You're not you when you are hungry. Grab a Snickers.
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.
Re: How I write HTTP services in Go after 13 years
#238> 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…
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.
I personally find being robust to errors and having clear error messages is the best option.
Don't focus so hard on getting things right, but rather dealing with things when they go wrong.
Re: How I write HTTP services in Go after 13 years
#239Earlier 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?
Every struct requiring its zero value to be meaningful is probably one of the worst design flaws in the language.
Re: How I write HTTP services in Go after 13 years
#240Earlier 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…
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.