Using the Go language to guide development design decisions

At DNSimple we love Go. Today, I want to show you one of the reasons why we love it, telling you the story of how we ended up using Go to guide our development design decisions.

This blog post is a transcript of the lighting talk I presented at dotGo 2016, with some small adjustments and examples.

Two years ago we started to redesign our domain API. We wanted it to be more powerful, and we wanted our customers to easily adopt the new API. Along with a complete redesign of our API, we decided to start developing official API clients beyond the Ruby client that we have been providing since the beginning and release them open source.

These days, DNSimple's codebase is mainly developed in Ruby, Go, and Erlang. Therefore, we decided to start working on two new official clients, one in Go and the other in Elixir—a language based on the Erlang virtual machine.

For weeks I developed, in parallel, in 3 different languages. The more I was writing code, the more I realized that for some reason the Go client was leading the development. In fact, my approach was to develop a feature in Go, then the other languages followed. It turned out it was not a coincidence.

Go is a strictly typed language, especially compared to Ruby or Elixir. Some people believe this is a big constraint. It is certainly a different way of coding, but what's interesting is that thanks to Go we quickly started to realize that some design decisions we made in API v1 made that API hard to be consumed in certain kind of languages.

API v1 was written in Ruby, and it was largely influenced by how Ruby works. Consuming our API with Go allowed us to understand flaws in our response formats and design. For example, at some point we realized that we were returning an array Strings to represent regions attached to a record, and a single string literal "global" when the record belongs to all regions.

Trying to consume such format in Go, without relying on JSON deserialization hacks, was very hard. That was a bad design decision; to use different types to represent conceptually similar information.

// ZoneRecord represents a DNS record in DNSimple.typeZoneRecordstruct{IDint`json:"id,omitempty"`// [...]Regions[]string`json:"regions,omitempty"`CreatedAtstring`json:"created_at,omitempty"`UpdatedAtstring`json:"updated_at,omitempty"`}

This is just one example. We also realized that in some cases we abused optional parameters in our existing clients, or we overcomplicated the query interface. Go doesn't allow optional parameters in method definitions, hence we had to carefully rethink the design of our clients and make them simpler.

// ListDomains lists the domains for an account.func(s*DomainsService)ListDomains(accountIDstring,options*DomainListOptions)(*DomainsResponse,error){// ...}// ListOptions contains the common options you can pass to a List method// in order to control parameters such as paginations and page number.typeListOptionsstruct{// The page to returnPageint`url:"page,omitempty"`// The number of entries to return per pagePerPageint`url:"per_page,omitempty"`// The order criteria to sort the results.// The value is a comma-separated list of field[:direction],// eg. name | name:desc | name:desc,expiration:descSortstring`url:"sort,omitempty"`}// DomainListOptions specifies the optional parameters you can provide// to customize the DomainsService.ListDomains method.typeDomainListOptionsstruct{// Select domains where the name contains given string.NameLikestring`url:"name_like,omitempty"`// Select domains where the registrant matches given ID.RegistrantIDint`url:"registrant_id,omitempty"`ListOptions}

The simplicity of Go, along with some of its constraints, for some people are limitations of the language. For us, they are features that forced us to rethink the way we designed our API. We used our Go client as the leading implementation and we let it guide our design decisions. Whatever feature we could not easily and cleanly implement in Go had to be simplified or removed.

Today we have 4 official API clients, in 4 different languages: Ruby, Node.js, Elixir, and Go. They all share the same approach, and we are really happy with the result. Go had a major impact in our decisions and success. Thanks to all of the contributors to Go, and thanks to the Go community for making it possible.