Pioneering Go in a Ruby World

Hannah Tran
Snapdocs Product & Engineering Blog
10 min readSep 13, 2021

We’ve all been there — the late-night shooting the breeze over video chat or a barstool — the brag about your incredible time with punch cards and Pascal — the grumbling over new-fangled languages these days — no one really wins the “coding language body count” game.

Snapdocs has had a fairly small coding language footprint, especially if you compare them to the Tech Giants or your Uncle Hal’s tall tales. Before 2021, our tech stack was primarily Ruby on Rails, with React on the frontend, along with a little bit of sprinkling of Python and some Bash (let’s be honest, who hasn’t sprinkled in a little bit of Bash when the time calls for it!).

Official Ruby logo

I’d like to walk you through our process of changing that. First, our conversations around if we should find another language, second — the choice to select one (spoiler alert — we added Go), and finally, the guidelines we followed to start coding with it. We don’t know everything about this — this was our first time, honestly — but we want to share the journey so when the time is right, you can walk your own.

Ask When, Resolve Why

At some point, as developers continue to specialize in new languages the popular and trending rankings of programming language choice will continue to change and morph. That reflects not only people looking for new challenges but the push for frameworks and functionality to meet the changing application and developer needs of what we want to build and imagine for our future. Companies must adapt to this changing landscape.

At Snapdocs, it was easy to be a “Ruby shop” when there were only seven employees. As our frontend, API, services, and scaling needs continued to grow exponentially, however, we started to look around to evaluate if we wanted to continue as that, asking if it still really made sense for us. Beyond spiking on Typescript, Snapdocs took on the mantle to look for a secondary backend language.

The next hurdle to overcome was to resolve “The Question” — which one?

How to Choose?

Evaluate for Your Needs

We evaluated new languages with a simple list of requirements.

Gap

Where is our current codebase not shining? Where is there space for “better”? We didn’t want a parallel language that would bring us no extra value. This eliminated several popular languages as we felt they had the same drawbacks and not a lot of additional advantages compared against what our current stack offered.

Support

There were a number of factors to consider — external, internal, and the long-term path.

Externally, using a framework or package that has very little community support or quality documentation is rough and painful. We didn’t want to introduce something that would cause frustration for a simple web search, and there were sure to be a lot of those.

Internally, we needed people who were interested, passionate and with a background in the new language to support, train and guide engineers who were more familiar with another stack.

The future, of course, is impossible to predict. The creators of Algol The ALGOL Programming Language and its fans probably never expected the decline in usage and recognition of the language in a few years time. But as an engineer, you must acknowledge as well — no one has time machine yet, you just have to make your choice and move forward. There’s always space to adjust if needed.

Hiring

Retention and attracting new talent hinges on people staying interested in the technology, excited by the challenge, and invested in the future. We wanted to continue to keep the interest and vibe for everyday work, and went looking for a language that could provide that for us.

Decision

Go met our needs for all of the above — for both now and the future state we wanted to be in.

An image of the Go gopher, the mascot of the programming language Go

It met a need and resolved a gap, especially compared to other popular languages like Python. Go offered a language syntactically closer to Ruby with several of the advantages of C. We wanted to experiment with types and compiled languages while seamlessly integrating with our existing architecture.

Go had a community of support — anything you could search for with one language, you could do the same search in Go!

Here are a few of my recent searches for things I could do easily in Ruby:

  • “turn string to byte array Golang”
  • “Golang “.env” files for local systems”

Each of these effortlessly gave me the desired results.

Additionally, it had internal proponents — there were some excited engineers who were very invested in what Go could bring the organization. It created a buzz of excitement among our current employees and incoming hires. We have already had people join with more C-like or Go backgrounds, and they were excited to see our implementation and come in on the ground floor. It also gave our engineers space to innovate in their personal skills — we have a number of services now making their way out to production.

Our Tech council wrote the following around their decision to use Go:

The language we’ve seen the most positive support for within Snapdocs Engineering is Go. Please keep in mind that we’re not necessarily looking for ideal here (that’s likely impossible), so whether or not Go supports a specific feature (like generics) does not necessarily make it the wrong choice. We were evaluating for why Go would not be suitable here. This is strongly in line with how we discuss ideas within the Tech Council to mitigate bike-shedding https://en.wikipedia.org/wiki/Law_of_triviality.

So, we had a decision and a starting place. Time to move forward — our next step was to trial it responsibly and roll it out.

Pioneer Sensibly

When you pioneer, whether you’re crossing the Oregon Trail or planting your first tree, there’s always preparation beforehand and learnings along the way. We used four guidelines to ensure we kept our experiments — the “Golang Trail” if you will — moving forwards.

“The Right Way” is subjective

One of the hardest present and future balancing acts we face is how to introduce something new. Do you rip the old out by the roots? Do you force everything going forward to adapt? Do you do changes on a one-off basis?

The answer to this question is heavily dependent on the situation and rarely straightforward. We thought through the technology, our knowledge base, and company needs. We have a culture of engineers changing teams and also changing parts of another team’s codebase. We needed to ensure that our support and maintenance process would remain fluid.

Snapdocs chose an iterative support approach to start off — how many people can we get trained up, how much foundation can we get under us — so we could eliminate any blockers that could affect the success of Go long term in the organization.

