11 ms·
Go in Production – Lessons Learned
- chimen 6y agoThis article has almost nothing to do with Go in particular. Just...general stuff to follow when writing code. What is it here that talks specific things about "Go" + "in production"?
- tsjq 6y agoHaha. Just to attract more clicks
- bsaul 6y agoThere’s an ongoing debate among go dev if the stdlib is enough or not to perform « real world » work (which isn‘t the case in other languages). This article is from someone who think it’s not. That’s interesting.
- marcus_holmes 6y agoIt's not really a debate. It divides fairly neatly along the lines of "has been writing Go for a couple of years" vs "has come to Go fairly recently". There's a well-trodden path of developers who were trained in PHP/Rails/JS/Django/etc starting on Go. Their first question is always "what framework should I use?" (you can see this being asked at least once a week on r/golang). They then go down that path, and find (what they think to be) Go to be clunky and boring and hard to think about. There's a minority that decide that what Go needs is another framework, because the one they tried obviously isn't working for them. Then there's a split, and some people write articles like this one, and probably move on to Rust or whatever language is next for them. The others start understanding. They refactor their code to not use that logging library because they start to understand the understated power of the stdlib's logging functions. They refactor to get rid of the ORM that's become a problem. Slow realisation dawns and they finally get rid of the framework and go back to writing http.Handlers. This process usually takes a couple of years (well, it did for me anyway). And then our reborn Gopher goes to the forums to spread the light: "all you need is the standard library! You don't need a framework!", and is met by derision and misunderstanding. The Go community gets a reputation for being unjustifiably anti-frameworks and a bit weird about ORMs too. Eventually our hero shrugs their shoulders and lets the newbies find their own path, contenting themselves with upvoting those who understand.
- waheoo 6y agoSome of them move back to PHP once they realise the PHP standard lib is actually rediculously full of helpful tools for web development and the idea of treating individual PHP files and the directory hierarchy as the routing structure makes everything so bloody simple. Pair that with apache and the newer mod php and you have an extremely easy to up and running situation. Development feedback loop is rediculous and everything is simple again. Frameworks need to die.
- Philip-J-Fry 6y agoWe use standard library in production. Never had a problem with it. It's performance is great, the http.Handler interface is a great pattern too. I rarely find myself straying far from the standard library. If there's something that takes a bit more code but doesn't bring a dependency then that's what I'll do. I definitely agree that there comes a day where you just see the light and realise that simple is good, the standard library is good, and that you don't need most of the stuff a framework gives you. But it gets really hard convincing others that Go is a great language because it's not Rust. Don't know what there's so much hate for it in this community. I've never fell in love with a language like this before. It's just incredibly productive for me and fits my thinking perfectly.
- marcus_holmes 6y agoagree 100%. This is exactly how I feel about it.
- st1x7 6y agoI'm not a backend developer and I can't comment on the actual framework vs no framework in Go discussion. What I want to say it looks a bit arrogant to portray yourself as this enlightenened person who has seen what would take other people years to understand, and to tell them that you won't bother to convince them but they'll understand one day. There has to be a better way to communicate your point.
- marcus_holmes 6y ago
- waheoo 6y agoI think it's stuff you need once you move beyond basic learning go projects. I found it pretty useful, the tip about sqlx, echo, and a quick docker file example were useful reminders, the rest ive seen elsewhere but yea. I get what your saying but I found this useful.
- lelandbatey 6y agoI wouldn't describe this as good lessons for using Go in production, I would describe this as "opinions I came to after building my first real Go web project." 1. You probably do NOT need a framework Use the default Go HTTP libraries. For the other functionality that you'll need, use libraries. If you throw in with frameworks like Labstack Echo, you're forever coupled to the incredibly specific and one-note behavior of the framework you choose. The dependencies you choose should be light, with their most attractive aspect being the interfaces they provide. I point most strongly to go-kit as an EXCELLENT set of libraries for writing HTTP services. Their Log package is a small example of what I look for in quality libraries. 2. You NEED a good code structure You need to be structuring your code, yes. But you should NOT be leaning on the conventional directory structure to give meaning. You will need to read more of other peoples code than you will need to write your own code, and other people will not be following your code structure. Much more useful is to get good tools and practices reading code. Go, unlike other languages (e.g. C#), is meant to be readable and understandable without a heavy IDE there to help resolve elaborate indirection and overloaded imports. If you have the code on your disk and can use Grep, you'll do fine. If you're using an editor which supports language servers like Gopls, you'll be able to fly through the codebases. 3. Pick a DB driver wisely, and the wisest choice for SQL driver is database/sql SQLx is not database/sql, which is a real problem when most all the ecosystem is built around database/sql. The only real pain point people have with database/sql is the scanning, which is why other people have built libraries to help with this: https://github.com/kisielk/sqlstruct https://github.com/kisielk/sqlstruct Probably just use database/sql. 4. Docker Most all of this advice is fine. Basically, I recommend sticking to the more lightweight and more standard implementations, as they have the abstractions you'll need to the long haul. Though if you're just trying to get a thing going ASAP and it has to be Go, do what you gotta do with the code that catches you're fancy.
- maccard 6y ago> You will need to read more of other peoples code than you will need to write your own code, Any examples of good go web projects to look at? All of the ones I've found have fallen into either "directory dump" or "I'm an mvc but not really".
- ldelossa 6y agoThe more you get to know Go's stdlib the less and less youll think a web framework is necessary. Youll find that utilizing http Round Tripper along with the Handler interface in the http library will make middleware easy. Youll eventually dig into filepath and path methods for extracting path parameters from url paths. And logging and recovery will be a concept youll need to extend outside of just http and into the rest of your application. I totally advise new comers to use a web framework, but in the long run, you probably wont want too.
- nsomaru 6y agoWhat path to this enlightenment would you recommend to one who has cut his teeth and delivered many projects (over many years) in Rails/Django?
- kortilla 6y agoThis is a side effect of go’s lack of meta programming to be honest. So much magic that is possible in rails/django/etc is just not possible in golang. This leads to go web frameworks being sort of semi-hard mountainous turds of code generation. You’ll want to stick to stdlib after sifting through them and deciding they aren’t worth it.
- ampdepolymerase 6y agoAfter Django, writing database code manually in Go is not fun at all.
- jchw 6y agoAdmittedly I liked Django's database layer but using it in Real World production has also left me feeling kind of... eh. Having automigrations and an ORM is fantastic 90% of the time. The other 10% of the time almost ruins the magic entirely. The ORM is not a panacea like SQLAlchemy nearly is. Issues with migrations in production can get surprisingly tricky surprisingly quick. The rest though I do not miss at all. Middleware patching attributes directly into the request object? Python's sad excuse for incremental typing? Ignoring the database bits of Django I struggle to find anything that I don't enjoy doing more in Go. I was a HUGE fan of Django REST Framework and assorted boilerplate for a long time, but I am so extremely glad to be off that ride. Yeah, it lets you do really cool, complex things succinctly and cleverly. The problem is that it lets you do really cool, complex things succinctly and cleverly. The cleverness becomes the enemy. I now have learned to appreciate code that is utterly stupid, obvious, nearly braindead. Every codepath is screaming at you. That is Go in a nutshell. if err != nil { ... } ad nauseam. Sounds terrible... but it kind of isn't. There are some things I would not use Go for. Game development is one of those things. Web servers, though? If you are going to be doing serious work in production environments, Go is absolutely among the best choices. I am still, however, looking forward to trying Rust more and more. My initial impressions with Rocket.rs have been lukewarm. (One thing that is bothersome but not quite a deal breaker is compile times. A lot of crates make the experience bad almost immediately.)
- tomohawk 6y ago> silently handling panics That has a bad smell
- partiallattice 6y agoEspecially since caught panics amount to exceptions and most Golang code isn't exception safe. It's a recipe for leaking database connections, deadlocks, etc.
- TheDong 6y agoGo error handling is awful, but this isn't a fair. In go, if you write code like the following: conn, err := db.Connect() defer conn.Close() That defer will be run during a panic. Same thing as 'defer mutex.Unlock()' Yes, like most of go, it's manual and painful and poorly thought out, but most people do follow these patterns, so for the most part go code will safely unwind from a panic.
- partiallattice 6y agoMost of the time, sure, but there are lots of cases where `defer` being function scoped leads to manual unlocks, e.g. inside loops or short critical sections in the middle of a function. Yes, you can use closures/otherwise, but few codebases are that disciplined. Those edge cases are plentiful enough that catching panics is dangerous.
- sethammons 6y agoI assume they meant “automatic handling of panics.” Unless that framework has interesting defaults. On mobile, else I’d check.
- hendry 6y agoFor logging libraries, I find TJ's structured logs indispensable: https://medium.com/@tjholowaychuk/apex-log-e8d9627f4a9a https://medium.com/@tjholowaychuk/apex-log-e8d9627f4a9a No need to use Echo..
- jwcrux 6y agoYou almost certainly don't need a web framework. Over the long term, it's easy to find yourself boxed in with how opinionated many of them are. Instead, if you build on the standard library, you can compose your application from there- a good muxer, some standard middleware that are generic http.Handler's, a session library, etc.
- vasachi 6y agoSo, you chose a good muxer. Now, which of hundreds of logging, cors, sessions, jwt and permissions middlewares work with said mud and with each other best? Or, and bear with me here, you can outsource those decisions to other folks, and just write your business logic.
- mrmonkeyman 6y agoWhy write code at all? I only think about stuff and outsource it _all_.
- deleted 6y ago[deleted]
- idolaspecus 6y ago> I recently got a DevOps job that mostly involves writing a new backend system in Go completely from scratch. If this wasn't on the front page of HN I'd have stopped reading here, but it is, so I didn't, then I regretted it.
- thiht 6y agoCan you at least explain why?
- idolaspecus 6y agoFor the same reason as if it had said “I recently got a React dev job which mostly consists of writing Kubernetes deployments.” It sounds like the beginning of an article which uses buzzwords without a lot of depth.
- EdwardDiego 6y agoWhat offends you in that statement? FWIW, I, who is usually writing JVM apps in Java/Kotlin occasionally Scala, had an app to write that that extensively used K8s APIs, and I found that the best client API was (naturally, considering the makeup of the K8s ecosystem) the Go one, so I wrote the app in Go. I had to overcome some common Go issues (use a map[t]interface{} when you need "is x in a list of unique elements", if you need more advanced set operations on custom types, you're going to need to generate code or hard code each implementation, because generics are for suckers, allegedly. Hit some issues around channels and blocking reads and buffering, but worked around them easily enough using pointers and locks where channels weren't appropriate) But aside from the limitations of Golang, over all it was a rather straight-forward experience. Stuff worked, tools worked, Intellij IDEA integration worked, Go's module system worked (DIAF $GOPATH), code ran, no worries. Will say though, Go logging realllllly needs some work from the community. An SLF4J equivalent at least. I used logrus as my logging library as it gave me the easiest route to making my logs go to Kafka in Logstash format, but it has some insane defaults - like the log event timestamp defaulting to "seconds since start-up". Oh, and be very careful with named returns. Naming a return, but then assigning nothing to it, will compile. Which is, to say the least, rather contrary to how most of Go works.
- vishnumohandas 6y agoFor configuration management, I've found viper[1] to be quite handy. [1]: https://github.com/spf13/viper https://github.com/spf13/viper
- jeffbee 6y agoWhy would you want to serve a 200 with an error message in a JSON response body?
- dastx 6y agoI've seen this pattern before, and one can argue that it is the correct one. Your options are your http status are your application's status, which I think is what most of us are used to. The other one is http status codes are http status codes. As in the http request was done correctly, but the application code wasn't. More specifically, http layer was executed successfully, but the application layer was not.
- mindwok 6y agoBut there's response codes for those situations. If the request is malformed you should send a 400 bad request. If the application errored, send something in the 500 range.
- spicyramen 6y agoI just had an issue today in which customer deleted a service account (which they shouldn't touch) required by our API to process their entities. Right now I send a 5xx server error but as is a customer misconfiguration problem, and problem is not on the server side and is not a bad request, but a setting client changed I would use this pattern. This will avoid triggering our alerts.
- daxfohl 6y agoThis is more RPC style. I used to prefer this approach because that way you're not constrained to the Nxx error codes of http; it's just request, response. Having worked in a few companies since then, it seems odd to me now. Besides, all your middleware etc is probably going to use status codes conventionally, so your application code's 2xx error responses will stick out like a sore thumb.
- lenkite 6y agoThat is simply a 500 error. Or a 422 if request was semantically wrong. No, this pattern is not the right one for REST. Don't call it REST, call it command/action RPC. The 90s called - they want their architecture back.
- didip 6y agoI personally like this combo as my "framework": * go-chi/chi * rs/zerolog * html/template * spf13/viper * raw SQL
- whateveracct 6y agosqlx is great for raw SQL
- SPBS 6y agoTo those who say frameworks are needed because the stdlib is not enough: No, the stdlib defines very clear interfaces that libraries can implement. The result is that you can drag and drop middlewares from multiple packages into your codebase, because they all conform to the universal middleware signature `func(http.Handler) http.Handler`. Want CSRF? Want Sessions? Want Auth? They all exist as separate packages (check out the Gorilla toolkit), but the beauty is that they all work without needing to know the existence of each other. Frameworks like Echo and Gin eschew the universal middleware signature `func (http.Handler) http.Handler` for their custom built ones, and as a result cannot tap into the ecosystem of middlewares that target Go's net/http. Between a stdlib package and a third party package, which do you think is more stable? You are not constrained to net/http's router. Chi is a good example of a third party router that implements features not in net/http but still conforms to the `func(http.Handler) http.Handler` interface, and as a result middlewares that work for net/http will also work for Chi. https://justinas.org/embrace-gos-http-tools https://justinas.org/embrace-gos-http-tools
- mmgutz 6y agoA much needed interface in stdlib is logging.
- johnisgood 6y agoYeah I made a library for that. I am not sure if people are actually using it but it got over 70k goddamn hits in my access.log!
- cdent 6y agoAgree. I wasn't at all convinced by the examples showing the Echo was somehow better than using the "built in" signatures, especially since there's quite a bit of handling that Echo (and things like it) makes implicit that you'll probably want to be explicit in some edge case. Coming from WSGI, I found go's middlwares familiar and positively pleasant. I too ended up on Chi as the router that worked well without getting in the way.
- morelisp 6y ago> Frameworks like Echo and Gin eschew the universal middleware signature `func (http.Handler) http.Handler` for their custom built ones, and as a result cannot tap into the ecosystem of middlewares that target Go's net/http. Between a stdlib package and a third party package, which do you think is more stable? I think you're overstating the cost of using "proprietary" middlewares. I'm most familiar with Gin; it is trivial to wrap a "universal" middleware in a Gin middleware (gin.WrapH). It is not trivial to make a universal middleware that does the equivalent of gin.Context#AbortWithError. I don't fully disagree either - obviously `gin.WrapH` everywhere is noise, but so is e.g. `chi.URLParam(req, "abc")` compared to `c.Param("abc")` in Gin, and Gin's parameters are much cheaper for a middleware to tweak. (I choose chi here because it's one I'm familiar with that tries hard to keep the standard Handler signature at the expense of interfaces to its own features.) A lot of problems would go away if the standard signature was `func (Context, ResponseWriter, Request) error`. Sometimes a minimal universal option is nice because minimalism is also a virtue; but also sometimes it's just missing necessary features.
- brunoluiz 6y agoAround the ORM choice, I would stay with raw SQL Queries and use sqlc with to generate the boilerplate (structs and the repository itself). I have been using it and it is amazing. Only issue it only supports postgres, although it has beta support for other DBs. https://github.com/kyleconroy/sqlc https://github.com/kyleconroy/sqlc SQLx is good for simple read queries, but iirc for write operations you still need to map things manually and reads with JOINs are a bit tricky. Gorm might be good for simple CRUD applications, but it's magic has a performance cost.
- tekkk 6y agoSo does the 2nd part's example snippet use transactions or what? Seems pretty dubious, even for an example. With Node.js I pass a function as a parameter to a transactional exec and any failure should rollback the whole thing. Don't know if that's the same with Go.
- sto_hristo 6y agoHaving seen the REST api examples they gave, i'd steer clear from using this language for that particular task.
- kapv89 6y agoWon't Go's ecosystem go through a lot of churn after generics are introduced?
- throw_m239339 6y agoGo will never have generics. mark my words.
- luord 6y agoFaced similar issues in both of the Go projects I've worked on, particularly the one that used Mongo as the database. My conclusion from both experiences is that Go is a tool like any other, with its strengths and weaknesses, and with particular idiosyncrasies that make it particularly important to properly architect the application. It can punish you pretty hard[1] if you don't follow proper patterns, or the architecture you decided. For instance, I think the issues were more pronounced with the project using Mongo precisely because of the flexibility Mongo offers, which allows to delay deciding on schemas, and thus means that the architecture itself can change constantly. [1]: Such as making coupling and code repetition to balloon pretty fast.