> 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?
How I write HTTP services in Go after 13 years
91–100 of 259 posts
Re: How I write HTTP services in Go after 13 years
#92> 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…
It causes what I call the lumpy carpet syndrome - sweeping the complexity under the carpet causes bumps to randomly appear that when squashed tend to cause other bumps to pop up rather than actually solving the problem.
Re: How I write HTTP services in Go after 13 years
#93I want to see a greater acceptance of this idea: > My handlers used to be methods hanging off a server struct, but I no longer do this. If a handler function wants a dependency, it can bloody well ask for it as an argument. No more surprise dependencies when you’re just trying to test a single handler. For HTTP services in any language, your handlers will usually end up with a lot of business logic, logic which proba…
type CreateUser struct {
store storage.Store
cache caching.Cache
logger logging.Logger
pub events.Publisher
// etc
}func (op CreateUser) ServeHTTP(ctx, req, rw) {}
// or if you have custom handlers
func (op CreateUser) ServeHTTP(ctx, input) (output, error) {}
And in my main.go, or where I set up my dependencies, I create each operation, passing it its specific dependencies. I love that because I can keep all the helper methods for that specific operation/handler on that specific struct as private methods.
It does get tedious when you have one operation needing another, as you might start passing these around or you extract that into its own package/service.
Re: How I write HTTP services in Go after 13 years
#94> 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
#95> 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.
Re: How I write HTTP services in Go after 13 years
#96I 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 the object created by NewServer is dealing with too much. Probably has too many data types coupled to it and too much behavior. Simple example is adding a logger. If you add it as a dependency to the constructor, the object starts doing a bit more than initial simple implementation. It's fine to do it, but shame to not figure out how to log without editing the implementation of a simple thing. Higher order f…
Re: How I write HTTP services in Go after 13 years
#97I'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.
The problem with this approach is writing openapi by hand from scratch is incredibly tedious process. Writing Protobufs, capnproto or any such similar idl feels much more productive
Agree it doesn't fix the "root" problem that the overall syntax is not ergonomic.
Re: How I write HTTP services in Go after 13 years
#98> 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…
Crazy that actually using your type system leads to better code. Stop passing everything around as `string`. Parse them, and type them.
1. Or maybe a map? Those keys might have significance I didn't tell you about.
Re: How I write HTTP services in Go after 13 years
#99I found fx( https://github.com/uber-go/fx ) to be a super simple yet versatile tool to design my application around. All the advice in the article is still helpful, but it takes the "how do I make sure X is initialized when Y needs it" part completely out of the equation and reduces it from an N*M problem to an N problem, ie I only have to worry about how to initialize individual pieces, not about how to synchronize…
I gotta say, I hate these dependency injection frameworks.
In a well designed system this should be trivial. Making sure something is initialised when you want to use it is just a matter of it being available to pass in a constructor as a parameter.
stockService := NewStockService()
orderService := NewOrderService()
orderProcessor := NewOrderProcessor(stockService, orderService)
There shouldn't be any sort of "synchronisation" of initialisation needed because your code won't compile if you do something wrong. If you add a cyclic dependency you will clearly see that because you won't be able to construct things in the right order without an obvious workaround.Re: How I write HTTP services in Go after 13 years
#100Earlier quoted context omitted.
> 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 na…
I value the hours wasted on diagnosing a bug far more than the extra keystrokes and couple of seconds required to avoid it in the first place.