Choosing Go at American Express (2019)
americanexpress.io
americanexpress.io
[0] https://en.wikipedia.org/wiki/Google_LLC_v._Oracle_America,_....
https://en.m.wikipedia.org/wiki/Google_LLC_v._Oracle_America....
1. Each team develops the same vertical slice of the business in their ecosystem of choice.
2. Test officials will make an edit to each codebase (somehow invisible to git history) that introduces a subtle bug. Alternatively, they may propose a small feature enhancement.
3. Time how long it takes each team to get a corrected/updated build deployed & re-tested successfully.
4. Go to 2 until statisticians are satisfied with the results.
I'm no longer with Amex (still up for debate in my mind if that was a good idea to leave or not), but last I heard the department was very successful in onboarding a lot of their Java engineers to Go and transitioning to a Golang microservice architecture.
I suspect that somewhere in the heart of the beast there still lurks an IBM-branded system of record.
Just because a machine from the 90s is running the database doesn't mean you can't put a webGL frontend on top of it.
Edit: I found a followup from 2021 - https://youtu.be/e7PtBOsTpXE
E.g., my manager is really interested in Dapr as a way to make basic Kafka usage easier for teams across our org.
Dapr's written in Go, and have made the reasonable decision to not use any libs that have C bindings for the same reason I don't like them.
However, this means that they can't use the Confluent Go Kafka client, as it's just a wrapper around librdkafka.
So they use Sarama, as it's written in pure Go, and is widely used, but is one of the most problematic Kafka clients I've had the misfortune to support. Although now that IBM has taken ownership of the project from Shopify, some of the more egregious bugs and oddities are finally being fixed.
But yeah, while you _can_ bind to C libs in Java, I've never encountered a library that did, so cross-platform lib compatibility wasn't something I ever had to worry about when working with Java.
I frequently make use and it's as easy as slog.Info("text", "field", value, "field", value)
Many non-trivial Go apps have 4-5 logging frameworks pulled in, it’s a mess.
1) What settings did they use for those published numbers? 2) They don't change the default `linger.ms` for confluent-kafka-go and indeed, don't provide a argument to do so, unlike for the code to test their client.
Automatically that renders the benchmark useless - the code running franz-go defaults to linger.ms of 0, librdkafka defaults to 5ms, so already you're not comparing apples with apples.
I did it in a day, behind a corporate firewall, without root access, and an outdated GCC compiler.
Most of the day was spent compiling a new version of GCC.
Industrial?
This can technically be done as a breaking change spread over several versions of go and I think the cost would be worth what we’d get out of it. Utilities can also be made to edit code to support this automatically without having to have some user go fix it all.
Basically, I want an introduction of either a function level or value level ‘nut’ keyword like Rust where the function can not mutate a field unless it has explicitly stated mut.
This would fundamentally solve a whole class of bugs and errors that happen in go where some library ends up modifying a struct (pointer or a struct with a pointer field) without the caller intending that.
I think the way to do the latter in Go is to not export the field and only have it modifiable by a dedicated method. (or even enforce copy-on-write).
And passing values instead of pointers where needed.
I don't think that breaking compatibility to add a mut keyword would fit Go. It makes more sense for rust.
(especially wrt interior mutability... That would make it a very pervasive change, probably too much of a change)
Another thing is that wrapping values in an empty interface essentially make those immutable. It doesn't necessarily have mechanical sympathy but as long as you don't wrap pointers, it would be difficult to modify (no aliasing).
Then again, it's more defensive and pragmatic coding than something that proves functional purity. Seems that it is enough.
I think this means you have a bigger upfront cost in writing code the first time, with the benefit of easier debugging and easy maintenance later (which is a tradeoff I'm very happy to take).
For example:
- types vs type aliases
- the init function
- how you define a struct can have significant impact on performance. There are lints that align struct better.
- changing a returned value in a defer function. Very useful but can easily hide complexity.
- interfaces are.. weird. They can also easily hide weirdness.
In that light, I agree init calls and defers are a bit magic. I'll even add panics in there. But I don't use these much personally, so I'm hesitant to say it is a lot of magic - especially compared to something like Java for example.
Choosing Go at American Express - https://news.ycombinator.com/item?id=21637916 - Nov 2019 (15 comments)
Go is a perfectly reasonable choice though, and easier to learn on average.
But when I read the following:
Based on these criteria, we narrowed our testing languages to C++17, Java, Node.js, and Go. The first three languages were already in use at American Express, whereas Go was not.
What I understand is that they did not really wanted to do a real benchmark but they decided to use Go first, and then they had to setup a kind of "bullshit neutral study" to pretend to leadership that Go was the best choice for all the languages that could fit the job.Maybe it is the case in the end for them, but it is another subject.
Based on this, it’s fair to put Node into a league above languages like Python and Ruby when considering performance.
This might have played a role in their decision to include Node.
In general java feels more enshittified in comparison
I was a heavy java snd scala user before 2018 and 5 years later I’m not missing anything. For what it’s worth, life got easier when I moved to Go.
For what I require (which is atm concurrent web services, restful and grpc APIs, occasional CLIs, k8s tools), it's on par with java
For collections, I don't think go has the same variety as java built in but there are 3rd party implementations for more niche data structures like rb trees. Bare essentials like arraylists and hashmaps come built in
In general, yeah ecosystem is more than good enough for me. Only big thing that I can think of that's missing is gui stuff like swing in java
With JVM bound projects I’ve rarely felt compelled to actually examine the rather foundational packages. With Go, however, I’m often surprised, even when looking at the more popular packages or framework, kind of a different attitude, you know. Same goes to the Go runtime system behavior. The deeper you look the more you can see, I guess. There’s always a caveat somewhere around the corner.
That said, I do have a few Go systems in production. I love the lower memory footprint though it often comes at a cost of unnecessary CPU thrashing, which for the IO bound services I consider acceptable. I see some companies move to Go, the sentiment I’ve perceived is mixed.
Go has no comfortable streams or standardized functional interfaces and generics is still in the "baby" phase. Though to be fair, Java generics is also merely toddler level, compared to C++/Rust.
To be honest, I like the latest Java releases better than Go. Go had an advantage over Java thanks to in-built virtual concurrency, but that advantage has been lost with Java 21 virtual threads.
Go still retains the advantages of a stream-lined yet full-featured and cohesive standard library, fast compilation and crisp, native executables. (However, for the last bit, GraalVM has been rapidly improving for Java)
1) Every package is namespaced 2) Namespaces are DNS names that you can prove you own
It eliminates typosquatting, and the drama of "someone created a bunch of helloworld crates just to grab the good package names"
github.com/rs/zerolog
mysite.com/myforks/zerolog
Sorry, I realise I've been overloading package to mean "dependency you download" (I'll stick to artifact now) when package has an existing meaning in Java, and I'm muddying the waters a tad there.
There's no requirement for Java package names in an artifact to match the group id. E.g.,
> my-domain.com -> com.my-domain (NOTE: The groupId should reverse the domain name exactly, even if the domain name contains hyphens or other characters that would result in an invalid Java package name. Hyphens are perfectly acceptable in groupIds, and you would not need to change your Java package name to match it)
https://central.sonatype.org/publish/requirements/coordinate...
> com.sun.whatever
Oracle owns sun.com and runs artifacts that use that group id prefix, so yeah, when you get bought or change names, you can just keep the old name up and running. If you can't, or don't want to, I have seen some projects change group id, and there's tooling for artifact publishers to do so in a way that doesn't break end users, it's a bit clunky, but doesn't tend to occur that often. https://maven.apache.org/guides/mini/guide-relocation.html
Having artifact group id tied to DNS names isn't super-duper flexible, but it offers some assurance that the dependency you're introducing is published by the people you think, so attackers can't easily steal crypto wallet info by publishing pandass pndas pandsa to catch people who typoed pandas etc. etc.
The extremely straight control flow of Go makes it much easier to maintain in the long term IMO.
I've experienced the same thing in java, and similarly with testing different conditionals/exception cases in java
Usually most of the mock annoyingness I've personally dealt with is from repetitive, unrefactored test coverage
I've always wired my stuff manually in Go though, which is useful if I want to optionally load different subsystems because of some config, etc.