Wednesday, December 3, 2014

All the things your microservices should do (Part 1)


I like to think of a Microservices environment to be pretty ideal. You deliver small concise pieces of functionality for others to pick and choose. Your customers can take a look around and put together another service or application very quickly. You maintain a suite of services, and create new ones when others aren't used anymore.

When writing and maintaining many small services, there are some extra things you might want to consider to keep your life sane.

Imagine a scenario of owning 8-12 services in a small team of 3, which I do not think is unreasonable.

I have started doing just this, and have created some really useful and helpful pieces of functionality to make it maintainable.

In my next few blog posts, I'll talk about what it is I have implemented and how it makes the above scenario manageable.

Version

Every service I write has a GET endpoint that returns the version of the service (note - not the version of the contract, this is different). The version number consists of a standard dev-maintaned major.minor.  I follow that by the Git SHA, followed by if there are any pending Git changes (this will make more sense in future posts when I talk about CD).

So by looking at the /version endpoint, I might see a response of

{ "Version": "1.1.ab41cd" }

or if I have pending changes:

{ "Version": "1.1.ab41cd+CHANGES" }

When using Golang, you can pass variable values during build time by passing it as a flag. I learned this versioning tip from reading the source of terraform.io.

Command Line Interface

Since microservices are so small, it is nice to provide their functionality not only as a hosted service, but also as a command line program. All the functionality of the service also works with command line options, and one of the most important is showing the version (-v).

By executing our service with the -v flag, it returns the version number which can be used by the build/deployment system, and by humans trying to troubleshoot issues.

The CLI can be used by both customers and automation, it is especially great for those "beginning of time" problems. Our deployment process depends on a service, and without the CLI, we would have to deploy the first time manually.

Health

Health information is very important as it is used by load balancers, deployment, monitoring, and as a troubleshooting tool. All in-process and external connections should be included in your check - databases, caches, other API's, logging, metrics, etc. Your health check should be low overhead, never fail, and return quickly as it will be called often.

Health requests should include a nice quick roll-up status, along with any details on each dependency.

I have an interface that my dependencies implement to return this information:
IsHealthy() (bool, error, []string)

In addition, I like my health response to include the last few error log entries, and metrics. This helps me troubleshoot any issues without having to look on the machine. It even shows me dynamic configuration values that are queried when the service starts in AWS. It is a huge helper for debugging issues.