Structuring testable microservices with Go

Chris Dyer
4 min readFeb 15, 2019

I have been writing Java microservices for the majority of my career and stumbled into Go as an alternative to the boilerplate logic and structure Java requires. One of the most confusing and debated topics with Go is structuring applications. While there are many approaches to project structure with Go, I have found the following structure easy to maintain, test and (relatively) easy to transition other programmers to the language. To bypass my explanation and jump to the code, see the Github repository here.

Engineers frequently transition between multiple languages on a daily basis. Personally, I migrate between Java, NodeJS and Go depending on the application and use case. I have identified several structural best practices from the languages I work with and incorporated into the following layout where it makes sense.

The Repository Pattern

The first such pattern I would like to discuss is the repository pattern. Abstracting the persistence technology from other layers of the application allows ease of testing and mocking. Let’s start with the common interface.

// UserRepository interface describes repository operations on User types
type UserRepository interface {
GetAll() ([]models.User, error)
GetByID(string) (*models.User, error)
Create(models.User) (string, error)
Delete(models.User) error
}

Most Java developers will feel at home with this concept as frameworks like Spring use programming to interfaces extensively. I introduce this abstraction layer for two reasons. First, the abstraction allows us to create mock implementations that do not require external connections to a database. For instance, if we wish to provide a mock implementation for testing our controller we could code a struct that implements this interface and retrieves data from a map data structure.

// MockUserRepository houses logic to retrieve users from a mock repository
type MockUserRepository struct{}
// NewMockUserRepository convenience function to create a MockUserRepository
func NewMockUserRepository() repository.UserRepository {
return &MockUserRepository{}
}
var users = map[string]models.User{
"1": models.User{
Name: "James Bond",
Gender: "male",
Age: 44,
ID: "1",
},
}
// GetAll get all users from the repository
func (r MockUserRepository) GetAll() ([]models.User, error) {
userList := []models.User{}
for _, user := range users {
userList = append(userList, user)
}
return userList, nil
}
...

Or, for negative test cases we could implement a type that only returns errors to inject into our controller for teasing out exceptional behavior.

// MockErroringUserRepository returns errors for all operations.
type MockErroringUserRepository struct{}
// NewMockErroringUserRepository convenience function to create a MockErroringUserRepository
func NewMockErroringUserRepository() repository.UserRepository {
return &MockErroringUserRepository{}
}
// GetAll get all users from the repository
func (r MockErroringUserRepository) GetAll() ([]models.User, error) {
return nil, errors.New("blamo")
}
...

Secondly, the repository pattern allows a plug and play extensibility to your application should you wish to change persistence types. For instance, today I may use MySQL, but tomorrow I could be required to switch to another implementation that has quite different usage. I could switch out my persistence layer without impacting other layers of the application by adhering to the interface.

Testing the actual implementation can be tricky depending on the persistence technology but if your in the relational world using the go sql package, DATA-DOG has a great testing library called go-sqlmock that I use extensively. This library creates a database and mock type which can be injected into the type under test. See the MySQL implementation and example tests here.

The Controller Pattern

The next pattern that is highly debated in Go is the controller pattern. I feel controllers allow isolation to handler functions and allow ease of testing. This pattern also allows the injection of mock persistence types to truly isolate the controller and provide interesting negative test cases. I do not introduce an interface at the controller level. Instead I prefer to create a type containing my repository for dependency injection at higher levels. See complete controller and tests here.

// UserController struct containing web related logic to operate on Users
type UserController struct {
userRepository repository.UserRepository
}
// NewUserController is a convenience function to create a UserController
func NewUserController(r repository.UserRepository) *UserController {
return &UserController{r}
}
// GetUsers retrieve all users
func (u UserController) GetUsers(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
users, err := u.userRepository.GetAll()
if err != nil {
http.Error(w, "service unavailable", http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
...

Wiring the Components

Now for the fun part, making it all work together. All wiring of dependencies and database configuration live within my main.go file. I tend to isolate configuration values like connection strings to an environment variable for easy management and flexibility.

func main() { 
r := httprouter.New()
ur := repository.NewUserRepository(getDatabase())
uc := controllers.NewUserController(ur)
r.GET("/users", uc.GetUsers)
r.POST("/users", uc.AddUser)
r.GET("/users/:id", uc.GetUserByID)
r.DELETE("/users/:id", uc.DeleteUser)
http.ListenAndServe(":8080", r)
}
func getDatabase() *sql.DB {
db, err := sql.Open("mysql", os.Getenv("MYSQL_HOST"))
if err != nil {
log.Panic(err)
}
return db
}

The repository is created, injected with a database type and provided to the controller for use. I have experimented with injection libraries like inject from Facebook but honestly feel the overhead isn’t worth the benefit for small microservices like the example above. Hand wiring the dependencies is clear and easy to follow.

I hope this example helps you in your journey to learn and use Go. Please share your experiences with structuring Go projects below!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Chris Dyer
Chris Dyer

Written by Chris Dyer

Passionate software craftsman. Consumed by test driven development and good design.

Responses (1)

Write a response

Chris thank you for this, I feel like this was exactly the article I needed! I come from a Spring background with Java and seeing succinct examples that have some parallels to Spring while still looking like what I understand idiomatic Go to be is great.