Software design matters. The way in which applications are architected profoundly impacts their workload performance, availability, scalability and lifecycle. Traditional monolithic application designs are still commonplace and appropriate for everyday goals.
However, organizations are fundamentally rethinking software design as business demands increase, virtualization and container technologies proliferate, and reliance on cloud deployment grows. Monolithic software architectures are being restructured as independent but interrelated software components called microservices, which enable businesses to build applications that are more scalable and easier to maintain.
Success with microservices requires a clear understanding of the concepts, tradeoffs and considerations of the microservices approach. This comprehensive guide to microservices walks through the entire journey: how microservices work, how they compare to other popular app architectures, important microservices design considerations, and key steps to deploy microservices-based architectures.
Microservices architecture components and functions
Traditional applications are designed and built as a single block of code. A microservices architecture deliberately expresses an application as a series of independent but related services or software components that can be developed, tested and deployed independently, or even as separate software projects. The services interoperate and communicate through APIs across a network using lightweight protocols such as HTTP and REST. Through this process, the greater application emerges.
Typical components of a microservices architecture typically consist of several principal features or functions. In addition to the individual services, typical components of a microservices architecture involve APIs, containers, a service mesh, service-oriented architecture concepts and the cloud.
Microservices and APIs
APIs and microservices are different but complementary technologies. APIs facilitate integration and communication between various services in a microservices architecture, allowing services to request and receive results from one another. This interoperability delivers the app's overall functionality.
At the heart of this structure is an API gateway, which manages, organizes and distributes API calls between services and outside clients. Gateways also routinely handle API security, monitoring and load balancing. Offloading such administrative overhead keeps the microservices themselves agile and lightweight.
Microservices and containers
Microservices are almost always associated with virtual containers, which are constructs used to package services and their dependencies. Containers can share a common OS kernel, which enables them to exist on servers in far greater numbers. They also can be quickly spun up and torn down, often within seconds.
Although containers are not required for microservices, containers make microservices practical. Containers' small and resource-lean instances are perfectly suited to the relatively small codebases found in microservices. Their fast creation and teardown make them (and microservices) scalable and temporary, or ethereal. Modern container orchestration tools, such as Kubernetes, support the ability to monitor containers to detect and restart failed containers with minimal human intervention.
By comparison, a typical virtual machine requires an entire operating system, drivers and other elements. Technically a microservice can be deployed in a VM; this can provide extra isolation, but it also creates unnecessary redundancy and cost. Why deploy and pay for 10 to 20 OS licenses for VMs to create a single application? VMs are far more cost- and resource-efficient for complete monolithic applications.
Microservices and service mesh
While APIs act as the proverbial glue for communication between services, the actual logic that governs communication is another challenge. It's possible, albeit cumbersome, to code communication logic into each service in a typically complex application.
Instead, microservices architectures often rely on a service mesh to abstract service-to-service communication away from the services and into another layer of the infrastructure. Essentially, it creates proxy containers (also called sidecar containers) that relate to one or more services, and route traffic as necessary while allowing those services to continue their operations. A microservices application may call upon numerous proxies to handle various services or collections of related services.
Microservices and SOA
There are similarities between microservices and service-oriented architecture (SOA) but they are not the same. SOA is a software development approach focused on combining reusable software components or services. A common interface allows the services to interoperate across an enterprise service bus, yet requires little knowledge of how each service works. SOA components often rely on XML and SOAP to communicate.
The SOA model works best for services that are largely transactional and reusable across large software systems. SOA isn't as well suited for new or refactored code, or projects that involve rapid and continuous development and deployment cycles -- that's where microservices shine.
In some ways, microservices are an evolution of SOA, but they are not mutually dependent. The primary difference between SOA and microservices is scope: SOA is designed to operate across the entire enterprise, while microservices' scope is confined to the application itself. SOA can complement microservices by allowing applications and components to interoperate with other reusable services across an enterprise.
Microservices and the cloud
Containers and microservices can be deployed and orchestrated in any data center or colocation facility, but they will require an infrastructure that is designed to handle such volumes of integrated services, as well as rapid or unpredictable scaling. Public clouds provide ideal environments for on-demand and scalable computing, as well as orchestration engines, API gateways, pay-as-you-go licensing structures, and other elements that act as building blocks for a microservices architecture.
Microservices architecture vs. monolithic architecture
A monolithic architecture incorporates all of the application or operational logic required to perform work within a single cohesive stack located on a single principal server within the data center. This has always been a logical and memory-efficient way to build applications, but it's inadequate for many modern application and business needs. Let's compare the tradeoffs of monolithic vs. microservices architectures.
How they work
Monolithic applications are traditionally considered all-in-one entities where the entire application is built, tested and deployed as a single codebase. Users access the application through the client-side application interface, or presentation layer. User interactions and queries are exchanged in accordance with underlying application and business logic, which dictate database access, data processing and how results are returned to the client. No monolithic application exists or runs without dependencies -- it's just that most of the application's specific "work" is performed within a single software entity.
A microservices architecture decomposes underlying logic into a series of distinctly different tasks or services, each of which can be developed and deployed separately and communicate through an API. Users interact with the application via client-side app or web portal; the interface distributes client requests to corresponding services and returns results to the user. A microservices application also routinely involves dependencies such as a common OS kernel, container orchestration engines and database access credentials.
Pros vs. cons
Monolithic architecture designs aren't necessarily complex; but, building complex applications with many integration points on a monolithic architecture introduces many possible points of failure, such as software bugs or hardware faults, that could result in extended downtimes and human intervention. Monolithic applications also do not scale well. For example, if an application requires more computing power to handle a spike in user requests, an entirely new instance of that application -- and all of its dependencies -- must be deployed on another server.
At scale, microservices provide a level of efficiency that traditional monolithic applications cannot. When a microservices function requires more computing power, only that microservice is scaled by adding more instances of that service through a load balancer to share network traffic. In addition, orchestration and automation tools used to deploy and manage services can detect when a service crashes or becomes unresponsive, and can automatically restart a troubled service. Finally, a single microservice contains less code and fewer functions to test, and requires far less regression testing to look for unintended consequences of changes or updates. These factors can speed up service development and maintenance.
Microservices have challenges too, of course. As individual services proliferate, the complexity of the microservices environment multiplies. Each service depends on network performance and integrity, and every individual service must be attached to management, logging, monitoring and other tools.
Hybrid and modular application architectures
The transition from monolithic to microservices application architectures can be problematic for developers. A monolithic codebase may not parse well into tidy individual services. As an alternative, consider a hybrid microservices model, which updates a legacy application with a mix of monolithic code and services, all deployed through cloud-based containers.
Another option is a modular monolithic architecture, which seeks to balance scalability, speed and operational complexity. This approach segments code into individual feature modules, which limits dependencies and isolates data stores but preserves the simpler communications, logic encapsulation and reusability of a monolithic architecture.
Benefits and challenges of microservices
The appeal of microservices architecture has grown because of containers, the cloud and hyperconnected systems. Microservices are ideal for IoT applications, for instance. However, a microservices architecture is no panacea. Consider these important tradeoffs of microservices benefits and drawbacks before you choose a microservices architecture for a software project.
Independence. Every microservice module or service can be developed using different languages and tools, and deployed on different platforms. The only connection between microservices components is the API through which they communicate across a network. This brings flexibility to application development.
Scalability. To scale a traditional monolithic application, IT staff must deploy a new copy of the entire application. With a microservices application, only the associated components must scale. It's faster and far less resource-intensive to deploy and load balance a few containers rather than an entire monolithic application.
Lifecycles. That development and deployment independence means different development teams commonly build and maintain microservices components through different continuous integration/continuous delivery (CI/CD) pipelines. This offers more development agility and can enhance time to market. It also reduces testing time and requirements because only the module is being tested -- there is little, if any, need to regression test the entire application.
While the benefits of microservices are compelling, you must also evaluate and address some potential disadvantages of a microservices application.
Performance. A microservice container provides excellent computing performance for its particular task, but many of them must communicate across the network for the overall application to function. Network congestion and latency can hamper performance and undo those individual performance benefits. In addition, the volume of microservices to support applications requires additional network management and control, and may require multiple load-balancing instances.
Monitoring. A microservices application environment can be extremely complex, and all of the components deployed to construct the application must be monitored and reported. This challenge is exacerbated by the ethereal nature of those components, which must be monitored as they are deployed and removed. This requires a high level of automation and orchestration.
Security. Application security is vital to protect access to the software and underlying data, but security can be more difficult with microservices. APIs must authorize and authenticate with every call, and each component must harden the service against intrusion. Security tools and processes must monitor and report in a complex and changeable microservices environment. Extensive automation is needed to attach security configuration and monitoring to each microservices component.
Culture. Microservices development represents a new mindset on how to design, build and operate enterprise applications. This means a business must adjust not only the developer tools and processes they use, but also foster programming skills and team culture to support microservices. Developers must collaborate as they work on different services, and embrace continuous development paradigms, comprehensive process and workflow automation practices, and monitoring and security.
Design considerations for a successful microservices transition
Many microservices implementations still adhere to, and are hampered by, legacy design patterns. This means that architects and developers must adopt new design principles and patterns, consider the implications of database access and interaction, and implement efficient messaging between services. It's a tall order that takes time and experience to deliver.
Design principles. Microservices architecture design principles help streamline the operation of each service and ensure that each service delivers optimum results for the overall application. The most common of these include the following:
- High cohesion. Services should work together with minimum communication.
- Low coupling. Services should have few, if any, interdependencies.
- Single responsibility scope. A service should set boundaries to reflect a specific business requirement, and do only one thing.
- Reliability. Design the services for maximum fault tolerance or resilience so a fault in one service does not disable the entire application.
- Don't share data. Each service should have/access its own database or storage volume.
- Use automation. Utilize automation tools to deploy and manage microservices components (often as part of a CI/CD pipeline).
- Use APIs. APIs are the go-to method for interaction and communication between services.
- Monitor. Use monitoring tools to oversee and report the performance or problems with services as well as manage traffic for best service performance.
Behavioral patterns. Resiliency, integration, discovery and registration are challenges for any application architecture, but especially with microservices. Here are some common microservices architectural design patterns to address:
- Discovery design. Endpoint clients that access a microservices application use client-side or server-side discovery to find an available service and forward a request.
- Registration design. Services employ self-registration or third-party registration to help keep track of which services are busy.
- Flow design. Architects and developers dictate the workflow between the front-end UI and the back-end data through model-view-controller (MVC) or model-view-ViewModel (MVVM) design patterns.
- Saga design. Some microservices rely on process instructions and others perform actual functions, which enables more orderly messaging and reliable recovery.
- Reliability design. Developers allow multiple call attempts (retry) or stop communication with a failed service (circuit breaker) to prevent errors from cascading between services.
- Transitional design. Refactor a monolithic application into microservices, such as through a strangler design pattern, to help segment a legacy application into specific functions and create decoupled modules.
Database implications. Data management for microservices isn't just a matter of design or technology, but rather a set of attributes or principles that support consistency, scaling and resilience. Fundamental microservices design patterns include the following:
- Single database per service. When correlating a service to a database, assign only one database to the service.
- Shared database. If multiple services share a database, concurrency demands can cause conflicts; a nontraditional database, such as NoSQL, can reduce contention but is not appropriate for many types of data.
- Saga design. Create a series of transactions and confirmations that allows the database to back out of a transaction and alert the system that an error has occurred.
- Event-based design. Employ a static database to capture and store records of event-based transactions.
Messaging patterns. Communication between services can pose challenges for developers and architects. Consider the following microservices messaging approaches:
- Loose vs. tight coupling. Loosely coupled services reduce dependencies but often demand more frequent and comprehensive messaging, while tightly coupled services can require less communication.
- Asynchronous vs. synchronous communication. A single channel and a request/response style is common in monolithic applications, but multiple and one-to-many communication channels are better for some microservices applications.
- Message brokers. Lightweight message brokers, such as Amazon SNS, can handle and sort messaging without waiting for responses, and are a common approach in complex microservices applications.
Key steps to deploy microservices
There are two general rules for any microservices project: A distributed microservices architecture isn't right for every enterprise or application type, and there is no single universal plan to deploy microservices in production. There are simply too many potential choices and alternatives available for developers and operations staff. Follow these guidelines to help smooth a microservices deployment and help avoid common microservices pitfalls.
Use the cloud. It is sometimes desirable to deploy microservices in a traditional data center for certain limited deployments. But the public cloud's scalability, on-demand resources and orchestration is an efficient and cost-effective infrastructure for container-based microservices.
Emphasize resilience. Create microservices as stateless entities that can be stopped, restarted, patched and scaled -- all without disrupting other services or the overall application. Designs that allow excessive dependencies between services can turn a planned microservices architecture into a distributed monolithic one.
Decentralize data. Each individual service should access its own data resources to ensure it has the data it needs and prevent data inconsistencies. Services also can use the best database design for the task at hand, and changes to one database won't impact other services.
Eliminate silos. Traditional applications often employ IT groups organized into silos that handle specific parts of the application's deployment. Microservices are better served through cross-functional and highly collaborative teams organized around the microservices application. This demands a flexible and supportive culture to find and fix problems as quickly and efficiently as possible.
Automate everywhere. Modern application development uses tightly coupled pipelines to build, test and deploy software -- and microservices are ideally suited to pipelines. The same automation frameworks that drive building and testing can be extended to deployments, which speeds the release of new updated services.
Use monitoring. An unmonitored service cannot be managed. Use monitoring tools, suited to dynamic component environments, for every service or component within complex microservices environments. Deployment automation helps ensure that each component is properly connected to associated monitoring, logging and reporting tools.
Combine and centralize logs. Microservices logging often involves separate logs for each service. However, data in log files stored within individual containers are lost when the container is destroyed, and thus can be difficult or impossible to find. Store logs in persistent storage. Consider data consistency and synchronization when creating many logs so that it's possible to combine them and form a cohesive picture of activity when trouble strikes.
Embrace security. Microservices architectures are uniquely sensitive to security issues. Large numbers of services, API dependence, disaggregated log files and extensive network communication requirements create a large attack surface with multiple attack vectors. Some microservices security best practices include the following:
- Using multiple layers of security. A single security tool or framework is rarely adequate to mitigate all security risks. Use all security measures available -- firewalls, tokens, encryption, monitoring and so on -- to secure services and the overall application.
- Focusing on access security. Encrypt any data accessed and exchanged between services, and secure APIs with proper authentication and authorization that adheres to least-privilege principles.
- Communicating and collaborating. Developers and operations staff must work together with security groups to create a comprehensive security approach and environment; this concept is known as DevSecOps.
Extend testing. The complexity of microservices architectures extends to testing them as well. Typical application end-to-end testing is less than ideal for microservices, so explore other options. Test services as individual subsystems, with cloning or simulation to handle any dependencies or related services. Contract testing scans how software under test uses an API, and then creates tests based on that usage.
How one deploys microservices also affects their testing. Rather than a sweeping deployment or update, each microservice can be deployed, updated, patched, tested and managed separately from other services. Prepare rigorous tests and strong rollback plans in case trouble arises. Consider testing cloud-based microservices locally; with sufficient service isolation, tests on one service won't affect others.
Adopt versioning techniques. Microservices adoption demands proper version control to ensure that the overall application includes the correct array of services, some or all of which may be updated and maintained independently. Even microservices that are independent and loosely coupled may have dependencies. A change in one version may require changes to other services, which results in corresponding version changes to other services. Microservices versioning techniques include the following approaches:
- URI versioning
- header versioning
- semantic versioning
- calendar versioning
Tools and platforms to manage microservices
The next few years should reveal transitions in microservices architectures and design patterns. Organizations will continue to gain expertise to explore microservices, or take smaller steps such as modular monolith designs.
The tool ecosystem for microservices design and deployment also is evolving. Few platforms provide the benefits of Kubernetes for microservices, whether installed locally or used as a public cloud service. Kubernetes' orchestration and management functions are ideally suited to containers typically used with microservices, as are its capabilities for self-healing, configuration management and version control, multi-cloud and hybrid cloud support, load balancing and security.
Beyond Kubernetes, organizations can choose from a growing assortment of tools to test, deploy, monitor and manage microservices environments. For example, popular microservices testing tools such as Gatling, Hoverfly, Jaeger, Pact, Vagrant, VCR and WireMock handle tests on everything from load testing and unit tests to API interactions and distributed tracing.
Organizations also struggle with microservices monitoring and management after deployment. Comprehensive tools can help optimize monitoring and observability, failure detection, and gathering metrics from logs to identify performance and stability issues. Some examples of such tools include Sentry, Sensu and Sumo Logic.