What are Microservices?

Microservice architecture is a distinctive method of developing software systems that tries to focus on building single-function modules with well-defined interfaces and operations.

The trend has grown in recent years as enterprises look to become more Agile and move towards a DevOps and continuous testing.

Microservices have many benefits for Agile and DevOps teams - as Martin Fowler points out, Netflix, eBay, Amazon, Twitter, PayPal, and other tech stars have all evolved from monolithic to microservices architecture.

The distinction

Unlike microservices, a monolith application is built as a single, autonomous unit. So any changes to the application are slow, as it affects the entire system. A modification made to a small section of code might require building and deploying an entirely new version of software. So to scale a specific function of an application also means you have to scale the entire application.

Modularity is the key

Microservices solve the challenges of monolithic systems by being as modular as possible. In the simplest form, they help build an application as a suite of small services, each running in its own process and being independently deployable. These services may be written in different programming languages and may even use different data storage techniques.

While this makes things more scalable and flexible, it needs a dynamic makeover. Microservices are often connected via APIs and can leverage many of the same tools and solutions that have grown in the RESTful and web service ecosystem. Testing these APIs is now non-negotiable to ensure quality software deployments. It works by validating the communication paths and flow of data throughout your microservice deployment.

Benefits Of Microservices

Simpler to deploy

Deploy in literal pieces without affecting other services.

Simpler to understand

Code is easier to follow since the function is isolated and less dependent.

Reusable across business

Share small services like payment or login systems across the business.

Faster defect isolation

When a test fails or service goes down, isolate it quickly.

Minimized risk from change

Avoid locking in technologies or languages - change on the fly with minimal risk.

Understanding Microservice Architecture

Just as there is no formal definition of the term microservices, there’s no standard model that you’ll see represented in every system based on this architectural style. But most microservice systems share some common traits.

See How SwaggerHub Helps Teams Get Started With Microservices

The Six Characteristics Of Microservices

  1. Multiple Components

    It can be broken down into multiple component services. Why? So that each service can be deployed, tweaked, and redeployed independently without compromising the integrity of an application. This way, you might only need to change one distinct service instead of redeploying entire applications. But this approach has its downsides, including expensive remote calls (instead of in-process calls), coarser-grained remote APIs, and more complexity when redistributing responsibilities between components.

  2. Built For Business

    The microservices style is usually organized around business capabilities and priorities. Unlike a traditional monolithic development approach – where different teams each have a specific focus on, say, UIs, databases, technology layers, or server-side logic – microservice architecture utilizes cross-functional teams. Each team is responsible to make specific products based on individual services communicating via message bus. In microservices, a team owns the product for its lifetime, as in Amazon’s oft-quoted maxim You build it, you run it.

  3. Simple Routing

    Microservices act somewhat like the classical UNIX system: they receive requests, process them, and generate a response accordingly. This is the opposite to how many other products such, as ESBs (Enterprise Service Buses) work. That’s where high-tech systems for message routing, choreography, and applying business rules are utilized. You could say that microservices have smart endpoints that process information and apply logic, and “dumb pipes” through which the info flows.

  4. Decentralized

    Since microservices involve a variety of technologies, old-school methods of centralized governance aren’t optimal. The microservices community favors decentralized governance so its developers can produce tools that can be used by others to solve the same problems. Just like decentralized governance, microservice architecture favors decentralized data management. Monolithic systems use a single logical database across different applications. In a microservice application, each service usually manages its unique database.

  5. Failure Resistant

    Like a well-rounded child, microservices are designed to cope with failure. Since several diverse services communicate, it’s quite possible that a service could fail (e.g., when the supplier isn’t available). In these instances, the client should allow its neighboring services to function while it gracefully bows out. However, monitoring microservices can help prevent the risk of a failure. For obvious reasons, this requirement adds more complexity to microservices as compared to monolithic systems architecture.

  6. Evolutionary

    It’s an evolutionary design and, again, is ideal for evolutionary systems where you can’t fully anticipate what future devices will access your application. Many applications start based on monolithic architecture, but as several unforeseen requirements surfaced, can be slowly revamped to microservices that interact over an older monolithic architecture through APIs.

Examples of Microservices

Netflix has a widespread architecture that has evolved from monolithic to SOA. It receives more than one billion calls every day, from more than 800 different types of devices, to its streaming-video API. Each API call then prompts around five additional calls to the backend service.

