Running microservices locally



Over the past few months I've been slowly splitting out my start-up's backend code into microservices. Our features can be clearly defined as separate apps with separate responsibilities. Which we wanted to be able to switch on or off, launch new apps seamlessly. So microservices made good sense.

Flash forward a few months, and I have two front-end code bases, a third in the pipeline, an API gateway, 9 microservices and two databases. This all runs perfectly fine in Kubernetes, provisioning them and deploying them independently.

However, running them locally is a different story. I found myself using docker-compose, which worked fine, but the more services I added, some of which depended on each other via gRPC. The more difficult it got to develop and test services.

After some research I found a nifty way of using environment variables to spoof other parts of the architecture.

For example, I have an authentication service, each of my services calls the auth-service via gRPC using a middleware. So already most of my services depend on another service, and another database.

 Environment variables

A good idea is to environment variables to spoof features and dependencies. For example...

// AuthService interface
var authService IAuthService

// Set authService as authService stub
authService = services.SpoofedAuthService{}

// If not in standalone mode (i.e production)
// Use the 'real' authService.
if os.Getenv("STANDALONE") != true {  
  authService = services.AuthService{}
}

authService.ValidateToken(c.Header("authorization"))  

The above example, I spoof the auth client, the methods normally used to validate a jwt token, just return true when STANDALONE is set to true.

This service in question also depends on a Mongodb database, so in order to test locally, I simply use Docker to spin up a throw away Mongodb instance.

default: run-test-db run-local

run-test-db:  
    docker run -d -p 27017:27017 mongo:latest

run-local:  
    DB_HOST=localhost STANDALONE=true ENV=local go run main.go

$ make now runs the service, along with a Mongodb instance locally. The STANDALONE environment variable tells parts of my service to 'pretend' to connect to external services and return a successful response.

I could have just used the ENV=local, but I might still want to run the entire stack locally.

If you're writing TDD, you'll most likely have stubs for things like database connections, gRPC calls etc.