Writing a background service in GoLang

Parag Garg
LimeTray Engineering
4 min readJan 28, 2018

We built our new POS system as a progressive web app sitting in the user’s browser. That’s because a web application offers a lot of advantages. It’s easier to maintain, faster to build and the tooling is very advanced. But it lacks access to some of the APIs which are necessary to build a good POS system. One of the problems was no way to print our receipts and KOTs frictionlessly without any user input.

Our take on the problem was to run a background service on the machine where the POS is running and send commands to it using http. While we looked at a lot of different solutions to build our service, Go stood out from the others because of the following reasons :-

  • It did not have any external dependencies like Java or .Net GCC and compiled as a standalone executable.
  • It supported cross-platform development, so we only had to write our business logic once.
  • It’s lightweight in both development and executable size.
Architecture Diagram of Printer Service

Let’s discuss our development process in more

Setting up our project

We followed a standard approach to building cross-platform software. We separated the files into common and platform specific. Eg: “/printer-windows.go”. The actual separation of files according to the right platform is done using “build Tags”.

Screenshot of our project structure

Build Tags

A build tag is just a comment at the top of the file which tells the build process which files to include and which ones to exclude. You can use OR, AND and the negation operator ! to put conditions on whether this file should be included or not.

As an example, the build tag found at the top of a source file

// +build darwin dragonfly freebsd linux netbsd openbsd solaris

For excluding a platform

// +build !linux

Calling Native API’s

Even though our business logic was all cross-platform we did have to write some platform specific code. Go has built-in support for using DLLs on windows. Here’s an example of our platform specific code.

Sample code from our printer service to load windows library “winspool” and then call its function “EnumPrintersW”

The above function enumPrinters list all the printers connected to a windows platform. We used C++ libraries and window dlls to interact with printer connected to the system.

Cross Compiling

Cross-Compiling in Go is easy and fast. Just specify the operating system(GOOS) and the architecture(GOARCH) and you are good to go. Here’s an example of our build process.

A Snippet from command line for building windows binary (.EXE) from OSX

If the compile is successful you’ll have a binary called YOURPACKAGE (possibly with a .exe extension if you’re targeting Windows) in your current working directory. -o may be used to alter the name and destination of your binary, but remember that go build takes a value that is relative to your $GOPATH/src, not your working directory, so changing directories then executing the go build command is also an option.

Pushing Updates

We used the go-update library to enable auto-update functionality in our service. This enabled us to update our service on the fly.

Running as a Service and Auto Start on Boot

Running as as service is enabled differently on different platforms. For windows we used the Go’s windows svc package. This package also has in-built StartType=2 option for auto start service on boot.
For Linux and OS X, we used nohup and added it to init.d.

Code Signing

We realized that .exe signing was very important because our service needs to run in background and it cannot be blocked by an antivirus or firewall. We tried using the windows’ signtool utility to sign our .exe but since we wanted to compile it on our linux machines, we used a linux specific utility called osslsigncode.

Finally

Our final application size was around 6.5 MB fo windows which was small enough to be distributed comfortably. While not a lot, we did have to write some platform specific code because printers work differently in windows and linux. We were also not able to find a good way to implement Restart on Crash mechanism for our service. Despite these, our service has been running quite well on some customers and we are already planning to extend it as a general purpose interface for all OS level APIs that the browser cannot access.

If you share our love for building software, we want to hear from you. Reach us at kickstart@limetray.com.

--

--