[HN Gopher] Spectral Contexts in Go ___________________________________________________________________ Spectral Contexts in Go Author : todsacerdoti Score : 27 points Date : 2023-06-18 19:32 UTC (3 hours ago) (HTM) web link (hypirion.com) (TXT) w3m dump (hypirion.com) | daniel-thompson wrote: | The author demonstrates attaching services to a context, which | afaict goes against the Go authors' usage recommendations in | https://pkg.go.dev/context: | | > Use context Values only for request-scoped data that transits | processes and APIs, not for passing optional parameters to | functions. | [deleted] | morelisp wrote: | 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 at compile time and | _visible_ when interface-boxed at runtime. They 're in no way | "spectral". | vore wrote: | I'm not a big fan of context variables: they're basically a super | clunky kind of goroutine-local global variable, with all the | problems you might get with these kinds of mutable global | variables. I would argue that if you're having to pass | dependencies down, you should be explicitly dependency injecting | them instead rather than implicitly via context. | cyberax wrote: | How would you do that with standalone functions? You'll need at | least one parameter for cancelation, and one parameter for the | logging context, and perhaps one more parameter for tracing. | | Even passing the single context variable increases clutter a | lot. | [deleted] | ilyt wrote: | They are not "goroutine local", they are essentially (at least | the way they are used) a _request-local_ variable. Which in | itself is useful tool. | | My only real issue with them is lack of type safety so you have | to remember which contest var is what. | | For example I'd like to have User context variable that is set | somewhere early if user is authenticated but now at every | subsequent call there needs to be a bit of fluff to check | whether it is right type, else you risk runtime safety. | vore wrote: | I think it always a mistake to start a goroutine without | passing a context to it, since you are no longer able to | cancel the goroutine unless you build your own mechanism to | do so. Given that, I think you can handwave it a bit and | regard context variables as goroutine local. | tommiegannert wrote: | 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 pretend-inject the key. | | https://cs.opensource.google/go/go/+/refs/tags/go1.20.5:src/. | .. | collinvandyck76 wrote: | 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. | [deleted] | atombender wrote: | 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 declare handlers as functions with no receiver | struct, and pass the data as an argument: | router.Get("/users", func(w io.ResponseWriter, r *http.Request) { | userroutes.HandleUsers(srv, w, r) }) | | For middlewares, e.g. something that parses a session cookie and | fetches the current user, do exactly the same thing. | Cyph0n wrote: | 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. | et1337 wrote: | 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 | the edge cases. | | Still, I think those issues are exposing problems in our codebase | rather than problems with typed contexts. | candiddevmike wrote: | Do you use a single context key with a struct or iota keys for | each value? | rcme wrote: | Why do you use it for dependency injection? Why can you not | pass those parameters directly when instantiating your service? ___________________________________________________________________ (page generated 2023-06-18 23:01 UTC)