One of the first things I've learned when starting working with Go was that it has so-called proverbs. They are a list of rules, which sound like some smart quotes, which should guide you during your journey. For a long time, I didn't quite understand why I should accept interfaces but return structs. I wanted to return interfaces as well since this would define what my return type does, not what it is exactly. It struck me almost a one full year or working with Go exclusively, how wrong I was. This post explains my line of thought, I hope it might save some of you sometime before you have your Aha! moment.

Implementing interface in Java

In my early career days, I spent most of my time writing Java code. While I don't remember how did I do that exactly (it's been almost three years since) I do remember how the inheritance works in that world.

In Java (and many other languages), whenever you define an interface that should describe given class, you need to bind those two explicitly:

// SomeInterface.java
...
public interface SomeInterface {
    void hello();
}

// SomeClass.java
...
public class SomeClass implements SomeInterface {
    public void hello() {
        System.out.println("Hello!");
    }
}    

Now, whenever we want to use hello() method from the SomeClass class, we can instantiate it directly...

SomeClass sc = new SomeClass();
sc.hello();

...or as an implementation of SomeInterface interface:

SomeInterface mi = new SomeClass();
mi.hello();

Implementing interface in Go

Go has another approach to implementation interfaces. The rule is, that if it walks like a duck and quacks as a duck, it's a duck, which means that we judge the particular struct by the way it behaves, not by how it is defined:

// some.go
type SomeStruct struct {}

func (SomeStruct) Hello() {
    fmt.Println("Hello!")
}

type SomeInterface interface {
    Hello()
}

Now, without having to define the connection explicitly, we can use the struct directly...

var ss SomeStruct
ss = SomeStruct{}
ss.Hello()

... and as an interface:

var si SomeInterface
si = SomeStruct{}
si.Hello()

Why expect interfaces? Why return structs?

You can be tempted to return an interface from a function declaration so that you know that the structure that is actually being returned does implement the interface:

func NewDataSource() DataSource {
    return MemoryDataSource{}
}

I've done that a time or two, but after a long time of working on Go full-time I realized how bad that was. You must realize that when a caller wants to use some function that returns anything, it can be either interested in a concrete type (expects a specific struct), or interested in a behavior (expect an interface).

In the first case, you just need to return a struct, end of story. But in second, you should go back and see that interface is implemented when its functions are a part of the struct, so why don't you just return a compatible struct?

My thinking was also based on the fact, that if my NewDataStore() func returns an interface, MemoryDataSource struct should publicly expose only functions being a part of DataSource. This kind of approach leads to limiting the capabilities of a struct.

Bad approach example

Let's define an interface...

type DataStore interface {
    Data(query string) ([]DataItem, error)
}

...which is implemented by MemoryDataSource whose contructor returns that interface:

type MemoryDataSource struct {}

func (mds MemoryDataSource) Data(query string) ([]DataItem, error) {
    ...
}

func NewMemoryDataSource() DataSource {
    return MemoryDataSource{}
}

Now, some service requires some data source to work, so we pass an interface there:

// someservice.go
func NewService(ds DataSource) SomeService {
    ...
    return SomeService{
        dataSource: ds,
    }
}

Let's write a test for our service:

// someservice_test.go
...
type mockDataSource struct {}
func (mockDataSource) Data(query string) ([]datastore.DataItem, error) {
    return []datastore.DataItem{
        {Value: query, score: 123},
    }, nil
}
...
func TestServiceAction(t *testing.T) {
    svc := NewService(mockDataSource{})
    ...
}

What if the interface for DataSource changes? Well, if it has another function added, all of the sudden we cannot use our mocked implementation (or any custom created one) because it is a breaking change. We know it should not be, because our service only cares about Data(query string) ([]datastore.DataItem, error) functionality, nothing else!

Good example

A good approach to that situation would be to define what is it exactly that we expect from our input parameters. To do that, we define interfaces where they are used and limit them to the bare minimum that is required for our code to work. We don't care about anything that a data source may provide, except for its data:

// someservice.go
...
type dataSource interface {
    Data(query string) ([]datastore.DataItem, error)
}

Having defined that, we can make our service independent from an outside interface:

// someservice.go
...
func NewService(ds dataSource) SomeService {
    ...
    return SomeService{
        dataSource: ds,
    }
}

Now, this is where the magic of Go marks its presence over what I've seen using Java. Any time we create our own struct that has that Data(..) function, it doesn't have to know anything about dataSource interface! It's SomeService who is aware of both and decides if the given type can be used in this place:

ds := redis.DataSource{}    // which has Data(..) function
svc := NewService(ds)       // it works! redis.DataSource had no idea, but it fits here!

Summary

Unlike Java, in Go it's the caller that defines the interfaces it uses. It took me a lot of time to realize that, but once I did it made me really appreciate the readability of Go on yet another level. This makes code easier to manage and understand, since you keep all related pieces close to each other.