Amazon has also migrated to microservices. They get countless calls from a variety of applications – including applications that manage the web service API as well as the website itself – which would have been simply impossible for their old, two-tiered architecture to handle.

Microservice Pros and Cons

As with anything else, whether a microservice architecture is right for you depends on your requirements, because they all have their pros and cons. Here’s a quick rundown of some of the good and bad:

Pros

  • Gives developers the freedom to independently develop and deploy services
  • Can be developed by a fairly small team
  • Code for different services can be written in different languages (though many practitioners discourage it)
  • Easy integration and automatic deployment (using open-source continuous integration tools such as Jenkins, Hudson, etc.)
  • Easy to understand and modify for developers, thus can help a new team member become productive quickly
  • Developers can make use of the latest technologies
  • The code is organized around business capabilities
  • Starts the web container more quickly, so the deployment is also faster
  • When change is required in a certain part of the application, only the related service can be modified and redeployed – no need to modify and redeploy the entire application
  • Better fault isolation: if one microservice fails, the other will continue to work (although one problematic area of a monolith application can jeopardize the entire system)
  • Easy to scale and integrate with third-party services
  • No long-term commitment to technology stack

Cons

  • Due to distributed deployment, testing can become complicated and tedious – and often hinders some of the scaling benefits of microservices
  • Increasing number of services can result in information barriers
  • Additional complexity as the developers mitigate fault tolerance, network latency, and a variety of message formats as well as load balancing
  • Being a distributed system, it can result in duplication of effort
  • When number of services increases, integration and managing whole products can become complicated
  • Developers have to deal with the additional complexity of a distributed system
  • Developers have to implement a mechanism of communication between the services
  • Handling use cases that span more than one service without using distributed transactions is not only tough, it requires cooperation between different teams

Pushing the needle forward

Microservices are not a silver bullet, and by implementing them you expose communication, teamwork, and other problems that may have been implicit but are now forced out into the open. But there are a few ways to mitigate these downfalls:

  • Using API Gateways in Microservices can greatly reduce build and QA time and effort
  • Implementing contract testing with Pactflow is a proven way to reduce the reliance of complex E2E testing

One common issue involves sharing schema/validation logic across services. What A requires in order to consider some data valid doesn’t always apply to B, if B has different needs. The best recommendation is to apply versioning and distribute schema in shared libraries.

Changes to libraries then become discussions between teams. Also, with strong versioning comes dependencies, which can cause more overhead. The best practice to overcome this is planning around backwards compatibility, and accepting regression tests from external services/teams. These prompt you to have a conversation before you disrupt someone else’s business process, not after.

How Microservice Architecture Works

1) Monoliths and Conway’s Law

Conway’s Law: “Organizations which design systems…are constrained to produce designs which are copies of the communication structures of these organizations.

Imagine Company X with two teams: Support and Accounting. Instinctively, we separate out the high-risk activities; it’s only difficult deciding responsibilities like customer refunds. Consider how we might answer questions like, “Does the accounting team have enough people to process both customer refunds and credits?” Or, “Would it be better to have our support people apply credits and deal with frustrated customers?”

The answers get resolved by Company X’s new policy: Support can apply a credit, but it takes Accounting to process a refund. The roles and responsibilities in this interconnected system have been successfully split, while gaining customer satisfaction and minimizing risks.

Likewise, at the beginning of any software design, companies typically assemble a team and create a project. Over time, the team grows, and multiple projects on the same codebase are completed.

This often leads to competing projects: two people will find it difficult to work at cross purposes in the same area of code without introducing tradeoffs. And adding more people to the equation only makes the problem worse. As Fred Brooks puts it, nine women can’t make a baby in one month.

Moreover, in Company X (or any dev team), priorities frequently shift, resulting in communication issues. Last month’s highest priority item may have caused the team to push hard to ship code – but now a user is reporting an issue, and we no longer have time to resolve it because of this month’s priority. This is the most compelling reason to adopt service-oriented approaches (SOAs), including the microservices variety. SOAs recognize the frictions involved between management changes, domain knowledge, and business priorities, allowing dev teams to address them. Of course, this in itself is a tradeoff – it requires coordination. But it allows you to centralize friction and introduce efficiency, as opposed to suffering from a large number of small inefficiencies.

