Migrating to Microservices: A Guide for Applications

Migrating to microservices requires careful planning. We show you best practices for redesigning monolithic applications and give you an example of splitting a Flask application into two separate microservice endpoints.

Understanding Serverless Architecture in Microservices

Serverless architecture allows you to deploy backend web services on demand. Instead of maintaining your own server configuration, you can develop your software for serverless providers to minimise the associated overhead. Serverless applications are typically deployed from a Git repository to an environment that can scale as needed.

An Overview of Microservices

Serverless deployments typically involve microservices. The use of microservices is an approach to software architecture in which an application is structured as a collection of services that are loosely coupled, independently deployable, and independently maintainable and testable. Microservice architectures existed before the widespread use of serverless deployments, but they are a natural fit. Microservices can be used in any context that allows them to be deployed independently and managed by a central process or job server. Serverless deployments abstract away this central process management so you can focus on your application logic.

Frameworks and State Management

Some architectures are a better fit for microservices than others. If your application logic contains multiple sequential steps that all depend on each other, it may not be a good idea to abstract each of them into separate microservices. In this case, you need a sophisticated controller architecture that can handle and route mid-stage failures. This is possible with a microservice architecture that uses a framework like Gearman to send subprocesses, but can be more cumbersome when working with serverless deployments and can add complexity without necessarily solving problems.

Deploy from Git

When working with microservices, apply the principles of GitOps as much as possible. Treat Git repositories as the single source of truth for deployment purposes. Most language-specific package managers, such as pip for Python and npm for Node.js, provide syntax to deploy packages from your own Git repositories. This can be used in addition to the standard functionality to install PyPI, npmjs.com or other upstream repositories. This way, you can easily combine your own functions under development with third-party libraries without deviating from best practices for maintainability or reproducibility.

API Endpoints and Migration to Microservices

Each of your microservices can implement its own API, and depending on the complexity of your application, you can implement another API layer on top of it (and so on) and plan to only expose the highest level API to your users. Although managing multiple different API routes can add complexity, this complexity can be solved by well documenting each of your microservices’ API endpoints. Communicating between processes with well-defined API calls like HTTP GET and POST adds virtually no additional burden and makes your microservices much more reusable than if they used more idiosyncratic inter-process communication.

Use CI/CD Principles

When redesigning an application to use microservices, you should follow continuous integration and continuous delivery best practices to incrementally replace features of your monolithic architecture. For example, you can use abstraction layering – creating an abstraction layer within an existing implementation so that a new implementation can be created in parallel behind the abstraction – to refactor production code without disruption to users. You can also use decorators, a language feature of TypeScript and Python, to add more code paths to existing functions. This way, you can turn functionalities on or off incrementally.

Portability

Microservices have become popular at the same time as containerisation frameworks like Docker for good reason. They have similar goals and architectural assumptions. In theory, your microservices should be equally suited to running in a Docker container or a Kubernetes cluster as in a serverless deployment. In practice, there may be significant advantages to either option. Very CPU-intensive microservices such as video editing may not be economical in serverless environments, while maintaining a Kubernetes control plan and configuration details requires significant effort. However, it’s always a worthwhile investment to develop with portability in mind. Depending on the complexity of your architecture, you may be able to support multiple environments simply by creating the relevant .yml metadata statements and Dockerfiles. Prototyping for Kubernetes and serverless environments can improve the overall resiliency of your architecture.

Versioning

If you make breaking, non-backward-compatible updates in your microservices, you should deploy new endpoints. For example, you might deploy a /my/service/v2 in addition to an existing /my/service/v1 and plan to gradually deprecate the /v1 endpoint. This is important because production microservices are likely to become useful and supported outside of their originally intended context. For this reason, many serverless providers will automatically deprecate their URL endpoints to /v1 as they deploy new features.

Microservices Migration Example

Implementing microservices in your application can replace nested function calls or private methods by making them standalone services. Take this example of a Flask application that performs a Google search based on a user’s input into a web form and manipulates the result before returning it to the user:

   # Flask-Anwendung

    def google_query(query, api_key, cse_id, **kwargs):
        # Führt eine Google-Suche aus
        pass

    def manipulate_result(input, cli=False):
        # Manipuliert das Suchergebnis
        pass

    @app.route('/<string:text>', methods=["GET"])
    def get_url(text):
        manipulated_text = manipulate_result(text)
        return render_template('index.html', prefill=text, value=Markup(manipulated_text))

    if __name__ == "__main__":
        serve(app, host='0.0.0.0', port=5000)


This application could be restructured into two separate microservices, both using HTTP GET parameters as input arguments. The first would return Google search results based on an input using the googleapiclient Python library:

# Microservice 1

    def main(input_text):
        # Führt eine Google-Suche aus
        pass


A second microservice would then manipulate and extract the relevant data to be returned to the user from these search results:

# Microservice 2

    def main(search_string, standalone=True):
        if standalone == False:
            # Ruft Microservice 1 über HTTP auf
            pass
        else:
            search_results = search_string
        # Manipuliert das Suchergebnis
        pass


In this example, microservice_2.py performs all input processing and calls microservice_1.py directly via an HTTP post if an additional argument standalone=False is passed. You could optionally create a separate third function to merge the two microservices if you prefer to keep them completely separate, but still provide their full functionality with a single API call.

This is a simple example, and the original Flask code doesn’t seem to present a significant maintenance burden, but there are still benefits to being able to remove Flask from your stack. If you no longer need to run your own web request handler, you can then return those results to a static website using a Jamstack environment instead of having to maintain a Flask backend –  A Guide for Applications

Create a Free Account

Register now and get access to our Cloud Services.

Posts you might be interested in: