Using the Service Object Pattern in Go

We’ve all likely seen a web application in Go with a handler function that looks something like this:

type WidgetHandler struct {
	DB *sql.DB
	// Renders the HTML page w/ a form to create a widget
	CreateTemplate *template.Template
	// Renders a list of widgets in an HTML page
	ShowTemplate *template.Template
}

func (handler *WidgetHandler) Create(w http.ResponseWriter, r *http.Request) {
	// Most HTML based web apps will use cookies for sessions
	cookie, err := r.Cookie("remember_token")
	if err != nil {
		http.Redirect(w, r, "/login", http.StatusFound)
		return
	}
	// Hash the value since we store remember token hashes in our db
	rememberHash := hash(cookie.Value)
	// Then look up the user in the database by hashed their remember token
	var user User
	row := handler.DB.QueryRow(`SELECT id, email FROM users WHERE remember_hash=$1`, rememberHash)
	err = row.Scan(&user.ID, &user.Email)
	if err != nil {
		http.Redirect(w, r, "/login", http.StatusFound)
		return
	}

	// From here on we can assume we have a user and move on to processing
	// the request
	var widget Widget
	widget.UserID = user.ID
	err = r.ParseForm()
	// TODO: handle the error
	widget.Name = r.FormValue("name")
	// postgres specific SQL
	const insertWidgetSql = `
INSERT INTO widgets (user_id, name) 
VALUES ($1, $2) 
RETURNING id`
	err = handler.DB.QueryRow(insertWidgetSql, widget.UserID, widget.Name).Scan(&widget.ID)
	if err != nil {
		// Render the error to the user and the create page
		w.Header().Set("Content-Type", "text/html")
		handler.CreateTemplate.Execute(w, map[string]interface{}{
			"Error":  "Failed to create the widget...",
			"Widget": widget,
		})
		return
	}
  // Redirect the user to the widget
  http.Redirect(w, r, fmt.Sprintf("/widgets/%d", widget.ID), http.StatusFound)
}

The exact details may vary - for instance the application may use another database, it might create UserService and WidgetService interfaces instead of directly writing SQL, and you might use a framework/router like echo, but generally speaking the code will look roughly the same. The first few lines of a handler will be used to parse data, then we will go about doing whatever we really wanted to do, then finally we will render any results or errors.

Handlers are a data parsing and rendering layer

If we look back at our original code, it is shocking just how much of that code is actually just parsing and rendering. The entire cookie retrieval section is used just to get a remember token or redirect the user if there is an error. Once we have the token we perform a database lookup, but again this is quickly followed with a error handling and rendering logic. Then comes parsing the form, getting the widget name, and rendering any errors that occur while creating the widget. Finally, we are able to redirect the user to the new widget if it is created, but if you think about it the redirect is basically just rendering logic as well.

All in all, about 60% of our code is just parsing data and rendering results/errors.

Parsing this data isn’t intrinsically bad, but what I do find disturbing is the fact that before data is parsed, requirements are unclear. Think about it - if I handed you this function definition and asked you to test it, could you tell me what data it expected?

func (handler *WidgetHandler) Create(w http.ResponseWriter, r *http.Request)

You might be able to infer from the WidgetHandler type and the function name - Create - that this is used to create a widget, so we need some information describing a widget, but would you know what format that data should be in? Would you know that the user needs to be signed in via a cookie based session?

Even worse, we can’t even infer which parts of the WidgetHandler need to be instantiated for this to work. If we scan the code we can clearly see that we use the DB field, and it looks like we render the CreateTemplate when there is an error so we need to set that, but we had to look through all of the code to see what all was used.

Handler functions need to be vague; there really isn’t a viable way to create an http server without having a vague definition of what an incoming HTTP request looks like and then writing some code to parse that incoming data. Even if we created reusable middleware and leveraged the request context to store the parsed data, we still need to write and test those middleware, and it doesn’t solve the problem of having unclear data requirements for our handler functions. So how do we fix that problem?

The service object pattern

Rather than fighting the fact that we need to parse data in our handlers, I have found that what works better is to embrace it and make those handlers strictly data parsing rendering layers. That is, in my http handlers I try to avoid any logic that isn’t related to parsing or rendering data and instead embrace a pattern very similar to the service objects pattern in Ruby.

The way the pattern works is pretty simple - rather than writing logic in my handlers to do things like create a widget, I instead pull that ocde out into a function that has clear data requirements and is easy to test. For instance, in the widget creation example I might create something like this:

func CreateWidget(db *sql.DB, userID int, name string) error {
  var widget Widget
  widget.Name = name
  widget.UserID = userID
	const insertWidgetSql = `
INSERT INTO widgets (user_id, name) 
VALUES ($1, $2) 
RETURNING id`
	err = db.QueryRow(insertWidgetSql, widget.UserID, widget.Name).Scan(&widget.ID)
	if err != nil {
    return err
  }
  return nil
}

Now it is much clearer that in order to create a widget, we need to have a database connection, the ID of the user creating the widget, and the name of the widget.

A more interesting example