Most importantly, implementing an SOA or microservice architecture forces you to apply the Interface Separation Principle. Due to the connected nature of mature systems, when isolating issues, the typical approach is to find a seam or communication point, then draw a dotted line between two halves of the system.

Without careful thought, however, this can lead to accidentally creating two smaller but growing monoliths, now connected with some kind of bridge. The consequence of this can be marooning important code on the wrong side of a barrier: Team A doesn’t bother to look after it, while Team B needs it, so they reinvent it.

2) Microservices: Avoiding the Monoliths

We’ve named some problems that commonly emerge; now let’s begin to look at some solutions.

How do you deploy relatively independent yet integrated services without spawning accidental monoliths?

Well, suppose you have a large application, as in the sample from our Company X below, and are splitting up the codebase and teams to scale. Instead of finding an entire section to split off, you can look for something on the edge of the application graph. You can tell which sections these are because nothing depends on them.

In our example, the arrows pointing to Printer and Storage suggest they’re two things that can be easily removed from our main application and abstracted away. Printing either a Job or Invoice is irrelevant; a Printer just wants printable data. Turning these – Printer and Storage – into external services avoids the monolith’s problem. It also makes sense, as they’re used multiple times, and there’s little that can be reinvented. Use cases are well known from experience, so you can avoid accidentally removing key functionality.

3) Service Objects and Identifying Data

So how do we go from monoliths to services? One way is through service objects. Without removing code from your application, you effectively just begin to structure it as though it were completely external.

To do that, you’ll first need to differentiate the actions that can be done and the data that is present as inputs and outputs of those actions. Consider the code below, with a notion of doing something useful and a status of that task.

    
    # A class to model a core transaction and execute it
  
          class Job
            def initialize
              @status = 'Queued'
            end
  
            def do_useful_work
              ....
              @status = 'Finished'
            end
  
            def finished?
              return @status == 'Finished'
            end
  
            def ready?
              return @status == 'Queued'
            end
          end
    
    

For this to begin looking like a microservice, what’s next?

    
    # Service to do useful work and modify a status
  
        class JobService
          def do_useful_work(job_status)
            ....
  
            job_status.finish!
  
            return job_status
          end
        end
  
        # A model of our Job's status
  
        class JobStatus
          def initialize
            @status = 'Queued'
          end
  
          def finished?
            return @status == 'Finished'
          end
  
          def ready?
            return @status == 'Queued'
          end
  
          def finish!
            @status = 'Finished'
          end
        end
    
    

Now we’ve distinguished two distinct classes: one that models the data, and one that performs the operations. Importantly, our JobService class has little or no state – you can call the same actions over and over, changing only the data, and expect to get consistent results.

If JobService somehow started taking place over a network, our otherwise monolithic application wouldn’t care. Shifting these types of classes into a library, and substituting a network client for the previous implementation, would allow you to transform the existing code into a scalable external service.

This is Hexagonal Architecture, where the core of your application and the coordination is in the center, and the external components are orchestrated around it to achieve your goals.

(You can read more about service objects and hexagonal architecture here.)

4) Coordination and "Dumb" Pipes

Now let’s take a closer look at what makes something a microservice as opposed to a traditional SOA.

Perhaps the most important distinction is side effects. Microservices avoid them. To see why, let’s look at an older approach: Unix pipes.

ls | wc -l

Above, two programs are chained together: the first lists all the files in a directory, the second reads the number of lines in a stream of input. Imagine writing a comparable program, then having to modify it into the below:

ls | less

Composing small pieces of functionality relies on repeatable results, a standard mechanism for input and output, and an exit code for a program to indicate success or lack thereof.

We know this works from observational evidence, and we also know that a Unix pipe is a “dumb” interface because it has no control statements. The pipe applies SRP by pushing data from A to B, and it’s up to members of the pipeline to decide if the input is unacceptable.

Let’s go back to Company X’s job and invoice systems. Each controls a transaction and can be used together or separately: Invoices can be created for jobs, jobs can be created without an invoice, and invoices can be created without a job.

Unlike Unix shell commands, the systems that own jobs and invoices have their own users working independently. But without falling back to a policy, it’s impossible to enforce rules for either system globally.

Say we want to extract the key operations that are repeatedly executed – the services for sending an invoice, mutating a job status and mutating an invoice status. These are completely separate from the task of persisting data.

