In this project, we surveyed and analyzed the ways in which developers write microservices today. We experimented with different frameworks, languages, libraries, and platforms that are popular among microservices developers, and gained firsthand experience with how these tools work. Below we discuss what a microservice is, the pros and cons of a microservice architecture, and finally we describe and evaluate the different microservice technologies we utilized.
A microservice architecture designs applications as a collection of small autonomous services, each with their own unique functionality. Each of these small modularized services are referred to as a microservice. Microservices communicate with each other over a private network and work together to create a fully functional multi-feature application.
The independent nature of microservices allow applications to be more efficiently and reliably implemented. Because each microservice performs a specialized task independent from the other, microservices can be added, removed, and updated without interfering with the functionality of other microservices that contribute to the application at large. This means that issues in one microservice may not necessarily cause a failure in the rest of the application (improved fault isolation), and that parts of the application can be upgraded easily. The improved modularity of the application often leads to increased programmer productivity as well, as code is not only more readable, but developers are also able to pinpoint and resolve issues faster when testing in smaller units. Developers can also focus on scaling individual components in an application by deploying multiple instances of the same services, instead of scaling the entirety of the application. Finally, deployment is arguably simpler and faster for these smaller units of code.
Despite their many advantages, applications designed using microservices can be difficult to implement because of their distributed nature. Microservices need to be able to seamlessly communicate with each other over the network and deal with partial failures and service discovery. In addition, more services generally means more resources. For example, instead of sharing a database with multiple tables, separate microservices may need their own database. Contrary to what was mentioned earlier, testing and deploying with microservices can also be more difficult. While deployment of a single microservice may be easier, orchastering and coordination among multiple services when deploying is complicated. For first time microservice developers, designing a microservice can be difficult and time consuming.
Because microservices are loosely coupled, they can use different languages and technologies that are best suited to their needs. We surveyed different tools/technologies that are popular among microservices developers and below we provide qualitative analysis of our experience working with them. Many of these technologies address the challenges of a microservice architecture that were previously stated.
Python’s easy integration with various technologies and its ability to enable fast prototyping make it popular among many developers. Python includes substitutes for implementations that are generally heavyweight, and includes many frameworks that microservice developers can take advantage of in order to make communication easier between their services. We talk more about our personal experience using Python when we discuss Flask, a popular micro web framework.
Go, or Golang, possesses several qualities that make it a popular language for writing microservices. Namely, Go is great at handling heavy loads, provides great support for concurrency and libraries that make it easy to write web services, and its clean and simple syntax helps keep code manageable and easy to read. Working with Go was very easy. However, one downside to Go is that because it evolves so quickly, it renders open source examples from as recent as 2019 relatively obsolete. The language itself, however, is enjoyable to work with.
Node.js is not actually a programming language, but rather an open-source JavaScript runtime server for running JavaScript code beyond the browser. Node.js runs on the V8 runtime, a high-performance JavaScript engine written in C++, making Node run really fast, much faster than Ruby or Python. Node is also great for handling thousands of concurrent connections, making it suitable for a microservice environment. Additionally, Node is really easy for developers to learn since it’s just regular JavaScript. This eases the learning curve, allowing first time Node developers to be productive very quickly.
Java has always been a popular programming language among app developers, and it continues to be popular among those interested in developing microservices. A popular feature of Java is its annotation syntax, which makes it easier to read and write applications, increasing developer productivity. Furthermore, Java’s JVM allows developers to easily work with different languages in other services, which is important to developers looking to experiment with new microservice technologies. Overall, Java’s maturity, familiarity, and stable frameworks such as Spring Boot make it a popular choice for microservice developers.
REST is an incredibly popular IPC mechanism due to its simplicity. With REST, there’s no need for any additional infrastructure as services communicate directly and sychrously over HTTP. REST is also easy for developers to prototype with because of the many libraries that exist to simplify development. One additional benefit is that you can test an HTTP API from within the browser or from the command line without ever needing to implement a client. There are some drawbacks of using REST however, like its lack of an intermediary to buffer messages, meaning that both the client and server must be running at the same time to communicate. In our personal experience, we encountered many libraries such as RestTemplate and WebClient for Spring Boot that made communicating using REST very easy.
gRPC is a powerful framework developed by Google for working with Remote Procedure Calls. It is programming-language agnostic, which makes it a great tool for microservices written in different languages that have to send data back and forth. gRPC requests are also really fast and it supports load balancing, which are two features that are really important for microservices that must communicate with each other over a (usually private) network. gRPC supports HTTP/2, allows multiple requests to be sent/received in a single connection, which explains its fast performance. Overall, working with gRPC was very simple and straightforward. The Google Code Labs for gRPC are very understandable and guide you through examples that are non-trivial but easy to understand. Additionally, since gRPC has support for multiple languages, we were able to use gRPC for multiple of our microservice examples.
Spring Boot’s API makes it easy to deal with many of the difficulties that come with writing microservices. For example, dealing with partial failures, an issue that arises in many distributed systems, is made easy with Spring Boot’s fault tolerance library Resilience4J, which provides various options to handle partial failures. Spring Cloud also adopts Eureka, an open source library created by Netflix, which handles service discovery for developers. Eureka uses client-side-discovery, and additionally implements load balancing. The daunting task of microservice communication is made easy in Spring Boot with libraries such as RestTemplate and WebClient, both of which we found easy to learn and use.
Eureka was arguably the most interesting and enjoyable part of developing the Spring Boot microservice application. Eureka servers and clients are easily created by adding the Eureka server dependency or Eureka client dependency to the service requiring it. If the service is an Eureka client, the service needs to be told where the Eureka server is located and identify itself by name. When the microservice is run for the first time it is automatically registered with the Eureka server (which acts like a service registry). All of this is incredibly simple for the developer, and allows microservices to communicate with other microservices without hardcoding the microservice location. Eureka also handles load balancing on the client side automatically, all done with the simple annotation @Loadbalanced. Thus, you can see how Spring Cloud’s strength when it comes to writing microservices lies in its ability to provide easy to use libraries that abstract away many of the challenges microservices introduce to developers.
Flask’s main strength is that it's easy to use and provides a convenient way to define endpoints, handle request data, and build HTTP responses. In our personal experience, using Flask to develop microservices was very easy, and we found lots of great documentation to do so. Although not related to Flask specifically, we did encounter quite a bit of difficulty when it came to using python-eureka-client, a Python library that is meant to replicate Spring Cloud’s Eureka. The Python library is community driven and appears to be maintained by a single developer. We used this library in order to be able to connect the microservice we wrote using Flask to the microservice we wrote using Spring Cloud. Unfortunately, while we were able to make GET requests to our Spring Cloud application there was no documentation on how to make a POST request using python-eureka-client and our attempt to make one after looking at the source code did not work. We found this experience to be a bit unfortunate since making a POST request seems like an essential feature. This experience highlighted how even though one of the strengths of microservices is that each service can be implemented in a different language or using a different framework, some tools may not be available in other frameworks or may not be implemented as well, which could cause issues in developing the application.
MongoDB’s flexible data model, scalability, and ability to manage multiple database instances using MongoDB Atlas are just a few of the reasons why MongoDB is popular among microservice developers. We personally found Spring MongoDB incredibly easy to use and integrate into our existing Spring Boot microservice application. There was plenty of well written documentation on Spring MongoDB including official documentation from MongoDB Inc. and blog posts from community users that we drew heavily from. We used MongoDB Atlas to host our cluster in the cloud. In order to be able to connect to the cluster however, the IP address of the client performing operations on the database needed to be added in the whitelist for Atlas connections. This actually ended up causing some issues later in the development process because our IP address changed and we were unable to connect to the database, but didn’t know why. We did think that this requirement was a great security feature however, and lent itself well with the microservice architecture design. Overall, we saw that Spring MongoDB, like many of the libraries in Spring Boot, utilizes abstractions and annotations that make integrating and using MongoDB incredibly easy.
Redis is an open-source, in-memory data structure storage system that is often used as a database, a cache, or a message broker in microservice. As Redis is written in C, it has exceptionally fast read and write operations. It also has support of multiple languages such a C, C++, C#, JavaScript, Java, Go, Objective-C, Python, and PHP. While the Redis tutorials were helpful enough to get started on a simple project, they lacked information that would help a first time user be self-reliant. That being said, the overall functionality was straightforward and easy to understand, but not being able to understand much about the setup process made dealing with random bugs very tedious.
Recall that with a microservice approach, several individual microservices must be orchestrated to coordinate with each other and produce the functionality of a single complex application. By far, the most popular technology for doing so is Kubernetes, an open-source container orchestration system for automating the deployment, scaling, and management of one or more containers. Kubernetes, released in 2015, has quickly become the go-to container deployment technology among developers. One of the key features of Kubernetes that make it so popular was how it expresses resources/dependencies as “data” rather than as code. Infrastructure in Kubernetes is expressed as YAML, which is easier for developers to reason about and makes the deployment process easier and faster, especially for those new to container orchestration.
The online tutorials, especially those from the Kubernetes website itself, for learning how to use Kubernetes are very informative and easy to understand. Additionally, the strong community of Kubernetes users and the abundance of open source examples make working with Kubernetes a pleasant experience.
In conclusion we saw that many of the tools involved in developing microservices have several characteristics in common. One, they are often easy to use and learn, and are well documented. Additionally, they tend to perform quickly, are lightweight, and are often open source. Finally, many of the popular frameworks include easy to use libraries to overcome the challenges of writing microservices.