Structuring testable microservices with Go

The Repository Pattern

// 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
}
// 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
}
...
// 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")
}
...

The Controller Pattern

// 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

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
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store