The way applications are developed evolves over time. Let's sketch concepts that have had a big impact on software development in recent years: cloud computing and microservices.
Cloud computing is an infrastructure that makes it possible to automatically provision computing resources on demand. The types of resource provided depend on the contract between the cloud provider and the customer—the cloud provider can provide software services, such as email or disk storage, platforms for software development, access to virtual machines, or infrastructure for running software applications.
The resources are provided dynamically and rapidly using the internet, and, as a result, the customer is able to use (and pay) for resources that they currently use. The cloud provider, on the other hand, can take advantage of economies of scale: specialization and optimal resource usage will result in quality improvements and cost optimization.
So, how does interaction with cloud computing infrastructures look from the developer's point of view? During the development, the cloud provider provides a platform that contains a number of tools: it enables developers to run multiple application frameworks, standalone services, and databases among others. It provides functionalities and tools needed by those applications: scaling, networking, security, and communication. Furthermore, as hinted earlier, a user pays only for the resources used; cloud infrastructure will adjust the resources provided based on the load used by your application.
The preceding description sounds promising, but it immediately raises a number of questions, such as how are the resources provisioned and scaled, what kinds of tools can I use, and what are the APIs for the tools provided.
One of the goals of this book is to provide you with all this information throughout. For the purpose of this introductory chapter, it is enough to acknowledge the most important information: cloud computing infrastructures will enable us to develop and deploy with a wide array of tools using computing resources provided on demand.
Microservices architecture is a software development methodology that advocates creating an application from loosely coupled services that cooperate together.
Such an architecture was researched and advertised for a long period of time: Some time ago, a lot of attention was given to Service-Oriented Architecture (SOA). Even earlier, CORBA, the standard for distributed computing, was designed. Furthermore, building your applications with loosely coupled, highly cohesive services is a good software practice, and it can (and should) also be applied in a traditional monolithic application. Why has the new concept been created then, and what distinguishes it?
In recent years, a number of companies building large distributed systems have found it harder to build and maintain their systems using the traditional monolithic software architectures and decided to refactor their systems to loosely coupled modular distributed systems. Looking at the experience of those companies that succeeded in doing so, we were able to gather common architectural patterns in the systems that they built. This gave birth to the microservice architecture concept. Put in another way, microservices can be thought of as a software architecture that is another iteration of distributed computing systems, whose characteristics were derived from practical experience. As a result, instead of providing a definition of microservice architectures to which all aspiring implementors have to adhere, it is easier to provide a common set of characteristics that microservice systems share (Further Reading, link 2). Let's do it now.
Microservices are built as standalone services that are independently deployable. From the technical point of view, it means that they run in different processes and communicate through the network using their APIs. Each of the services can be started, stopped, and updated independently. Each service is responsible for its own data and can modify the data of other services using only their API.
The system is decomposed into microservices around business functionalities. Each microservices is built by one, small team that consists of all necessary technical specialists. For example, in a store application, there may be a review service. The review service team may consist of programmers, database engineers, testers, and domain experts. The team is responsible for every aspect of this service—from getting customer feedback to database administration.
As you can see, instead of advertising a set of recommended characteristics that the applications should adhere to, successful microservice practitioners have created a technological environment that enforces modularity and loose coupling.
So, if you successfully implement a microservice architecture, what benefits will you obtain?
The first thing that is straightforward but should be emphasized is that if you successfully create a system whose architectural characteristics-force modularity and loose coupling, you will obtain a highly modular system as ad hoc fixes and extensions won't compromise and effectively abandon the boundaries between services throughout the development process.
Because of the modular characteristics of developed applications, components that constitute them may be developed more effectively: As there is a small, cross-sectional team working on each service, its members can concentrate on their own area of work relatively independently of other teams. As the practice suggests, as the team grows communication starts to inhibit work more and more. The small, focused team knows the domain well, and they also know each other well, can communicate immediately, and move the work forward.
Also, crucial is the fact that the service can be deployed independently of other services. A successful microservices architecture does not have the concept of big system release in which all teams gather their recent updates together and create a major release of the whole system. Instead, all teams are able to release and deploy their new functionalities independently of other services. There is no synchronization between the teams, and if there is a new version of a service that can be released, the service's team can just independently design to do it. Such a characteristic is a catalyst for Continous Integration. The team is able to build the pipeline so that each code triggers a test, review, and deploy process.
The characteristics described in the preceding paragraph—small, focused teams and independent and automated build and deployment processess—lead to very important characteristics of the successful microservices-based system: an ability to implement required changes very fast. This is crucial, as it allows for immediate responses to customer needs. This tightens the feedback loop between the customer and developer and allows the system to quickly evolve to meet the customer needs.
Last but not least, we should mention the direct technical consequences. Microservices can be scaled more effectively: When scaling a traditional monolith application, we need to replicate a number of application servers effectively, replicating all the functionalities implemented in the application. Scaling microservices can be more fine-grained; we are able to replicate only the services that need more instances across different servers.
Furthermore, microservices architecture tends to improve the availability: if a review service is down, the rest of the store can work regardless of it. Such a situation is obviously far from ideal but way better than a shutdown of the whole system.
In the preceding paragraph, we mentioned that the preceding characteristics apply to successful microservices implementation. As it turns out, creating such systems is not simple. Let's learn why.
The challenges that encompass implementing microservice architecture can be summarized in one phrase: distributed system.
Is the functionality that you will implement will use a bunch of services throughout a network. You will have to deal with network delays and failures. What if the response is not immediate? Is the target service down or busy? How should we find out, and what we should do about it?
Should the data belong to one microservice? Easier said than done. We can make the database underlying the service consistent, but how do we propagate this information to other services that rely on this data?
Also, it is nice that each team can work independently, but what if we really need to implement cross-service functionality? That can become a pain: a cross-team endeavor that may introduce large architectural changes and substantially impact the whole architecture.
Let's assume that we managed to deal with the preceding problems and have a running system. What happens when an error occurs? We will have to analyze logs scattered around a number of services, also tracing network interactions between all of them.
So, how should you decide whether the microservice architecture is suitable for your application?
Microservices should primarily be considered for systems in which managing the traditional monolithic application has become too complex to develop and maintain. If you are developing a small application, additional complexity, described in the preceding paragraph, may outweigh the modularity benefits and inhibit, instead of amplifying, your development process.
It has been suggested (Further Reading, link 3) that microservices architecture should be an evolution of the monolith application. Most systems should start as a monolith, and the transition to microservices should only be considered when the system grows to the extent that it becomes too hard to develop and maintain.
Last but not least, if the system is badly designed, the transition to microservices won't magically solve its problems. To put it more bluntly, distributing a messy system will result in an even greater mess. As we have already mentioned, microservices should be considered as a solution when the complexity of the system requires imposing modularity and not as a magical fix for badly written software.
In order to implement a successful microservices architecture, we will need to automate as much of the infrastructure as possible. Eventually, we will be dealing with a system containing a large number of independent services running somewhere across the network. Maintaining such systems manually is virtually impossible.
We will like each service to be automatically built, tested, scaled, and monitored. The cloud infrastructure is a natural microservices environment, which allows you to achieve that. Each service can be run and scaled on resources provided on demand, and the tools available will allow us to build, test, and connect the services in a fault-tolerant way.
You will learn about all of those in this book.
It's time to look how Java EE can fit into the cloud-microservices picture.
As mentioned in The basic architecture of Java EE applications section, traditionally, in Java EE, you were creating JARs with your applications and deploying them on an application server. With microservices, we will like to transform the same kind of JARs into runnable services:
In a traditional scenario, the application server has to support all the APIs specified in the standard.
In a microservices scenario, we will like to transform each JAR, which is an implementation of a microservice, into a runnable JAR. This can be done by creating a runtime for the given microservice and assembling this runtime and the service's archive into a runnable JAR. Since the assembled runtime will be used by only one service, we don't have to include all the Java EE modules in it. The tool that builds your microservices will have to analyze your service's archive and create a runtime, which contains only those functionalities that are required by it.
We have already sketched how we can use Java EE as a base for microservices architecture, but what are the benefits that you will achieve by doing so? Firstly, you will be able to take advantage of proven technologies and your experience with them immediately. Moreover, there is a portability aspect. As we covered in the preceding section, you are encouraged to start with monolithic applications and refactor it microservices, if necessary. Owing to the common set of technologies used and the standard archive format that is used in both scenarios, you can easily migrate between the two, creating an elastic architecture that can be changed and refactored when necessary.