Live data from Hacker News

REST Servers in Go: Part 1 – standard library

eli.thegreenplace.net

131–140 of 149 posts

Re: REST Servers in Go: Part 1 – standard library

#131

Earlier quoted context omitted.

I was hoping even less that was the case, feels like a very out of touch comment, but I’m willing to listen.

I don't think I was out of touch (which I guess is always how it goes). The value propositions of Golang and Rust are pretty well understood at this point, and I think that if you want abstraction power the choice between them is clear. Rust is hard, but it's not harder than Haskell, and at a high level of abstraction it can be simpler than Golang has to offer in the stdlib, in the happy path with better results, and…

Ah, I see your point. But it could just as easily be made in nearly any language, the difference at the level of “is abstract-able” is meters rather than kilometers.

The problem is that as soon as you need something more than a command line call the engineering burden explodes relative to languages that natively support greater abstraction at the lexical and logical level.

Re: REST Servers in Go: Part 1 – standard library

#132
post #16

Earlier quoted context omitted.

I've never understood the value proposition of a DI framework. Why would I want one when I can initialize my objects in main()? Is XML or JSON or whatever really that much more pleasant than wiring together Go objects?

I'm with you on this. Dependency injection seems to me to replicate some of the features of interfaces while introducing complexity because the injection is indirect -- instead of having code that initializes a different object, it all happens dynamically during runtime using reflection. An antipattern as far as I'm concerned. It seems to achieve little or no gain at a very high cost.

Me three.

DI and IoC are for people ignorant of or hostile towards composition.

Further, aspects and reflection are for those unwilling or unable to make reasonable architectural assumptions.

Said another way, meta programming is for personal projects. And maybe for small, disciplined, high trust teams.

Most devs are average (axiomatically) and most projects are CRUD or scraping. So choice of these tools is self soothing to mitigate inferiority complexes. Like Mensa.

Re: REST Servers in Go: Part 1 – standard library

#133
post #16

Earlier quoted context omitted.

I'm with you on this. Dependency injection seems to me to replicate some of the features of interfaces while introducing complexity because the injection is indirect -- instead of having code that initializes a different object, it all happens dynamically during runtime using reflection. An antipattern as far as I'm concerned. It seems to achieve little or no gain at a very high cost.

Me three. DI and IoC are for people ignorant of or hostile towards composition. Further, aspects and reflection are for those unwilling or unable to make reasonable architectural assumptions. Said another way, meta programming is for personal projects. And maybe for small, disciplined, high trust teams. Most devs are average (axiomatically) and most projects are CRUD or scraping. So choice of these tools is self soot…

> DI and IoC are for people ignorant of or hostile towards composition.

This isn't helped by the messy terminology. "Dependency Injection" is literally another term for "composition", but dependency injection frameworks imply automating the composition of one's object graph. However, people who like these frameworks don't seem to be aware that they can more easily compose their object graph using the structures available in most general purpose languages (literals for lists, maps, structs, etc as well as function calls and so on).

Arguably there's some repetition in constructing an object graph (initializing a list of objects that vary only slightly) that one might want to DRY up, but we already know how to do that with helper functions in host languages, and anyway this is just boilerplate--it's almost certainly not where your bugs are, and it's not where your developers are spending their time. A framework introduces a bunch of complexity at the top level (near main()) i.e., the stuff that everyone from developers to testers to operators/sysadmins will probably need to dig into at some point, and all that for no material advantage to anyone.

Re: REST Servers in Go: Part 1 – standard library

#134

Earlier quoted context omitted.

Me three. DI and IoC are for people ignorant of or hostile towards composition. Further, aspects and reflection are for those unwilling or unable to make reasonable architectural assumptions. Said another way, meta programming is for personal projects. And maybe for small, disciplined, high trust teams. Most devs are average (axiomatically) and most projects are CRUD or scraping. So choice of these tools is self soot…

> DI and IoC are for people ignorant of or hostile towards composition. This isn't helped by the messy terminology. "Dependency Injection" is literally another term for "composition", but dependency injection frameworks imply automating the composition of one's object graph. However, people who like these frameworks don't seem to be aware that they can more easily compose their object graph using the structures avail…

Agree on all points.

VRML-97 is the near pinnacle of human achievement, for declaring scene graphs, a special use case of object graphs. It had reuse. It had patch cords (specify non parent-child relations). It only lacked path expressions.

I really I wish I could tell younger me to publish my own VMRL successor, way back when. Young me forfeited when confronted by XML's JavaScript-like metastasis, which had overwhelmed all rationale human endeavors. Then maybe I could have spared humanity the indignity of JSON and kin.

Had I only known that all bouts of irrational exuberance eventually implode...

Re: REST Servers in Go: Part 1 – standard library

#135
post #75

