Transition to microservices for a logistics company

    Helping a logistics company boost software performance and scalability by shifting from a monolithic architecture to microservices

    Product description

    We helped our client upgrade a warehouse and transportation management logistics app that:

    has 40,000 registered users across various time zones
    processes 200,000 transactions a day
    is a high-volume and fault-tolerant system

    Our client came to us with:

    a monolithic Java application
    an architecture based on MySQL, Jetty, and nginx
    ineffective manual testing and deployment processes
    an iterative Agile approach to completing and upgrading features

    Monolithic architecture challenges

    The app’s monolithic architecture inhibited its development. The app was hard to scale as its modules were interconnected. Implementing new libraries, frameworks, and languages would have affected the entire app and significantly increased the cost of development.

    The app was too large and complex to update it quickly and correctly:

    A growing number of vital changes and the system redeployment they required led to long system downtimes.

    Changes in business logic led to changes in all parts of the system.

    Even the smallest upgrades required testing the entire system.

    To overcome these challenges, we decided to optimize the app by shifting to a microservice architecture. We also improved the continuous integration / continuous development (CI/CD) pipeline and ensured automated testing.

    Challenges that formed the product backlog

    Huge amount of time spent making changes to the product

    High software latency leading to loss of users

    Need to manage an increasing number of microservices

    Memory leakage that jeopardized the app’s proper work


    Minimizing time spent making changes to the product

    Adding new features and making changes to the app required one to three months for testing and checks — even for hotfixes and bug fixes.

    We solved this problem by:

    identifying logical parts of the app to move each to its own microservice

    separating microservices by technical characteristics

    deciding on the order in which to move parts to microservices

    adding WARs with microservices to Jetty, which is responsible for connecting servers to each microservice

    For example, we moved the business feature responsible for changing user parameters to a separate microservice. Now, each time the business logic changes, we don’t lose time on reloading Jetty. Consequently, users don’t face system downtime.

    Reducing software latency

    Parts of our MySQL database were initially scattered between different data centers. This caused high software latency and jeopardized user retention.

    To reduce the delay between an input and the desired output, we replicated the entire database across different data centers. As a result, if one data center doesn’t work properly, we can switch to another and use its copy of the database.

    Managing an increasing number of microservices

    When we implemented 20 and more microservices, we faced the problem of their typization and versioning. We also experienced difficulties deploying many WAR files in Jetty. It took us up to two hours to update all microservices.

    The following steps helped us solve these issues and fully shift to a microservices architecture:


    To address the issue of microservice typization, we created client libraries (module APIs) for service controllers. We used the same call interface for client libraries and controllers.

    Data management

    So that basic business features and their transactions do not affect several servers simultaneously, we decided to use Redis for events and user actions. We deployed Redis as an intermediary between the database and server. As a result, users can smoothly transfer between servers.

    Shifting to a horizontal architecture

    We turned a vertical architecture into a horizontal architecture, divided it across several data centers, and added a separate server for Elastic Stack. The Kibana user interface collects data by indexes and doesn’t use the server’s system resources.

    Wrapping Kubernetes around the app

    To ensure easy scaling and shifts from one cloud service provider to another if needed, we wrapped our app in Kubernetes. We transformed the app to have a stateless architecture to provide simple request sending and the creation of multiple database instances.

    Detecting and handling memory leaks

    We faced a memory leak problem when subsystems took over and kept resources no longer used by the app. This issue was critical, as the the app was consuming an increasing amount of resources, which result in a fatal OutOfMemoryError.

    We solved the memory leak problem by:

    using subsystems’ native mechanisms to deal with the out-of-memory condition

    running the system using a custom JAR service with a scenario for detecting memory leaks

    handling memory leaks by using an out-of-memory killer, an established mechanism for recovering system memory


    We improved CI/CD and started to use GitFlow, which facilitated source code management.

    Continuous Delivery schema

    Continuous Integration schema

    After switching to GitFlow, we ensured automated testing and implemented a new deployment pipeline for the app. With continuous integration, we started collecting and running tests for each commit, running unit and integration tests, and collecting and stacking artifacts.

    We moved from a monolithic app to microservices. This helped our client get rid of typization and versioning problems as well as issues with changing the work of sessions.

    How the app benefits from a microservice architecture:

    Updating the system takes less than five minutes and adding new services doesn’t affect other system modules.

    The system allows developers to scale the app horizontally using different technologies, programming languages, frameworks, and libraries.

    The microservice architecture within Docker allows for creating distributed development teams that can work independently on different parts of the app.

    Looking to improve your software performance?

    We can help you transit from a monolithic architecture to microservices and enhance your system performance, maintainability, and scalability.

    Get in touch with us