This allows us to wire together the discrete components into two pipelines:

User creates a manual invoice

  • Adds data to invoice, status created

    — Invokes BillingPolicyService to determine when an invoice is payable for a given customer

  • Invoice is issued to customer
  • Persists to the invoice data service, status sent

User finishes a job, creating an invoice

  • Validates job is completable
  • Adds data to invoice, status created

    — Invokes BillingPolicyService to determine when an invoice is payable for a given customer

  • Invoice is issued to customer
  • Persists to the invoice data service, status sent

The invoice calculation related steps are idempotent, and it’s then trivial to compose a draft invoice or preview the amounts payable by the customer by leveraging our new dedicated microservices.

Unlike traditional SOA, the difference here is that we have low-level details exposed via a simple interface, as compared to a high-level API call that might execute an entire business action.

With a high-level API, in fact, it becomes difficult to rewire small components together, since the service designer has removed many of the seams or choices we can take by providing a one-shot interface.

By this point, the repetition of business logic, policy and rules leads many to traditionally push this complexity into a service bus or singular, centralized workflow orchestration tool.

However, the crucial advantage of microservice architecture is not that we never share business rules/processes/policies, but that we push them into discrete packages, aligned to business needs. Not only does this mean that policy is distributed, but it also means that you can change your business processes without risk.

SOA vs. Microservices

“Wait a minute,” you may be say, “Isn’t this just another name for SOA?” Service-Oriented Architecture sprung up during the first few years of this century, and microservice architecture (abbreviated by some as MSA) bears a number of similarities.

Traditional SOA, however, is a broader framework and can mean a variety of things. Some microservices advocates reject the SOA tag altogether, while others consider microservices as an ideal, refined form of SOA.

In any event, we think there are clear enough differences to justify a distinct “microservice” concept (at least as a special form of SOA, as we’ll illustrate later).

The typical SOA model, for example, usually has more dependent ESBs, with microservices using faster messaging mechanisms. SOA also focuses on imperative programming, whereas microservices architecture focuses on a responsive-actor programming style.

Moreover, SOA models tend to have an outsized relational database, while microservices frequently use NoSQL or micro-SQL databases (which can be connected to conventional databases). But the real difference has to do with the architecture methods used to arrive at an integrated set of services in the first place.

Since everything changes in the digital world, agile development techniques that can keep up with the demands of software evolution are invaluable. Most of the practices used in microservices architecture come from developers who have created software applications for large enterprise organizations, and who know that today’s end users expect dynamic yet consistent experiences across a wide range of devices.

Scalable, adaptable, modular, and quickly accessible cloud-based applications are in high demand. And this has led many developers to change their approach.

The Future of Microservice Architecture

Whether or not microservice architecture becomes the preferred style of developers in future, it’s clearly a potent idea that offers serious benefits for designing and implementing enterprise applications. Many developers and organizations, without ever using the name or even labeling their practice as SOA, have been using an approach toward leveraging APIs that could be classified as microservices.

We’ve also seen a number of existing technologies try to address parts of the segmentation and communication problems that microservices aim to resolve. SOAP does well at describing the operations available on a given endpoint and where to discover it via WSDLs. UDDI is theoretically a good step toward advertising what a service can do and where it can be found.

But these technologies have been compromised by a relatively complex implementation and tend not to be adopted in newer projects. REST-based services face the same issues, and although you can use WSDLs with REST, it is not widely done.

Assuming discovery is a solved problem, sharing schema and meaning across unrelated applications still remains a difficult proposition for anything other than microservices and other SOA systems. Technologies such as RDFS, OWL, and RIF exist and are standardized, but are not commonly used. JSON-LD and Schema.org offer a glimpse of what an entire open web that shares definitions looks like, but these aren’t yet adopted in large private enterprises.

The power of shared, standardized definitions are making inroads within government, though. The results are visible through in data.gov and data.gov.uk, and you can explore the large number of data sets available as well-described linked data here.

If a large number of standardized definitions can be agreed upon, the next steps are most likely toward agents: small programs that orchestrate microservices from a large number of vendors to achieve certain goals.

When you add the increasing complexity and communication requirements of SaaS apps, wearables, and the Internet of Things into the overall picture, it’s clear that microservice architecture probably has a very bright future.

Further Resources

Tools:

Additional Reading: