Leveraging package-local interfaces in Go

Observations about how interfaces are commonly used in Go and a suggestion on how to keep interfaces narrow.

This article is about leveraging interfaces local to your packages in Go to protect your future self from incidental complexity -- problems you've created for yourself, that get in the way of solving your actual problems -- when developing an application. The examples used in this article are within the context of a typical web application.

Coming from Ruby on Rails, writing a web application in Go just based on net/http can be a daunting task: Rails provides organizationial principles for your code and comes with lots of goodies for making your life easier. Go's net/http is the complete opposite of this: it only takes care of handling HTTP requests and doesn't prescribe or help you with other tasks you face when developing a web application.





Observations in practice

Wide interfaces



type Gateway interface {
	CreateCustomer(customer *Customer) error
	Customers() ([]*Customer, error)
	Charge(customer *Customer, amount int) error

This interface reflects the actions exposed by the payment provider's API and next to that interface you have a concrete implementation that talks to the payment provider via its API.

Next you write some code that makes use of this interface:

type PlaceBookingController struct {
	Payments payment.Gateway
	// other details omitted

	// ...

		controller.handleError(w, req, err)

When using this controller, you need to provide an object that implements payment.Gateway -- four methods! -- even though only one is used here. This causes one issue right now and another one a bit further down the line:


Tightly coupled dependencies


But what if you could update dependencies casually? What if any breaking changes would be isolated to only one small part of your code base? This can be achieved by introducing a thin layer -- implemented using interfaces -- between your code and the third-party code you want to isolate yourself from. Martin Fowler provides a great, detailed example in Ruby for how this looks in practice here

One change, many benefits

Having looked at some of the problems in the detail, we can now look at one possible solution for addressing them. For the remainder of this article, we'll call the solution "local interfaces". As it commonly happens, it is easiest to apply this technique to new code, but changing existing code for this to work should be straight forward as well.

In a gist, here's the technique:


Applying this techniques yields a couple of benefits:


Example: Password reset

The explanation of how to use "local interfaces" can be hard to visualize, so we'll go through a step-by-step example here. For this example we will look at resetting a user's password. The flow for this feature is:

  2. A lookup against the database is performed to see whether the email address belongs to a registered user -- if it doesn't, we return early.


type ForgotPasswordController struct{}

	existingUser, err := controller.users.FindByEmail(emailAddress)

	if existingUser == nil {
		// attacker whether the email address belongs to a registered user
		// or not.
		return nil

	if err != nil {
		return err

	token, err := controller.passwordResetTokens.GenerateFor(existingUser)
	if err != nil {
		return err

	return controller.emails.SendPasswordForgotten(emailAddress, existingUser, token)

This was a straight copy of the description of our task in English. We've pretended that we have access to three objects that make our life easier:

All of these yield straighforward, narrow interfaces:

type Users interface {
	FindByEmail(emailAddress string) (*User, error)

type PasswordResetTokens interface {
	GenerateFor(user *User) (*PasswordResetToken, error)

type Emails interface {

We provide these dependencies when creating a new instance of the ForgotPasswordController:

type ForgotPasswordController struct {
	users               Users
	passwordResetTokens PasswordResetTokens
	emails              Emails

	return &ForgotPasswordController{
		emails:              emails,
		passwordResetTokens: passwordResetTokens,
		users:               users,

How the concrete implementation of those interfaces looks like is left to the reader -- it depends on the rest of the system, i.e. which database is being used, how emails are sent in general, etc. Providing implementations for unit tests is pretty straighforward and doesn't depend on the rest of the system. Here's the test for checking that we don't accidentally send out an email if no user is found:

func ForgotPasswordController_ForgotPassword_does_not_send_email_if_no_user_is_found(t *testing.T) {
	emails := NewEmailsInMemory()
	users := NewUsersInMemory()
	tokens := NewPasswordResetTokensInMemory()
	controller := NewForgotPasswordController(users, tokens, emails)



type EmailsInMemory struct {
	sent []string

func NewEmailsInMemory() *EmailInMemory {
	return &EmailsInMemory{
		sent: []string{},

	return emails.sent

	return nil

This test will run fast and works on a comfortably high level of abstraction. The speed comes from being able to use in-memory versions of all dependencies, instead of having to send out an actual email, talking to an actual database, etc. As an additional bonus, we can get proper integration tests by substituting the "real" implementations that are being used in production for all dependencies.
