Live data from Hacker News

Spectral Contexts in Go

hypirion.com

21–30 of 41 posts

Re: Spectral Contexts in Go

#21
post #11

We use strongly typed contexts for: - Request-scoped parameters like user ID, request ID, etc. - Dependency injection (database connection and other IO) - Cancellation It works great. The one hairy part is when you need to change the context, for example when logging in you take an anonymous context and end up with a logged-in context. We use linters to ensure that stuff is right, but it still takes some thought in t…

Do you use a single context key with a struct or iota keys for each value?

I experimented with the struct because I was too lazy to do the iota keys. It works but it can be a foot gun. We have multiple strongly typed contexts (again one example would be anonymous vs logged in context) and with the struct approach, it’s easy to get in a situation where you lose one of your values by passing the context through a layer that doesn’t know about your types.

I still think these gotchas are indicative of issues in the codebase rather than the struct or the typed context. They only come up in the hairiest parts of the code.

Re: Spectral Contexts in Go

#22
post #14
post #11

We use strongly typed contexts for: - Request-scoped parameters like user ID, request ID, etc. - Dependency injection (database connection and other IO) - Cancellation It works great. The one hairy part is when you need to change the context, for example when logging in you take an anonymous context and end up with a logged-in context. We use linters to ensure that stuff is right, but it still takes some thought in t…

Why do you use it for dependency injection? Why can you not pass those parameters directly when instantiating your service?

Because there’s a lot of them. Logging, MQ messages, database connection, emails, S3. If we want to limit which of these are available to the code, we do so using a strongly typed context. (For example a ReadOnlyContext)

The only use of dependency injection for us is testing. Our test fixtures have to provide both request-scoped info (user ID etc.) and mocks for IO. It makes sense to do that all in one object.

Re: Spectral Contexts in Go

#23
post #20

Earlier quoted context omitted.

are one-character variables common in Go? every time that i think "maybe i should pick up Go," and then i see the syntax... it gives me pause. seems really ugly!

Yes but every time I've seen it, it's in a small scope, it's very obvious the type of the variable and what it's doing. It's really not as bad as it seems once you spend some time in go.

And there are a lot of conventions so you know, eg, w is an http.ResponseWriter, r is an http.Request pointer, etc.

Re: Spectral Contexts in Go

#24
post #3

Don't do it. Don't past non-request-scoped data through contexts, and none of these are request-scoped. You can trivially pass such values through middlewares using methods or closures. Also, while I think I know what "unlike Rust, these phantom types appear at runtime in Go" is saying, it's saying it in a really confusing way. These types do not "appear at runtime"; they are exactly like all other types, constructed…

"phantom" and "phantom type" are well-understood terms that describe a type with a type parameter that isn't used in the type definition. "spectral" is a pun (phantom -> spectral). It has nothing to do with runtime representation of types.

Re: Spectral Contexts in Go

#25

There's no need to put any dependencies in the context, ever. For HTTP servers, the easiest way is just to use a struct to hold the dependencies: type Server struct { logger *Logger users *UserService // etc. } Assuming we use a router like Chi or similar: router.Get("/users", srv.HandleUsers) ...where HandleUsers is a method on Server. Some people like to spread handlers in different packages. You can of course decl…

are one-character variables common in Go? every time that i think "maybe i should pick up Go," and then i see the syntax... it gives me pause. seems really ugly!

[dead]

Re: Spectral Contexts in Go

#26
post #16

There's no need to put any dependencies in the context, ever. For HTTP servers, the easiest way is just to use a struct to hold the dependencies: type Server struct { logger *Logger users *UserService // etc. } Assuming we use a router like Chi or similar: router.Get("/users", srv.HandleUsers) ...where HandleUsers is a method on Server. Some people like to spread handlers in different packages. You can of course decl…

Exactly this. Your dependencies are not request scoped, so there is no reason for them to be included in a per-request context. Instead, dependencies should be passed in to and stored in the struct that uses them. The data you store in the context should be specific to the current request. Think: request ID, user ID, auth info, etc.

Thanks for this perspective - "request scoped" seems like a really good guideline for context data

Re: Spectral Contexts in Go

#27

Earlier quoted context omitted.

Not sure I understand the typing issue. The standard pattern is to provide an x.Context(ctx, ...) and an x.FromContext(ctx), and those are the only two functions that know about the context key, and they do the type-casting. Since they are the only functions knowing the context key (the key is often a pointer to an unexported variable), you are guaranteed type-safety as long as there is no unsafe code that can preten…

When people mention type safety in this context, they are talking about compiler guarantees. Application code that uses type assertions will never be "type safe" in the way that a compiler can ensure. So while you're correct that if the application code that does the type checking is bug free then the type safety is implied, but is not certain in all cases.

Sure but the interface is typesafe and usually that’s good enough. The two methods of the API (NewContext and FromContext) are typesafe from the perspective of the user.

Re: Spectral Contexts in Go

#28

There's no need to put any dependencies in the context, ever. For HTTP servers, the easiest way is just to use a struct to hold the dependencies: type Server struct { logger *Logger users *UserService // etc. } Assuming we use a router like Chi or similar: router.Get("/users", srv.HandleUsers) ...where HandleUsers is a method on Server. Some people like to spread handlers in different packages. You can of course decl…

are one-character variables common in Go? every time that i think "maybe i should pick up Go," and then i see the syntax... it gives me pause. seems really ugly!

Choosing to name variables with a single character has nothing to do with syntax. At most it could be considered idiomatic, but there's no reason to slavishly follow every idiomatic choice, just as there isn't in natural language.

Re: Spectral Contexts in Go

#29

There's no need to put any dependencies in the context, ever. For HTTP servers, the easiest way is just to use a struct to hold the dependencies: type Server struct { logger *Logger users *UserService // etc. } Assuming we use a router like Chi or similar: router.Get("/users", srv.HandleUsers) ...where HandleUsers is a method on Server. Some people like to spread handlers in different packages. You can of course decl…

are one-character variables common in Go? every time that i think "maybe i should pick up Go," and then i see the syntax... it gives me pause. seems really ugly!

Variable naming conventions are different from syntax, right? The variables in your own code will be the ones you named.

I think this is more prevalent in Go though because go forces you to make many more local variables (since error handling prevents you from composing expressions), and when you need lots of useless local variables for intermediate values with no externally imposed meaning people tend to fall back to things that are easy to write.

Re: Spectral Contexts in Go

#30

There's no need to put any dependencies in the context, ever. For HTTP servers, the easiest way is just to use a struct to hold the dependencies: type Server struct { logger *Logger users *UserService // etc. } Assuming we use a router like Chi or similar: router.Get("/users", srv.HandleUsers) ...where HandleUsers is a method on Server. Some people like to spread handlers in different packages. You can of course decl…

Wow, thanks for this. This nails a nagging problem I was having at my last job. I’d intuitively figured out what was wrong and what a solution might look like, but the way you’ve articulated it here helped crystallize that understanding much better.

It’s weird. It isn’t a complex problem or solution, but almost every team I’ve worked on hasn’t done this. You get to this point where you’ve seen so much context-stuffing that you half expect to see tools to help you do it in the standard library. You almost wonder if your aversion to it is because you’re the crazy one, haha.

Post reply on HN