[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)