Live data from Hacker News

Ask HN: How are you using Go to write production-grade back end services?

news.ycombinator.com

1–10 of 25 posts

Ask HN: How are you using Go to write production-grade back end services?

#1
As the title suggests. Interested to see how (and what) companies are using Go to make production grade backend services.

- What are you using for tests?

- Do you use dependency injection or any mocking frameworks?

- Are you using any routing frameworks?

- HTTP web frameworks?

Would love to know the ins and outs! What is good, what do you not like about Go, any pain points? Anything you would want to improve?

Thanks!

Re: Ask HN: How are you using Go to write production-grade back end services?

#2
Heh, production grade depends more on the people in charge than the tech choices. The lack of exceptions in Go is the biggest stupid thing in software. Exceptions have always been a much less risque feature than multiple return values. In Go every single "function" has multiple return values and the code is litered everywhere with if err != nil... garbage. It will never change because it is apparent that exceptions are to Go as collection literals and operator overloading are to Java.

Re: Ask HN: How are you using Go to write production-grade back end services?

#3
- Web: No framework, I mostly use https://goswagger.io but for basic stuff then just the standard http library + https://github.com/julienschmidt/httprouter

- Testing: https://golang.org/pkg/testing/ + https://pkg.go.dev/mod/github.com/stretchr/testify

- Mocks: https://github.com/golang/mock

- Dependency Injection: None, current user of https://github.com/uber-go/dig and I regret it.

Re: Ask HN: How are you using Go to write production-grade back end services?

#4
My work mainly involves setting up internal enterprise applications.

- The standard testing package

- Manually setup dependency injection. Generally a flow like config=>databases=>models=>routes=>server.

- https://github.com/gorilla/mux for routing and https://github.com/swaggo/swag for documentation

- As for HTTP frameworks I just use the standard package and I really enjoy https://github.com/sirupsen/logrus for logging

We use IIS. I really wanted to use go so, I found a way to run go applications on top of it. That was not fun to figure out, but I can make use of the windows authentication underneath IIS. Required a custom module that forwards some headers to my go applications. Also the version of IIS we use doesn't even support http2 which sucks.

Re: Ask HN: How are you using Go to write production-grade back end services?

#5
post #2

Heh, production grade depends more on the people in charge than the tech choices. The lack of exceptions in Go is the biggest stupid thing in software. Exceptions have always been a much less risque feature than multiple return values. In Go every single "function" has multiple return values and the code is litered everywhere with if err != nil... garbage. It will never change because it is apparent that exceptions a…

Exceptions aren't the only way to handle error. Rust leverages algebraic types to good effect, for example.

Re: Ask HN: How are you using Go to write production-grade back end services?

#6

My work mainly involves setting up internal enterprise applications. - The standard testing package - Manually setup dependency injection. Generally a flow like config=>databases=>models=>routes=>server. - https://github.com/gorilla/mux for routing and https://github.com/swaggo/swag for documentation - As for HTTP frameworks I just use the standard package and I really enjoy https://github.com/sirupsen/logrus for log…

Do you have a writeup/reference for Go under IIS?

Re: Ask HN: How are you using Go to write production-grade back end services?

#7
I've been wanting to do a blog series on how we use Go. Here is a short version.

Tests:

We use the testing package for unit tests and (maybe too much) use interfaces as arguments so we can create test fakes that behave the way we want so we can validate error paths, logs (yes, we assert on logs), metrics, and, of course, green/good expected behavior.

We then have acceptance level testing. These are ensuring the system works as expected. We leverage docker-compose to spin up all our dependencies (or, in some cases, stubs - but only rarely). We then have a custom testing package built atop the stdlib one. It behaves very similarly, but allows for the registering of test suites, pre and post test suite methods, pre and post test methods, and generates reports in json/xml for QA to keep track of test cases, when they ran, pass rates, etc. As part of our SOC2 compliance, we have these to back up our thoroughness in testing. Tests also can have labels so we can run all tests for a given feature only, or a given suite. These tests hit the running binary of our service under test, so if it works here, it will work when deployed.

Before a service makes it to prod, it lands in staging. There, a final suite of tests go through user features and ensure that things are ok one last time. Total black box.

Dependency Injection / Mocking:

