Andrew Katsikas - Software Engineer

Building a personal web service in Go, TDD Style: Part 2 - Groundwork

Laying the groundwork

Before I got started writing code for my new “artist tracker” service, I wanted to make sure I was building on a solid foundation. I was on the lookout for a good example to follow - one that made use of quality design patterns, sound principles and could be easily extended with my own use case. A search for golang web service design pattern was quite fruitful. The very first result was Ichsan Rahardianto’s excellent service-pattern-go. As I read through the README and familiarized myself with the code, I felt confident that this repository could fit my use case very well.

What makes this example repository a good choice?

The code was clean, it was easy to understand, well-structured and the justification given for the design choices was clear and compelling. Above all, this repository demonstrated excellent use of the principle of “separation of concerns”. By structuring the code in a way where separate “units” are focused on a specific “concern”, code is easier to understand and can scale in a more sustainable way. Designing in this way gives us the “units” that make unit testing possible.

Dependency injection

Moreover, it provided a very good example of the dependency injection design pattern, as well as proper mocking in testing. Rahardianto’s own README does a wonderful job at explaining these concepts and how they all work together to make more testable code. For years, I had wondered about some of these concepts, and was especially mystified by a past engineering group’s ubiquitous use of Java interfaces in the system. Rahardianto explained that he too had the same incredulity before his “epiphany” when he finally understood how these concepts work together to facilitate unit testing. By combining interfaces with dependency injection, we can inject the real dependencies at runtime, or just as easily inject a mock when testing. Reading this, I finally understood and had the same epiphany myself! At this point, I was totally sold on this repository, and Rahardianto’s sudden liberal use of curse words endeared me even further, a man after my own heart. By following these principles and patterns, I felt confident that I could finally do test-driven development (TDD) with the big dogs.

Getting familiar with the code

Confident in my choice, I spent a few nights familiarizing myself with the repository, its code and its structure. Late at night, when I should probably have been in bed, I would go over different files and lines of code, using my editor to Ctrl-click into various methods to understand how the application flowed. The first few nights were very challenging and nothing made sense. Frustrated, I wondered if it was simply too late at night to be learning this stuff with any clarity of mind. However, after several bleary-eyed sessions, as if by magic, what used to be inscrutable gradually became clear. After building and running the application locally and making a few trivial changes to add some logging (to confirm I indeed understood what I was doing), I decided to make a more interesting change to the application.

Object-relational mapping (ORM)

While I was very satisfied with the service pattern repository, I noticed that it was not really taking advantage of the object-relational mapping (ORM) package it required, GORM (Go ORM). ORMs provide an abstraction layer around databases, enabling the programmer to access and manipulate their database in a way that is more in line with the programming language they are using, and eschews the need for raw queries in application code. ORMs can help reduce the amount of code required to interact with a database. They can also ease the effort needed to change a system’s underlying database solution. By providing a vendor-agnostic API to interact with a database, less changes are required to migrate (e.g. migrating from SQLite to MySQL). While ORMs can have some drawbacks (such as inefficiently optimized queries), I believe they are a vital part of any modern web application that makes use of a database.

Making a code change

In the example repository, while GORM is a required dependency and helps connect the application to the database, access to the data is still accomplished through a raw SQL query in the application code. Take this example function, GetPlayerByName (the example application retrieves scores from tennis matches):

 
func (pr *PlayerRepository) GetPlayerByName(name string) (models.PlayerModel, error) {
	rows, err := pr.Query(fmt.Sprintf("SELECT*FROM players WHERE name = '%s'", name))
	if err != nil {
		return models.PlayerModel{}, err
	}

	var player models.PlayerModel

	rows.Next()
	rows.Scan(&player.Id, &player.Name, &player.Score)

	return player, nil
}

As you can see, the query has to be assembled directly via Go’s fmt.Sprintf, and we even have to manage the rows returned, iterating through the results with .Next() and using .Scan() to access the data. Compare that to my modified version below:

 
func (pr *PlayerRepository) GetPlayerByName(name string) (models.PlayerModel, error) {
	gormConn := pr.Connection()

	var player models.PlayerModel
	result := gormConn.First(&player, "name = ?", name)

	if result.Error != nil {
		return player, result.Error
	}
	return player, nil
}

Now we have far less code, no raw queries, and no need to interact with any “rows” - we simply ask GORM for the first result that matches the provided name. Satisfied that I had mastered this example code, I started to look at my options for infrastructure and deployment.

#go #golang #tdd #gorm #orm #sql #db #dependency injection #mocking #unit testing #test-driven development #design pattern