My Go courses are discounted for the next few weeks to help out anyone who may need or want access to them. I'm also going to try to help out anyone who can't afford a course, and I will be writing posts about working from home over the next week in an attempt to help anyone new to WFH. Read more here .

Using the Service Object Pattern in Go

This is an experiment Most of the code and ideas in this post are things I have been experimenting with. That doesn’t mean the ideas and lessons aren’t valuable, but it does mean that you shouldn’t just blindly follow this pattern. It has its own set of pros and cons that should be considered on a case-by-case basis. That said, the pattern has been working very well for me and using a design to isolate parsing data from application logic is a critical step in building web applications that support multiple formats (HTML and JSON API), as we will explore in a future post.

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.

In this example which fields we use are obvious, but imagine our WidgetHandler was used to create, update, publish, and perform many other actions on a widget. In that case our WidgetHandler type would have a lot more fields and we surely wouldn’t need them all to be set to test just this handler.

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.

In reality, I even try to pull as much data rendering out of the handlers as possible too. See Creating the V in MVC for more ideas on how to do this.

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.

You don’t have to create such specific requirements here. For instance, I’ll often create functions like this that expect both a User and a Widget as its arguments instead of the more specific userID and name arguments. That choice is up to you to make.

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.HandlerFunc s 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.