[HN Gopher] How I write HTTP services in Go after 13 years
       ___________________________________________________________________
        
       How I write HTTP services in Go after 13 years
        
       Author : matryer
       Score  : 315 points
       Date   : 2024-02-09 19:00 UTC (3 hours ago)
        
 (HTM) web link (grafana.com)
 (TXT) w3m dump (grafana.com)
        
       | ballresin wrote:
       | What is the value making main.go as small as possible?
       | 
       | Whose dreams come true in this scenario?
        
         | sebastianz wrote:
         | The initialization has to be done in a separate function that
         | you call from the setup code for your end-to-end tests.
        
         | janosdebugs wrote:
         | If you do, you can use the application as a library and most of
         | your code will also be easier to test.
        
         | mattboardman wrote:
         | Usually your main function can't be used by any other part of
         | your program. You should move all component implementations to
         | modules so they can be re-used elsewhere.
        
         | mosselman wrote:
         | "Whose dreams come true in this scenario?"
         | 
         | I love this! I will use this as well.
         | 
         | There are so many situations where I have a feeling that people
         | are solving problems that don't exist. In code I run into at
         | work, code and projects I see online, etc
         | 
         | The "whose dreams are you making come true" really applies
         | here, because dreams are exactly what they are.
         | 
         | I spent quite some time writing an automatic image resizer and
         | optimiser for my blog. Does it matter? No! Should I have spent
         | that time writing blog posts instead? Yes! Still I was chasing
         | some dream.
         | 
         | Thanks for this image
        
         | arccy wrote:
         | not main.go but func main. This allows your run function to
         | return an error and you only need to deal with the abruptness
         | of using os.Exit once
        
         | pphysch wrote:
         | For bespoke internal services, I like to keep main.go as flat
         | as reasonable, like a "script". Handlers can have their own
         | files but the bulk of the control flow and moving parts should
         | be apparent from reading the main file.
         | 
         | Abstracting things away from main makes it less readable and is
         | general pointless for bespoke services that will be deployed in
         | exactly one configuration.
        
           | donio wrote:
           | That's a nice way of putting it. When exploring a new
           | codebase for the first time it can be very helpful to have
           | main.go give you a high level idea about the overall
           | structure of the program.
        
         | abuani wrote:
         | The author goes on to explain a few scenarios where the pattern
         | is helpful. It's not to keep main.go as small as possible, it's
         | so that you can test parts of your main.go file properly. In my
         | experience, if all of my logic is stuffed into `func main()
         | {}`, then I can't actually test it. If I have a helper
         | method(like run in this case), I can test out specific
         | scenarios and ensure the application handles it properly. Some
         | of the examples Mat gave were handling context cancellations
         | properly.
        
         | jrockway wrote:
         | I've never been a fan of making main.go one line. I create the
         | logger, parse the flags, create objects from the flags, and
         | call Run() or something. In the tests, you aren't ever going to
         | do those things in the same way, so there is really no point in
         | putting them in some other file.
        
         | dilyevsky wrote:
         | The idea is to keep the untestable code as small as possible
         | but in practice you just add a layer of indirection and all of
         | your untestable init code is in a different castle.
        
       | arccy wrote:
       | i really like the patterns in this post, pretty much what i've
       | also settled on after much experimentation with different styles.
        
       | romantomjak wrote:
       | Great article with lots of interesting ideas. Can't believe I
       | didn't know about signal.NotifyContext. Finally I'll be able to
       | actually rememeber how to respond to signals instead of copy-
       | pasting that between projects.
        
       | sesm wrote:
       | TLDR: optimize for unit tests and do DI with explicit function
       | arguments. Looks kind of similar to Dropwizard.
        
       | lelandbatey wrote:
       | I 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 probably has many
       | dependencies. I see single handlers using all of the following on
       | a regular basis: DB, cache, blob storage, some kind of special
       | authz thing specific to your endpoints, maybe some fancy
       | licensing checker, a queue or two, a specialized logger, and
       | specialized metrics client. Many of those (metrics,
       | request/response logging) can live in middlewares _most_ of the
       | time, but in every code base there will be times where you need
       | to do something custom with one or the other. As time passes, the
       | more I wonder  "why aren't these all just function parameters?"
       | 
       | Yes, that would be a lot of function parameters (9+ for a single
       | handler, before even getting into the request or custom params
       | themselves), and we all have many rules of thumb and linter rules
       | which try to keep us from having lots of function parameters. But
       | it's not like we're not writing code which depends on all those
       | dependencies, instead we're just sticking them on the "server"
       | class/struct and pretending that because the method signature is
       | shorter, we have fewer dependencies!
       | 
       | As time passes, I find myself wishing more and more for code that
       | takes _all_ its dependencies in the function /method signature,
       | even if there's 20 of them; at least then we wouldn't be lying
       | about how complex the code's getting...
        
         | wereHamster wrote:
         | It doesn't have to be 9+ separate arguments, in some languages
         | it can be a single 'context' or 'env' object that contains just
         | what the handler needs, something like `handleHello({ db,
         | cache, blobStore, authz }, req, res)`. That way, if two
         | handlers use the exact same context you can reuse, but it's
         | also easy enough to declare a per-handler context at the call
         | site.
        
       | jbmsf wrote:
       | I don't write go, but I like these patterns. Feels fairly
       | universal for testable code.
       | 
       | I never want to see another (esp. Python) Quick Start guide that
       | treats dependencies as implicit/static/untestable.
        
       | matt_callmann wrote:
       | is there a git repo with example code?
        
         | mtlynch wrote:
         | Not OP, but I design my Go projects with a very similar pattern
         | that I learned from OP's 2018 post.
         | 
         | I think this is a pretty good example of a real-world
         | implementation:
         | 
         | https://github.com/mtlynch/picoshare
         | 
         | Particularly these files:
         | 
         | https://github.com/mtlynch/picoshare/blob/2cd9979dab084ca781...
         | 
         | https://github.com/mtlynch/picoshare/blob/2cd9979dab084ca781...
        
           | karolist wrote:
           | out of curiosity, why no sort-of-established pkg and internal
           | dirs? What do you think of
           | https://github.com/photoprism/photoprism structure?
        
             | mtlynch wrote:
             | I'm not familiar with that package structure,
             | unfortunately. It might be good, but I'm not sure what the
             | reasons are for structuring the project that way.
        
       | mtlynch wrote:
       | I 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 able to
       | figure out a better solution.
       | 
       | It means that a huge chunk of your code has a huge amount of
       | unnecessary shared state.
       | 
       | I often end up writing HTTP handlers that only need access to a
       | tiny amount of the shared state. Like the HTTP handler needs to
       | check if the requesting user has access to a resource, and then
       | it needs to call one function on the datastore.
       | 
       | I'd love to write tests where I only mock out those two methods,
       | but I can't write simple tests because the handler is part of
       | this giant glob where it has access to _all_ of the datastore and
       | every object the parent server has access to because it 's all
       | one giant object.
       | 
       | Nothing against Mat Ryer, as his pattern is the best I've found,
       | but I still feel like there's some better solution out there.
        
         | jxramos wrote:
         | I've become increasingly sensitive to these high afferent
         | coupling points in the repos I work on, especially the deeper I
         | embed into the world of bazel and how dependency management and
         | physical design influence the code I author.
         | 
         | Where possible plugins are a great strategy to lay down these
         | code seam points that don't force all possibilities upon some
         | body of code, because fundamentally with plugin architectures
         | you pick and choose what you want. Plugins are opt out by
         | default, you must explicitly opt into a plugin for it to
         | manifest. I've been calling software that has this quality
         | going as being an "a la carte" style.
         | 
         | But in general you do what you need to do to avoid "doing
         | everything so you can do anything".
        
         | zer00eyz wrote:
         | I tend to write most of my logic in packages... so a "users"
         | package or a "comments" package (if we were building HN). These
         | have NO http interface! They do however each have their own
         | "main" and some sort of CLI interface: "//go:build ignore" in
         | the comment of that file is your friend.
        
         | vjerancrnjak wrote:
         | 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 functions (a logger decorator) get there to allow
         | composition, but even they have their drawbacks.
         | 
         | It's still some form of structure that you can deal with, not a
         | mistake.
        
           | oogali wrote:
           | I agree that too many arguments to the constructor may have
           | the smell of too much coupling.
           | 
           | But if I really feel I can't avoid the need to pass a good
           | amount of external context, I create a dedicated "options"
           | struct and pass that into the constructor as a pointer. The
           | purpose of the pointer (rather than pass by value) is if I
           | want default arguments, I can pass nil.
           | type ServerOptions struct {             logger
           | *magic.Logger             secretKey string         }
           | func NewServer(options *ServerOptions) (*Server, error) {
           | ...         }
        
         | j1elo wrote:
         | I think a useful iteration of that pattern is one called
         | _Functional Options_ :
         | 
         | * https://dave.cheney.net/2014/10/17/functional-options-for-
         | fr...
         | 
         | * https://github.com/uber-
         | go/guide/blob/master/style.md#functi...
        
       | sethammons wrote:
       | I like a lot of what they've done here. My testing looks a bit
       | different however.
       | 
       | srv, err := newTestServer()
       | 
       | require.NoError(t, err)
       | 
       | defer srv.Close()
       | 
       | resp, err :=
       | http.Post(fmt.Sprintf("http://localhost:%d/signup/json",
       | srv.Port()), "application/json", strings.NewReader(`
       | {"email":"test@example.com", "password": "p@55Word",
       | "password_copy": "p@55Word"} `))
       | 
       | In my newTestServer, I spin up a server with fakes for my
       | dependencies. If I want to test a dependency error, I replace
       | that property with a fake that will return an error. I can
       | validate my error paths. I can validate my log entries. I can
       | validate my metric emission. I can validate timeouts and graceful
       | shutdowns.
       | 
       | After the server starts, I inspect to determine which port it is
       | running on (default is :0 so I have to wait to see what it got
       | bound to).
       | 
       | My "unit" tests can test at the handler level or the http level,
       | making sure that I can fully test the code as the users of my
       | system will see it, exercising all middleware or none. I can spin
       | up N instances and run my tests in parallel.
        
       | jopsen wrote:
       | I'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.
        
         | dilyevsky wrote:
         | 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
        
       | zemo wrote:
       | > 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 write back to the
       | config object when it was passed to them. Or another case was
       | where I've seen it such that you couldn't disable a portion of
       | the system because it wrote data into the config object that was
       | read by some other subsystem later. The pattern of "your
       | configuration is one big value, which is mutable" is one of the
       | more annoying patterns that I've seen before, both in Go and in
       | other languages.
        
         | doh wrote:
         | I think that's a valid criticism. What do you think would be a
         | more ergonomic pattern?
        
           | Raynos wrote:
           | I wrote a static config class that reads configuration for
           | the entire app / server from a JSON or YAML file ( https://gi
           | thub.com/uber/zanzibar/blob/master/runtime/static_... ).
           | 
           | Once you've loaded it and mutated it for testing purposes or
           | for copying from ENV vars into the config, you can then
           | freeze it before passing it down to all your app level code.
           | 
           | Having this wrapper object that can be frozen and has a
           | `get()` method to read JSON like data make it effectively not
           | mutable.
        
             | doh wrote:
             | I use similar pattern myself. Was curious if the OP is
             | using some other, like for instance splitting the struct
             | into two (im/mutable) and then passing them around, or
             | what.
             | 
             | BTW kudos on zanzibar. Love the tech and the code).
        
         | tejinderss wrote:
         | The keyword here is "mutable" config object and not config data
         | object in general. I use immutable config dataclass liberally
         | in one of my python projects and i pass it around in all
         | modules. Many functions rely on multiple values and instead of
         | passing all of them as function parameters (which requires
         | their own function typings), the dataclass has all variables
         | with typing definitions in one place, its pretty handy design
         | pattern.
        
         | gloryjulio wrote:
         | I agree. We ran into sev by changing the top level config
         | object before. You DO NOT want to modify it. The wasted man
         | hour is not worth. You will never know where or how it get
         | used. If you make changes it's better to derive from it
         | instead.
         | 
         | Update: What's funny was, in our design the config object was
         | kinda immutable. You have to use the WARNING_DO_NOT_USE api to
         | make modification. We did mutate the object and we caused a sev
        
         | fnordlord wrote:
         | I've tended to create a Config struct for each package and then
         | a configs.Config struct that's just made up of each package's
         | Config. It might not be a Go best practice but I like that I
         | can setup the entire system's configuration on startup as one
         | entity but then I only pass in the minimally required
         | dependencies for each package. It also makes testing a little
         | easier because I don't have to fake out the entire
         | configuration for testing one package.
        
         | MrDarcy wrote:
         | 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{opts: o}       }            func (c *Config)
         | Name() string {         return c.opts.name       }
         | 
         | Use it with:                 cfg :=
         | config.New(config.Name("Emanon"))       fmt.Println(cfg.Name())
        
       | mtlynch wrote:
       | > _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 checker.
       | 
       | For example, imagine you wanted to defend against the user
       | picking an illegal username. Like you want to make sure the user
       | can't ever specify a username with angle brackets in it.
       | 
       | With the Validator approach, you have to remember to call the
       | validator on 100% of code paths where the username value comes
       | from an untrusted source.
       | 
       | Instead of using a validator, you can do this:
       | type Username struct {           value string         }
       | func NewUsername(username string) (Username, error) {
       | // Validate the username adheres to our schema.           ...
       | return Username{username}         }
       | 
       | That guarantees that you can never forget to validate the
       | username through any codepath. If you have a Username object, you
       | know that it was validated because there was no other way to
       | create the object.
       | 
       | [0] https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-
       | va...
        
         | oorza wrote:
         | Conceptually equivalent to the ancient arts of private
         | constructors and factory methods.
        
         | xboxnolifes wrote:
         | Crazy that actually using your type system leads to better
         | code. Stop passing everything around as `string`. Parse them,
         | and type them.
        
           | stickfigure wrote:
           | There's a name for this anti-pattern: "Stringly typed"
        
         | asp_hornet wrote:
         | Now what? the username is in an unexported field and unusable?
         | I can kind of see what its going for but it seems like a way
         | just to add another layer of wrapping and indirection.
        
           | pants2 wrote:
           | It would need a getter here. Probably good to keep it
           | immutable, if you want guarantees that it will never be
           | changed to something that violates the username rules.
        
             | asp_hornet wrote:
             | > need a getter
             | 
             | 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.
             | 
             | Not to mention I cant easily marshal/unmarshal into it and
             | next week valid username is a username that doesnt already
             | exist in the database.
             | 
             | Maybe this approach appeals to people and Im hesitant to
             | say "that's not how Go is supposed to be written" but for
             | me this feels like "clever over clear".
        
         | cogman10 wrote:
         | 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 object shared
         | by both.
         | 
         | For example, consider data looking like                   Data
         | {           value string         }
         | 
         | and ValidatedData looking like                   ValidatedData
         | {           value int         }
         | 
         | There's a mighty temptation for some devs to want to apply DRY
         | and zip these two things together. Unfortunately, that can
         | really be messy on these sorts of type changes and the where of
         | where validation needs to happen gets muddled.
        
           | xboxnolifes wrote:
           | Except Username is _not_ exactly the same as string, and that
           | 's important. Username is a _subset_ of string. If they were
           | equivalent, we wouldn 't need to parse/validate.
           | 
           | The often misinterpreted part of DRY is conflating "these are
           | the same words, so they are the same", with "these are the
           | same concept, so they are the same". A Username and a String
           | are conceptually different.
        
             | cogman10 wrote:
             | DRY is just "Do not repeat yourself". And a LOT of devs
             | take that literally. It's not "Do not repeat concepts"
             | (which is what it SHOULD be but DRC isn't a fun acronym).
             | 
             | Unfortunately "This is the same character string" is all a
             | DRY purist needs to start messing up the code base.
             | 
             | I honestly believe that "DRY" is an anti-pattern because of
             | how often I see this exact behavior trotted out or
             | espoused. It's a cargo cult thing to some devs.
        
               | oorza wrote:
               | That's why I like to tell people to always remember to
               | stay MOIST - the Most Optimal is Implicitly the Simplest
               | Thing.
               | 
               | When you add complexity to DRY out your code, you're
               | adding a readability regression. DRY matters in very few
               | context beyond readability, and simplicity and low
               | cognitive load need to be in charge. Everything else you
               | do code-style wise should be in service of those two
               | things.
        
               | hombre_fatal wrote:
               | This seems less about DRY and more a story about a
               | hypothetical junior dev making a dumb mistake
               | masquerading as commentary about "DRY purism".
        
               | cogman10 wrote:
               | Man I wish it was just jr devs. I cut jrs a ton of slack,
               | they don't know any better. However, it's the seniors
               | with the quick quips that are the biggest issue I run
               | into. Or perhaps senior devs with jr mentalities
        
               | ikiris wrote:
               | most srs are just jrs with inflated egos and titles
        
               | sroussey wrote:
               | Like everything, it depends is the right answer.
        
           | devjab wrote:
           | One of the major issues with a lot of the outdated concepts
           | in programming is that we still teach them to young people. I
           | work a side gig as an external examiner for CS students.
           | Especially in the early years they are taught the same OOP
           | content that I was taught some decades ago, stuff that I
           | haven't used (also) for some decades. Because while a lot of
           | the concepts may work well in theory, they never work out in
           | a world where programmers have to write code on a Thursday
           | afternoon after a terrible week.
           | 
           | It's almost always better to repeat code. It's obviously not
           | something that is completely black and white, even if I
           | prefer to never really do any form of inheritance or
           | mutability, it's not like I wouldn't want you to create a
           | "base" class with "created by" "updated by" and so on for
           | your data classes and if you have some functions that do
           | universal stuff for you and never change, then by all means
           | use them in different places. But for the most part,
           | repeating code will keep your code much cleaner. Maybe not
           | today or the next month, but five years down the line nobody
           | is going to want to touch that shared code which is now so
           | complicated you may as well close your business before you
           | let anyone touch it. Again, not because the theoretical
           | concepts that lead to this are necessarily flawed, but
           | because they require too much "correctness" to be useful.
           | 
           | Academia hasn't really caught on though. I still grade first
           | semester students who have the whole "Animal" -> "duck",
           | "dog", "cat" or whatever they use into their heads as the
           | "correct way" to do things. Similar to how they are often
           | taught other processes than agile, but are taught that agile
           | is the "only" way, even though we've seen just how wrong that
           | is.
           | 
           | I'm not sure what we can really do about it. I've always
           | championed strongly opinionated dev setups where I work. Some
           | of the things we've done, and are going to do, aren't going
           | to be great, but what we try to do is to build an environment
           | where it's as easy as possible for every developer to build
           | code the most maintainable way. We want to help them get
           | there, even when it's 15:45 on a Thursday that has been full
           | of shit meetings in a week that's been full of screaming
           | children and an angry spouse and a car that exploded. And
           | things like DRY just aren't useful.
        
             | shrimp_emoji wrote:
             | > _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 overloaded
             | Car serialize and your overloaded Boat serializer, with
             | only a few different fields to justify the difference!
             | 
             | OOP is bad. Copy pasta is bad. DRY is good. All hail DRY,
             | forever, at any cost.
        
         | skybrian wrote:
         | This is a good design pattern, but be wary of doing validation
         | too early. The design pattern allows you to do it as early or
         | late as you like, but doesn't tell you when to do it. Often
         | it's best to do it as part of parsing/validating some larger
         | object.
         | 
         | See Steven Witten's "I is for Intent" [1] for some ideas about
         | the use of unvalidated data in a UI context.
         | 
         | [1] https://acko.net/blog/i-is-for-intent/
        
         | andrus wrote:
         | 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.
        
         | 3pm wrote:
         | AKA 'Value Object' from DDD or a similar 'Quantity' accounting
         | pattern. Another angle is that this fixes 'Primitive Obsession'
         | code smell.
        
         | costco wrote:
         | Just do                   type Username string
         | 
         | And replace                     return Username{username}
         | 
         | with                     return Username(username)
        
           | daveFNbuck wrote:
           | If you do that, people outside the package can also do
           | Username(x) conversions instead of calling NewUsername.
           | Making value package private means that you can only set it
           | from outside the package using provided functionality.
        
           | mtlynch wrote:
           | The problem there is that you lose the guarantee that the
           | parser validated the string value.
           | 
           | A caller can just say:                   // This is returning
           | an error for some reason, so let's do it directly.         //
           | username, err := parsers.NewUsername(raw)         username :=
           | parsers.Username(raw)
           | 
           | You also get implicit conversions in ways you probably don't
           | want like:                   var u Username         u =
           | "<hello>" // Implicitly converts from string to Username
        
             | costco wrote:
             | That's true I did not think of that.
        
         | Scarblac wrote:
         | > and a human-readable explanation of the issue is set as the
         | value.
         | 
         | This is annoying to translate later. At least also include some
         | error code string that is documented somewhere and isn't prone
         | to change randomly.
        
           | klodolph wrote:
           | I mean, you may end up just wanting something like,
           | type UsernameError struct {           name   string
           | reason string         }         func (e *UsernameError)
           | Error() string {            return fmt.Errorf("invalid
           | username %q: %s", e.name, e.reason)         }
           | 
           | And reason can be "username cannot be empty" or "username may
           | not contain '<'" or something like that.
           | 
           | This is fine for lots of different cases, because it's likely
           | that your code wants to know how to handle "username is
           | invalid", but only humans care about _why_.
           | 
           | I have personally never seen a Go codebase where you parse
           | error strings. I know that people keep complaining about it
           | so it must be happening out there--but every codebase I've
           | worked with either has error constants (an exported var set
           | to some errors.New() value) or some kind of custom error type
           | you can check. Or if it doesn't have those things, I had no
           | interest in parsing the errors.
        
             | Scarblac wrote:
             | I write mostly frontends. Sometimes the APIs I talk to give
             | back beautiful English error messages - that I can't just
             | show to the user, because they are using a different
             | language most of the time. And I don't want to write logic
             | that depends on that sentence, far too brittle.
        
         | patrickkristl wrote:
         | em ai have a problem from cars
        
       | jupp0r wrote:
       | I 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 initialization
       | between them.
       | 
       | I've used quite a few dependency injection libraries in various
       | languages over the years (and implemented a couple myself) and
       | the simplicity and versatility of fx makes it my favorite so far.
        
       | liampulles wrote:
       | 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 structured config to the run function to let
       | those tests be more focused.
       | 
       | * I disagree with parsing templates using sync.Once in a handler
       | because I don't think handlers should do template parsing at all.
       | I would do this when the app starts: if the template cannot be
       | parsed, the app should not become ready to receive any requests
       | and should rather exit with a non-zero exit code.
        
       | earthboundkid wrote:
       | The validator should return map[string][]string so that a request
       | can have multiple problems with one field.
        
         | earthboundkid wrote:
         | The sync.Once should be sync.OnceValues instead.
        
       | bumpa wrote:
       | The encode example contains a bug and a lint issue. Firstly,
       | calling w.Header().Set after w.WriteHeader is likely a bug, as
       | the w.WriteHeader method call should occur after setting the
       | headers.
       | 
       | The second issue involves passing an unused *http.Request, which
       | will likely cause the linter to flag it.
        
       | Animats wrote:
       | I just run Go servers under fcgi. You get orchestration and crash
       | recovery with a very simple interface. Fcgi will launch server
       | processes as needed, feed them events, and shut it down when
       | there's no traffic. Performance is good, and you can run on cheap
       | hosting.
        
       | sylware wrote:
       | I did write my own HTTP stuff in C (and more generally internet
       | stuff), on linux (sometimes without a libc, namely direct
       | syscalls), running on ARM64 and x86_64.
       | 
       | And I plan to move to rv64 assembly once I can get reasonably
       | performant hardware (it is already here, but it extremely hard to
       | get some where I am from and how I operate). I dunno if it will
       | be bare metal or with a linux kernel first (coze a minimal TCP
       | stack is already a big thingy).
        
       ___________________________________________________________________
       (page generated 2024-02-09 23:00 UTC)