I am very, very much against mocking. For that, I did write a blog post; though, I think the thing it highlighted most is I need to write more :). You can goolge "When Writing Unit Tests, Don’t Use Mocks" if you want to read it. When you mock, you create brittle tests that are tied to the assumptions in your mock. Instead, we use "fakes." These are test structs that match an interface and allow us to control their behavior. You might ask how that is different than a mock. Mocks have assumptions and make your tests more brittle and subject to change when you update the code (which is what Martin Fowler concluded in Mocks Aren’t Stubs). People tend to write "thing called 4 times, with arguments foo and bar, and will return x, y, z... blah blah blah." Instead, when you use a fake struct that matches an interface, you can make them as simple or complex as needed, and usually simpler is better. Return a result or an error. Validate the code does what you need. We also avoid functions as parameters for just testing. IE, your test code uses a custom function that is not the function used in prod. These are easy to cause nil panics and are kludgy. Fakes get us what we need 99 times out of 100.

Routing frameworks:

We have folks who don't use them, or use gorilla mux, or chi (my fav). They are convenient and make things easier for passing URL parameters. You can, of course, do this without a customer router. I like Chi because it is stdlib compatible.

HTTP Frameworks:

Nope. I could see, maaaayyyybee if we were writing a bunch of CRUD apps, but we don't. The services my team makes tends to have few routes and not all the CRUD stuff. Even then, if we do a lot of CRUD work, it will only eat up a few days. What we do have, however, is a project skeleton generator so our projects all start out with the same basic directory structure and entry points. Everyone knows that your app starts in cmd/appname/main.go for example.

Logging (and errors):

The other one we leverage in place of HTTP Frameworks is a custom logger and an experiment we are doing with custom error types. We have logging requirements to play nice in our ecosystem at work. Logs all are structured json and have some expected keys. The logger generates all that. We looked at all the log packages and none matched exactly what we needed. We can store key value pairs on the logger and pass that logger around (so you only have to do logger.Add("userid", userID) once and now all logs going forward in a request will have it. You get timestamps, app name, and a few other fields for free. You can create a child logger that will have its own context kv pairs so you don't pollute its parent (helpful for when you go into a function and want to add more details to logs based on errors specific to that function). The other one that we are playing with now on our new project is a custom error type that stores a map of key value pairs so we can just bubble up our custom error type, wrap it with more kv pairs on each bubble up, and then only log at the top, and then when the error is logged, we use our logger to extract the KV pairs and bingo, structured logs with context for each error bubble up point with potentially relevant kv pairs that are only known down in deep levels.

BuildPipe:

We run our tests locally usually. But when we create a PR, a build is kicked off using BuildKite (plugin system is really nice). A PR cannot be merged to master until the test suite passes, which includes the acceptance tests from earlier. After merging to master, a fresh build is run again and that creates artifacts that are then used by ArgoCD so we can roll our code out to our kube cluster.

I love Go. It is my favorite language I've worked in. There are warts, for sure. You can get that list anywhere. There are some oddities when assigning to structs in maps, nil interfaces, shadow variables, non-auto-checked discarded errors, and others. The biggest wart right now is the module system. I think that will improve over time.

Re: Ask HN: How are you using Go to write production-grade back end services?

#8

My work mainly involves setting up internal enterprise applications. - The standard testing package - Manually setup dependency injection. Generally a flow like config=>databases=>models=>routes=>server. - https://github.com/gorilla/mux for routing and https://github.com/swaggo/swag for documentation - As for HTTP frameworks I just use the standard package and I really enjoy https://github.com/sirupsen/logrus for log…

I remember setting up Python to run under IIS.. what a ballache that was.

Re: Ask HN: How are you using Go to write production-grade back end services?

#9
- Test -> stdlib + testify/assert + vektra/mockery

- Dependency injection -> manual DI passing values and objects down

- Routing framework -> gorilla/mux

- HTTP web frameworks -> stdlib + go-kit for the general architecture

We then have a pretty large internal library for all things shared

Re: Ask HN: How are you using Go to write production-grade back end services?

#10
post #2

Heh, production grade depends more on the people in charge than the tech choices. The lack of exceptions in Go is the biggest stupid thing in software. Exceptions have always been a much less risque feature than multiple return values. In Go every single "function" has multiple return values and the code is litered everywhere with if err != nil... garbage. It will never change because it is apparent that exceptions a…

I've worked in exception based code bases such as Python (Twisted specifically) and Go. Unlike many, I've used both in the same organization working on similar problems for nearly a decade while the scale of number of engineers working in the code and the number of requests it serves have gone up considerably. Hundreds of contributors. Billions of daily requests.

I humbly suggest that indexing on the lack of exceptions as a quality measure is a poorly lacking criteria. Ditching exceptions in Twisted Python and porting code to Go has created vastly more readable, maintainable, and performant code in multiple cases for us.

Post reply on HN