usually for stores like this I just do this: type Store interface { GetTask(*Task) error } instead of having "GetTaskByID" and "GetTaskByTag" and whatnot. Then in the caller you just do this: task := store.Task{ID: 5} if err := db.GetTask(&task); err != nil { // wahtever } ^ that gets the task by ID task := store.Task{Tag: "foo"} if err := db.GetTask(&task); err != nil { // wahtever } ^ that gets the task by tag.

The downside to this approach is that you're unable to know what fields you can use in your query without reading the GetTask function, and changes to the GetTask function can silently break all callers, since it's now a runtime error.

tbh I've been using this method for years and the first one has never been an issue, because practically speaking you should only expect to give a single struct with a field or two filled in if that field or combination of fields is unique. You probably need that level of domain knowledge about what you're working on elsewhere anyway, so it has never been a problem.

I mean ... for the second problem that's broadly true of making any changes to your data access layer since by definition your Go compiler is not going to, for example, check the validity of a SQL query. So ... yes? but that's not unique to this approach, that's true generally.

Re: REST Servers in Go: Part 1 – standard library

#136
post #91
post #75

usually for stores like this I just do this: type Store interface { GetTask(*Task) error } instead of having "GetTaskByID" and "GetTaskByTag" and whatnot. Then in the caller you just do this: task := store.Task{ID: 5} if err := db.GetTask(&task); err != nil { // wahtever } ^ that gets the task by ID task := store.Task{Tag: "foo"} if err := db.GetTask(&task); err != nil { // wahtever } ^ that gets the task by tag.

This makes sense, did you implement this alongside grpc / protobuf? I'm curios about the way you handled zero values, field masks could be a solution, but I think it would get bloaty.

    func (db *actualStoreImplementation) GetTask(t *Task) error {
        if (t.ID != 0) {
            // query by ID, mutate the parameter, return nil
        }
        if (t.Tag != "") {
            // query by tag, mutate the parameter, return nil
        }
        return ErrWhatever
    }
usually I have some other package that defines all of the types that can appear on the wire (which I often call `wire` because `proto` is taken by protobuf), define some exported interface in that package with an unexported method so that no other packages can define new types for that interface, and then have a method on my db structs that returns the wire types, like this:

    func (t Task) Public() wire.Value {
        return wire.Task{
            // explicitly generate what you want
        }
    }

Re: REST Servers in Go: Part 1 – standard library

#137

Earlier quoted context omitted.

I don't think I was out of touch (which I guess is always how it goes). The value propositions of Golang and Rust are pretty well understood at this point, and I think that if you want abstraction power the choice between them is clear. Rust is hard, but it's not harder than Haskell, and at a high level of abstraction it can be simpler than Golang has to offer in the stdlib, in the happy path with better results, and…

Ah, I see your point. But it could just as easily be made in nearly any language, the difference at the level of “is abstract-able” is meters rather than kilometers. The problem is that as soon as you need something more than a command line call the engineering burden explodes relative to languages that natively support greater abstraction at the lexical and logical level.

> Ah, I see your point. But it could just as easily be made in nearly any language, the difference at the level of “is abstract-able” is meters rather than kilometers.

True, most languages could do it, but there are some hard stops to how easy it is to abstract in golang, and performance also knocks some languages out.

> The problem is that as soon as you need something more than a command line call the engineering burden explodes relative to languages that natively support greater abstraction at the lexical and logical level.

Agreed, once you're off the happy path things can be many times more painful in Rust than Go. I personally think Go will replace Java in most enterprise-y software shops within 5-10 years. Unless there's a specific reason to use the JVM, Go is more than good enough and it's goals align with industry very well.

Re: REST Servers in Go: Part 1 – standard library

#138
post #96

I would probably go with gRPC + grpc-gateway[1] instead. Declaring your services and models in proto files, annotating your services with google.api.http to help grpc-gateway scaffold your HTTP base. Then just implement your services from the interface generated by grpc-go. You can even register your gRPC services to grpc-gateway without actually bringing up a gRPC server. You finally end up having your exact data mo…

Can you expand on how you can register gRPC services to grpc-gateway without needing to run a gRPC server?

Re: REST Servers in Go: Part 1 – standard library

#139
post #116
post #3

Good introduction. A few thoughts: 1) Be careful with locks in the form "x.Lock(); x.DoSomething(); x.Unlock()". If DoSomething panics, you will still be holding the lock, and that's pretty much the end of your program. ("x.Lock(); defer x.Unlock(); x.DoSomething()" avoids this problem, but obviously in the non-panic case, the lock is released at a different time than in this implementation. Additional tweaking is re…

Thank you for the detailed comment! Just want to address (1) quickly. As you've mentioned at the end of the parenthesized note, the reason I did not use `defer` here is to avoid the lock staying across the rest of the handler. I wanted to confine it to the datastore interaction. Thinking more about this now and having read the comments, I'm considering to just hide the lock in TaskStore and avoid all these explicit l…

If you only want to hold the lock for a portion of a function, pull that part out into its own function. Makes the code much easier to reason about. I consider .Unlock() without defer to be a code smell in nearly all cases.

Re: REST Servers in Go: Part 1 – standard library

#140

Earlier quoted context omitted.

Ah, I see your point. But it could just as easily be made in nearly any language, the difference at the level of “is abstract-able” is meters rather than kilometers. The problem is that as soon as you need something more than a command line call the engineering burden explodes relative to languages that natively support greater abstraction at the lexical and logical level.

> Ah, I see your point. But it could just as easily be made in nearly any language, the difference at the level of “is abstract-able” is meters rather than kilometers. True, most languages could do it, but there are some hard stops to how easy it is to abstract in golang, and performance also knocks some languages out. > The problem is that as soon as you need something more than a command line call the engineering b…

I think go would be huge if they could ease their pain points. The average developer isn’t interested in why generics are actually not necessary, or “here’s how you can still use generics” stuff. Even if they’re wrong, you still need to work in that reality.
Post reply on HN