Software Engineering for Full-stack Golang Web Applications

Steve Roehling

October 2, 2018

Go (Golang) is often used to implement microservices and system software. However, it can also be used to implement back-ends for full-stack web applications. When used for web applications, Go is part of a technology stack which also includes Javascript, CSS, and HTML. The development environment will also include tools for building, testing, integrating and deploying the application.

On a software project for a web application, the software engineering tools, processes and workflows must consider and encompass every element of the technology stack, not just the Go back-end. In this regard, it is important to consider how the software engineering for Go development can be effectively incorporated. For example, some relevant concerns include:

The software engineering which supported Resultra’s development addressed many of the concerns listed above. Therefore, Resultra can serve as a representative example for the software engineering which needs to accompany Go development.

Agile Software Development with Go

To some extent, almost every contemporary software development project is based around the principles and values for agile software development. With respect to Go development, several of these principles and values are relevant, including:

Go is a statically typed language with memory safety and garbage collection. Given these language features, Go inherently supports a type of development process like the following:

  1. Implement a basic working version of a software feature.
  2. Implement automated tests for the functionality and/or performance of the feature.
  3. As the requirements change, a better design is found or performance bottlenecks are encountered, incrementally improve and refactor the code.

Since Go is statically typed, many source code conflicts introduced during refactoring will be immediately identified by the compiler. Existing automated tests ensure there is not a regression in functionality or performance. In other words, Go supports a software development process which focuses on working software, but also gives developers the confidence to refactor and change the code as needed.

Even with continuous code improvements and refactoring, my own experience with Go development is the resulting software remained very stable and robust. Resultra’s development included unit tests and API tests, and the use of a profiler to identify performance bottlenecks. Resultra’s development notably did not include the use of a debugger; the types of serious defects which are typically found with a debugger did not occur or were more readily found with test cases or simple logging output.

Modular Package Organization

The code base for any non-trivial software project will expand beyond a single package. Go uses packages to identify code from a specific directory. Software engineering discipline and package principles help to determine what constitutes a package and how the software code and accompanying files should be organized and split into multiple packages.

One lesson learned from Resultra’s development is to orient towards smaller packages. This means packages will have a manageable number of files and a well-defined purpose. In fact, Resultra’s Go code base currently has a total of 97 different packages. These packages are roughly categorized as follows:

The rationale for smaller packages includes the following:

Modular APIs

Consistent with the modular package organization described above, there are several approaches to registering the APIs for individual packages. For each API endpoint, what this entails in to register a callback function which is invoked when the API is called.

One approach is a “top down” registration scheme. During program initialization, this involves a top-level package calling registration functions within each of the different packages.

A second “bottom up” approach is for each package to independently register its APIs at startup. Resultra uses the Gorilla mux library for this purpose. Within an individual package, an init() function is used to register the APIs, as shown in the following example:

func init() {

checkBoxRouter := mux.NewRouter()

checkBoxRouter.HandleFunc(“/api/frm/checkBox/new”, newCheckBox)  
checkBoxRouter.HandleFunc(“/api/frm/checkBox/resize”,  
   resizeCheckBox)  
checkBoxRouter.HandleFunc(“/api/frm/checkBox/setColorScheme”,  
   setColorScheme)  
checkBoxRouter.HandleFunc(“/api/frm/checkBox/setStrikethrough”,  
   setStrikethrough)  
checkBoxRouter.HandleFunc(“/api/frm/checkBox/setLabelFormat”,  
  setLabelFormat)  
checkBoxRouter.HandleFunc(“/api/frm/checkBox/setVisibility”,  
   setVisibility)  
checkBoxRouter.HandleFunc(“/api/frm/checkBox/setPermissions”,  
   setPermissions)  
checkBoxRouter.HandleFunc(  
   “/api/frm/checkBox/setClearValueSupported”,  
   setClearValueSupported)  
checkBoxRouter.HandleFunc(“/api/frm/checkBox/setHelpPopupMsg”,  
   setHelpPopupMsg)  
checkBoxRouter.HandleFunc(“/api/frm/checkBox/setValidation”,  
   setValidation)  
checkBoxRouter.HandleFunc(“/api/frm/checkBox/validateInput”,  
   validateInputAPI)

http.Handle(“/api/frm/checkBox/”, checkBoxRouter)

}

There are several advantages of a “bottom up” or self-contained API registration scheme. First, a top-level package doesn’t need to establish a package dependency upon another package just to complete the API registrations. Secondly, when a package is included in another package, the registrations happen automatically. Finally, as a system grows and changes over time, it is conceivable for different packages to be integrated into different micro-services, tested independently, etc.; to the extent both the functionality and APIs for a package are fully self-contained with a package, “bottom up” API registration ensures that a package’s APIs are always registered at runtime.

System Design Considerations

For a full-stack web application, there are many high level system design considerations; for example:

Using Go as a back-end has some implications on system design.

First, Go’s runtime model is such that the back-end executable will startup then run continuously. This is different from other languages such as PHP, where the lifetime of the script’s execution is only for a single API request or web page load. Since Golang runs continuously, it can potentially cache information between API requests or maintain persistent connections with clients.

Go has built-in concurrent programming primitives, such as goroutines and channels. This allows the performance of a single Go executable to seamlessly scale (vertically scale) with the available CPU and memory resources on the server. Unless an application is expected to serve millions of users, running a single back-end Go executable may be the best system design alternative.

In full-stack web applications, there is a separation of concerns between the front-end (web browser) and back-end (server). Go is arguably a good choice for the back-end. For numerically intensive computations and complex business logic, compiled Go executables can have optimum performance. The front-end is then focused on viewing or presenting the information.

Integrating Go with a System Build Environment

Nominally, only the `go build` command is needed to build a Golang executable. However, in the broader context of building and integrating a complete web application, many other steps are typically required to build and integrate the entire system. For example:

Any number of build tools could be used to accomplish the above. Resultra uses Makefiles to invoke the individual commands, and a Python build script to perform a phased build.

Summary

Go is a suitable language for full-stack web applications. Choosing Go for a technology stack has implications on the software engineering which accompanies and provides discipline for Go development.

During the development of Resultra, numerous software engineering decisions were made to effectively incorporate a Go back-end implementation into a web application technology stack. Notably, consideration was given to aligning the Go development with an agile software development process, modular package organization, and automatically building and testing the Go back-end within a system build environment. The software engineering used for Resultra’s development is representative of the tools, processes and disciplines needed to develop any full-stack web application with a Go backend.