This particular example is pretty boring, so let’s look at a more interesting example. Let’s imagine we wanted to handle having a user sign up for our application, and when this happens we create the user in our database, send the user a welcome email, and add them to our mailing list tool. A traditional handler might look something like this:

func (handler *UserHandler) Signup(w http.ResponseWriter, r *http.Reqeust) {
  // 1. parse user data
  r.ParseForm()
  email = r.FormValue("email")
  password = r.FormValue("password")

  // 2. hash the pw and create the user, handling any errors
  hashedPw, err := handler.Hasher.Bcrypt(password)
  if err != nil {
    // ... handle this
  }
  var userID int
  err := handler.DB.QueryRow("INSERT INTO users .... RETURNING id", email, hashedPw).Scan(&userID)
  if err != nil {
    handler.SignupForm.Execute(...)
    return
  }

  // 3. Add the user to our mailing list
  err = handler.MailingService.Subscribe(email)
  if err != nil {
    // handle the error somehow
  }

  // 4. Send them a welcome email
  err = handler.Emailer.WelcomeUser(email)
  if err != nil {
    // handle the error
  }


  // 5. Finally redirect the user to their dashboard
  http.Redirect(...)
}

As you can see, we have a good bit of error handling, and in each of those if blocks we could easily need to render an error page, send the user back to the signup page, or anything else. We also end up using quite a few pieces of the handler - the MailingService, SignupForm, Emailer, and the Hasher - and none of these are obvious for testing purposes.

What makes this even worse is that testing each of these individual pieces is somewhat annoying. If we just wanted to verify that calling this endpoint created a user in the database we would still need to at least stub out all of those other pieces.

In cases like this, splitting our code into a few service objects that have clear requirements and can be independently tested is incredibly useful.

type UserCreator struct {
  DB *sql.DB
  Hasher
  Emailer
  MailingService
}

func (uc *UserCreator) Run(email, password string) (*User, error) {
  pwHash, err := uc.Hasher.BCrypt(password)
  if err != nil {
    return nil, err
  }
  user := User{
    Email: email,
  }
  row := uc.DB.QueryRow("INSERT INTO users .... RETURNING id", email, hashedPw)
  err = row.Scan(&user.ID)
  if err != nil {
    return nil, err
  }
  err = uc.MailingService.Subscribe(email)
  if err != nil {
    // log the error
  }
  err = uc.Emailer.WelcomeUser(email)
  if err != nil {
    // log the error
  }
  return &user, nil
}

Now we can easily test the code used to create a user; the dependencies are clear and we don’t need to mess around with HTTP requests. It is just regular old Go code.

We also have the added benefit of simplifying our handler code. It no longer needs to mess around dealing with non-fatal errors that just need information logged, and we can instead focus on just parsing data.

type UserHandler struct {
  signup func(email, password string) (*User, error)
}

func (handler *UserHandler) Signup(w http.ResponseWriter, r *http.Reqeust) {
  // 1. parse user data
  r.ParseForm()
  email = r.FormValue("email")
  password = r.FormValue("password")
  user, err := handler.signup(email, password)
  if err != nil {
    // render an error
  }
  http.Redirect(...)
}

To instantiate this code, we would write something like:

uc := &UserCreator{...}
uh := &UserHandler{signup: uc.Run}

And then we would be free to use the methods on uh as http.HandlerFuncs in our router.

More, but clearer code

This approach clearly requires more code. We now need to setup a UserCreator type and then set its Run function to the signup field in the UserHandler, but by doing this we have clearly separated the role of each function and made it much easier to to test our code. We no longer need to even have a database connection to test our handler, and could instead test it with code like this:

uh := &UserHandler{
  signup: func(email, password) (*User, error) {
    return &User{
      ID: 123,
      Email: email,
    }, nil
  }
}

Similarly, when testing our UserCreator we don’t need to use the httptest package at all. Neato! 🙌

Finally, as we will see in a followup post (I’m working on it still), this also opens the door for writing applications that are mostly agnostic of their input/output formats. That is, we could take an existing web application and add JSON API support with fairly minimal effort.

Learn Web Development with Go!

Sign up for my mailing list and I'll send you a FREE sample from my course - Web Development with Go. The sample includes 19 screencasts and the first few chapters from the book.

You will also receive emails from me about Go coding techniques, upcoming courses (including FREE ones), and course discounts.

Avatar of Jon Calhoun
Written by
Jon Calhoun

Jon Calhoun is a full stack web developer who teaches about Go, web development, algorithms, and anything programming. If you haven't already, you should totally check out his Go courses.

Previously, Jon worked at several statups including co-founding EasyPost, a shipping API used by several fortune 500 companies. Prior to that Jon worked at Google, competed at world finals in programming competitions, and has been programming since he was a child.

Related articles

Spread the word

Did you find this page helpful? Let others know about it!

Sharing helps me continue to create both free and premium Go resources.

Want to discuss the article?

See something that is wrong, think this article could be improved, or just want to say thanks? I'd love to hear what you have to say!

You can reach me via email or via twitter.

Recent Articles All Articles Mini-Series Progress Updates Tags About Me Go Courses

©2024 Jonathan Calhoun. All rights reserved.