Have an Open Mind

Have an open mind

Let’s face it — change is hard. Developers don’t have any special powers to adjust to change any better than other humans. How do you balance pragmatism and strong feelings with a real need to adapt and grow?

In our case, we did have some teammates who said “I can’t do this exact thing in Go, so we shouldn’t use it” or “I don’t like how Go does this” or “No one needs to use anything but Ruby, Ruby is great”.

Our most important duty was redirecting these comments. We didn’t get into arguments of if Ruby is great, if Rails is a good framework, if tabs are better than spaces. Instead, we responded with “Go does X well. Go handles Y in a really simple way. Here’s an example of Z with tests written in Go”. We stayed away from conversations of replacing Ruby and instead looked for ways Go could support Ruby in our tech stack and also shine on its own, identifying and calling out where it had strong value.

I personally have opinions on the language, especially given my Ruby heavy recent background, so for me, it was an active task of evaluating and seeing: where does Go make sense? And that’s how we started building it out and where we started using it. From there it spread to several other implementations and continues to grow in usage across teams.

Build a solid foundation

Even though we weren’t trying to mirror our Ruby process, architecture or implementation, we knew there were some core things every team would need to resolve.

Patterns

We needed solid patterns to ensure everyone would develop along the same lines. Especially coming from such an opinionated framework as Rails and Ruby, figuring out consistent patterns for Go implementations was important to both document and write examples for.

One such outcome — our Developer Efficiency team devised a Go template that any new Go service could start off with. People could just create a new repo for a service and have all the boilerplate code ready at their fingertips.

We also invested heavily in testing — testing to 100% test coverage isn’t a natural thing with Go, and felt much smoother with languages like Ruby and Javascript when we first started out. We found a way to get to our needs — automatic linting, thorough testing, and maintaining the output of each. Then we rolled out several examples and ensured everyone had access to help and training.

Migrations

Next, we faced down a challenge we had not anticipated — standardizing our database migrations and flow. We wanted to continue to ensure engineers could easily understand the database for a service, and be able to switch between our Rails and Go implementations, while not needlessly hamstringing the power and flexibility of Go.

Rails provides an ORM for its migrations, and we built our existing migrations and flow around ActiveRecord’s strong opinions. Pure SQL and ORM implementations exist for Go, but there is no de-facto standard.

Our journey began with asking an engineer to actually go through, investigate, and share examples for the top options. This helped teams make informed decisions and, hopefully, for us to choose a process that will make sense for us. We’re not sure if we will narrow down to one pattern for migrations yet — maybe that won’t make sense for where we want to be — so for now, we’re trying them out and comparing across services.

Deploys

Also still in process, we wanted a really clear way to not only deploy our go services but also manage services, gems, and other processes and setup we had ironed out for Ruby.

With two languages, it was especially important to ensure our architecture and structure was held to a standard that would allow continual easy communication without maintaining parallel implementations across different services. Frameworks like gRPC allowed us to not change that interface too much, and we were able to simply adapt our existing tooling to ensure Go and Ruby gems could interface seamlessly.

With the support of our devops team, our service communication we had practiced locally deployed fairly easy. We built tooling and worked to attain easy, painless “boring” deployments with the outcome to ensure Go needed no special attention during our release cycle.

EVANGELIZE like crazy

Even with the most incredible, well meaning team, fear and uncertainty around brand new things can impede progress. Change can be an uphill battle and we wanted to level the playing field as much as we possibly could.

Don’t let your Golang Trail end up like this!

8bit dysentery on a trail to Oregon

We strove to constantly and continually ensure everyone had a chance to learn and be as productive, comfortable and happy as possible with the new languages as they were in their current stack.

We wanted to make sure all the current networks we offered would help support people in this way. One such tool — our messaging service! We made a dedicated channel for Go (called #gophers) so people had a space to fearlessly ask questions, muse, share current findings, and announce fun learnings.

Here are some examples of posts on the #gophers channel among the early adopters:

Hey BTW we got a mix of .config and env vars using viper.EnvKeyReplacer(strings.NewReplacer("-", "", ".", ""))

Hello all, have you done scheduled jobs for any of the Go services?

We also monitored other channels for Go comments and concerns and made sure we could do quick resolutions for each of them.

Other teams picked up the language and process as well, and we’ve had a number of context-sharing calls in our current flow of meetings, and additional calls ad hoc as needs arose. Our team even had an engineer from another pod spend dedicated time on our team’s stories to work in Go to ensure he could learn the language. Other teams took up the mantle as well and began to write new services in Go, sharing their struggles and learnings along the way. We now have a number of Go-specific services deployed all the way out to production.

And of course — it’s a journey. Pioneers aren’t done when they stop walking — they’ve got to live and adjust and maybe even walk some more depending on what they discover!

Get Moving

Where to go from here? How should you or your team approach the adoption of a new language or framework?

Follow the same steps — or blaze your own trail! Again, the “right way” to do this is subjective to you and your organization. Figure out what you’re really solving for, choose the one that works well for your needs, and implement it sensibly. Most of all, just try new things and adjust when needed!

We’ll keep you updated with our process along the way. See you on the Trail!

--

--