Free Trial
Engineering

Using the Go language to guide development design decisions

Simone Carletti's profile picture Simone Carletti on

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.

{
  "name": "www",
  "type": "A",
  "content": "127.0.0.1",
  "regions": "global"
}

{
  "name": "www",
  "type": "A",
  "content": "127.0.0.1",
  "regions": ["TKO", "AMS"]
}

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.
type ZoneRecord struct {
	ID           int      `json:"id,omitempty"`
	// [...]
	Regions      []string `json:"regions,omitempty"`
	CreatedAt    string   `json:"created_at,omitempty"`
	UpdatedAt    string   `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(accountID string, 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.
type ListOptions struct {
	// The page to return
	Page int `url:"page,omitempty"`

	// The number of entries to return per page
	PerPage int `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:desc
	Sort string `url:"sort,omitempty"`
}

// DomainListOptions specifies the optional parameters you can provide
// to customize the DomainsService.ListDomains method.
type DomainListOptions struct {
	// Select domains where the name contains given string.
	NameLike string `url:"name_like,omitempty"`
	// Select domains where the registrant matches given ID.
	RegistrantID int `url:"registrant_id,omitempty"`

	ListOptions
}

People develop in all sorts of different languages these days, and we wanted our API clients to be consistent across the board. In a previous blog post Anthony explained how we standardized the API client testing and development with HTTP fixtures.

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.

Visit the DNSimple Developer Center to learn more about our domain management, DNS hosting, and SSL certificate API. If you have any questions or feedback about our API or Go client, don't hesitate to contact us or open an issue in the dnsimple-go issue tracker.

Share on Twitter and Facebook

Simone Carletti's profile picture

Simone Carletti

Italian software developer, a PADI scuba instructor and a former professional sommelier. I make awesome code and troll Anthony for fun and profit.

We think domain management should be easy.
That's why we continue building DNSimple.

Try us free for 30 days
4.5 stars

4.3 out of 5 stars.

Based on Trustpilot.com and G2.com reviews.