I recently undertook a time-boxed four hour spike to investigate another Go microservices framework. Go-Micro is a “RPC framework for microservices”. It aims to provide common components that are often used in microservice deployments. It advertises itself as providing a pluggable architecture and boasts a long list of compatibilities.
The first thing that a new user will notice is the lack of documentation. There are a few examples and some insightful blog posts. But the only formal documentation is the readme on the front page of the repo.
The architecture of go-micro resembles a tiered stack and separates concerns via plugins of five different types. The top layers consist of a client-server model and a service abstraction.
Services may be defined as protocol buffer files with an interface, inputs and outputs. You can also manually write the definition. This creates a solid interface for the rest of the application to use. The proto file is converted into usable go code, but this is only possible with a concerning fork of the protobuf library. I assume that this is because of some non-standard code generation. But I would prefer if this was wrapped in a more stable manner.
The first example application on the go-micro readme presents a simplified demonstration of the go-micro service concept. The example implements an interface from the service proto file and registers the implementation for use.
This example is using protobuf RPC calls to communicate. I.e. You also need to start a client service that has access to the proto schema in order to test it. I would suggest that this isn’t a great initial example, as users have no way of externally verifying the service. Typical hello world microservice example are usually REST based, so the user can
curl or open a browser to see the result. A quick “feel good” experiment is important for the user experience.
Furthermore, the example defaults to using Consul for service registration and discovery. If you don’t have a Consul server running, the application will crash out. I don’t believe that adding this requirement is either necessary or wise at this level of example. There is a note about overriding the discovery mechanism to use an embedded DNS server, but what if I don’t want to use anything? I’m not a fan of opinionated workflows, especially in such a simple example.
Finally, one thing that I like to see in examples such as these is a demonstration of code scalability. This is usually achieved by encapsulation; both at the code level and in the architecture. As I started editing the simple examples, this wasn’t clear. For example, when I was considering endpoints, this was placed in the main file, rather than in separate classes. This can obviously resolved by cleaner development, but it would be nice to see the blog posts/examples demonstrating this encapsulation from the start.
After the initial example, next I wanted to implement the typical REST microservices demo. Firstly, there is no documentation, examples or blog posts of using go-micro as a REST client. The idea being that REST services should only be a proxy to RPC based services.
In my arrogance, the readme’s lead me to a package called micro/go-web. It advertises itself as “a wrapper around registration, heartbeating and initialization of the go-micro client”, which I mistakenly took to mean that I should replace the core
web.NewService. I was confused why there was a duplication of code in these packages, there was no flags option nor no
Server implementation capable of passing to the service clients.
After some very helpful discussion on micro’s slack channel, I found that the go-micro service and the go-web web service was incompatible. These cannot be used together. Instead, the intention is that you use the go-web package individually.
The implementation of a go-web service is quite different from go-micro. It doesn’t have a clear service abstraction, in the way I think of a service. Instead the service implementation is handled in web-handlers. Much like the standard
net/http package. I was clearly not a fan of this, since it negates all the encapsulation benefits that a service abstraction brings.
At this point I got stuck again and asked for help. Another suggestion was to use another http implementation, which I tried, but resulted in the same architecture. A go-micro service must be used alone. As of now, I believe that it is not possible (or at least very difficult) to do what I was trying to do. Something that I consider the most basic microservice example.
My opinion is that go-micro is too opinionated. Some of the choices imposed by go-micro are worthy ideas. For example, I totally agree that if you’re developing an internal microservices application, then there is little point in using REST. Use a binary protocol that improves performance and increases stability due to well defined interfaces and schemas. And RPC’s are a great way of encoding stateful decisions. But I don’t want to be forced to use RPC with protobufs. I might want to use REST, especially considering the prevalence of REST examples. (See this paper for a short review).
This means that “plugability” and all the features that it provides are only available if you subscribe to the opinions of the framework. The fact that I had spent four hours coding, and I didn’t have a working REST example was quite disappointed and obviously tainted my view of the framework.
I’m always conscious that criticisms sound harsh when written down. I performed this evaluation to ascertain the differences between this and other Go microservice frameworks. It’s clear that I don’t subscribe to some of the same opinions enforced by the framework, but that doesn’t mean there isn’t good work in here. The sheer list of support for other microservice tools could be a real reason to invest some time to see if it fits your needs. With other frameworks you would need to spend time implementing support. This could mean you save significant man hours, simply by conceding to some of the opinions.
Also, I like the idea of using proto files to specify the service interface. This significantly reduces the amount of boilerplate. I’m not totally sold on the use of protocol buffers in particular, nor the fact that I have to use protocol buffers, but I do like the code generation and encapsulation.
I’ve also spoken to some of the developers over Slack. They were kind enough to help me out in my foolish endeavour; thank you to them.