Why TDD Can Be Enemy in Microservices Architecture

Why TDD Can Be Enemy in Microservices Architecture

Cherish DEV was contacted by the company T (due to a strict NDA, we can’t disclose the name). This company was about to move from a monolithic architecture to a microservices one. Their goal is to improve the scalability and independence of their product development. They had been successfully using the Test-Driven Development (TDD) approach for several years and treated it as the core of their development process.

However, while TDD helped them maintain high code quality in the monolith, it caused problems when migrating to microservices.

Over time, the team noticed that the time spent on maintaining and updating tests became more significant. Tests began to delay the development process rather than help it. Moreover, many of the tests developed within TDD were found to be ineffective in the new microservices context.

In this article, we'll take a closer look at their case study, understand what went wrong, and discuss how testing in a microservice architecture can be approached differently to avoid similar problems.

1. Benefits of TDD for Monolithic Applications

Test-Driven Development (TDD) is a software development method in which the developer first writes tests that determine the desired behavior of a function or method, and then writes code that allows those tests to pass. This not only helps ensure high quality code, but also promotes a deeper understanding of how a particular function or method should work.

TDD has a number of advantages, especially in the context of a monolithic architecture:

1. Continuous Feedback: TDD provides continuous feedback to developers about the functionality of their code, allowing errors to be identified and corrected more quickly.

2. Improving code quality: Because tests are written before code, TDD helps keep code clean and minimize errors.

3. Documentation: Tests written within TDD can serve as a form of documentation, allowing new team members or other stakeholders to quickly understand what a specific piece of code does.

4. Architecture improvements: TDD practice can lead to a cleaner and more modular architecture, since it requires functions and methods to be independent and uniquely defined.

TDD can be ineffective for a microservice architecture due to the coupling and interdependency between different services. It is difficult to write comprehensive tests in a microservice environment because each service typically interacts with other services, and changes to one service can affect the performance of others. Moreover, since each microservice is typically developed by a separate team, coordinating tests between teams can be a complex and resource-intensive process. Thus, while TDD can be useful for ensuring code quality within a single microservice, its effectiveness is reduced in a more complex microservice architecture.

2. Company Product Overview and TDD Applications

Company T is developing a large online trading platform. They service many types of products, from electronics to food. As a monolithic application, the platform concentrated all functions including inventory management, order processing, content management, customer service and more into a single code base.

When they decided to move to a microservices architecture, they started by gradually separating individual functions into their own microservices.

Application of TDD

Company T decided to begin its transition to a microservice architecture by developing an order processing microservice.

Development process with TDD:

1. Definition of functional requirements

The T developers started by defining the basic functional requirements for the order processing microservice. These requirements included the following basic functions:

  • Creating an order: The user should be able to create a new order. The order must contain information about the user, the selected product and its quantity.
  • Order Processing: After receiving a new order, the system should automatically process it. This includes checking that the required item is in stock, reserving the item for the order, updating the order status to “processed,” and updating inventory information.
  • Order tracking:The user should be able to track the status of their order at any time. The system should provide up-to-date information about the status of the order after each stage of processing.

2. Writing tests

Given these functional requirements, the T developers wrote a corresponding set of tests. Each functional requirement was translated into one or more tests.

For the "Create Order" function, the developers wrote tests that checked that:

  • the order is successfully created in the system
  • the order contains correct user and product information
  • the order is in the "new" status after creation

For the "Order Processing" function, the developers wrote tests that checked that:

  • the system checks the availability of goods in the warehouse
  • the system reserves the required quantity of goods for the order
  • The order status is updated to "processed" if there is enough product in stock
  • Inventory information is updated after order processing

For the Order Tracking function, the developers wrote tests that verified that:

  • the user can request the status of his order
  • the system returns up-to-date information about the order status

3. Writing code

After writing the tests, the developers wrote code that would allow these tests to pass successfully.

If any test failed, the code was reworked until all tests passed green. This could include fixing bugs, refactoring code, or adding new features.

The entire process was iterative: writing the test, writing the code, validating the test, refactoring the code, and so on, until all the features were fully implemented and all the tests passed.

Problems Caused by TDD

At first, everything went smoothly. However, when they started integrating the order processing microservice with other parts of the system, problems arose.

With the choice of microservice architecture, TDD began to cause undesirable effects.

  1. Service dependencies: Many of the microservices were interdependent. Each change in one microservice caused a chain reaction that required updating tests in other services. This resulted in an increase in support time from 15% to 35%, significantly reducing team efficiency.
  2. Tests against reality: When introducing a new order processing service, internal tests failed, despite the fact that everything worked perfectly in isolation. The company noticed that tests created in TDD were unable to model the interaction between services, which led to 20% of errors in order processing.
  3. Scaling issues: One of the tests, testing the processing of 1000 orders at a time, worked successfully during the development stage. However, when run in a production environment with a real workload (about 10,000 orders per minute), it resulted in a 40% decrease in service performance.

TDD, which had proven itself in a monolithic architecture, turned out to be incompatible with the new microservices structure. This impacted team efficiency, increased support time, and impacted service performance, which could ultimately impact customer trust and the company's financial performance.

3. Possible alternatives to TDD in microservices

Let's look at alternatives that may be more suitable for use in a microservice architecture.

1. Contract testing

Contract testing is an approach in which tests are created to test the interactions between individual services. These tests typically focus on interactions across specific "contracts" or "interfaces", ensuring that each service communicates correctly with the others.

  • Advantages: This helps detect interoperability problems between services early, without the need for extensive integration testing.
  • Disadvantages: Contract tests can be difficult to write and maintain, especially if the “contracts” change frequently.

2. Integration testing

Unlike unit tests, which test functionality within a single service, integration tests test how multiple services interact together.

  • Advantages: Provides greater confidence that all services work together correctly.
  • Disadvantages: Can be complex and expensive to maintain, since any changes in one service may require updating tests in other services.

3. System level testing

System-level testing involves testing the entire system as a whole. This usually involves running real use cases and verifying that all services work together as expected.

  • Advantages: This allows you to discover how the system will perform in a real environment.
  • Disadvantages: This can be a complex and resource-intensive process, requiring significant infrastructure and data preparation.

4. Testing at the application level

Here tests are written to test functionality at the user level. This involves testing how users interact with the system and making sure everything works as expected.

  • Advantages: Testing at the application level provides a high level of confidence that the system will work correctly for the end user.
  • Disadvantages: These tests typically require more time and resources than other types of tests. They may also be more error-prone due to the complexity of interacting with the user interface.

Each approach has its pros and cons, and the best choice will probably involve a combination of different strategies specific to the context and needs of a particular development team.

4. How Cherish DEV helped Company T overcome the problem

Based on our analysis of Company T's current testing processes, we saw that their TDD approach didn’t provide adequate test coverage for the microservice framework. We developed and implemented a new testing strategy that included the following aspects.

1. Contract testing

We implemented contract testing using Spring Cloud Contract. This allowed us to ensure stability and reliability of interactions between microservices, while ensuring their independence from each other. Contracts were agreed upon in advance and used to write tests that ensure that each microservice meets its obligations.

2. Integration testing

We helped the T team implement automated integration tests that tested interactions between microservices. We used TestContainers to mock dependent services and create an isolated test environment for each service.

3. Continuous integration and continuous delivery (CI/CD)

We implemented a CI/CD process using Jenkins and GitLab CI. By doing this, we provided quick and constant feedback to developers about the status of their code. We implemented a continuous testing practice in which tests were run automatically whenever the code changed.

4. Monitoring and logging

A monitoring and logging system was implemented using Prometheus and ELK stack. This is how we began to monitor the performance of each microservice in real time. This provided an additional level of control and visibility for Company T.

Conclusion

Transitioning from a monolithic architecture to a microservices organization can be challenging. One of the main lessons we can learn from Company T's experience is that time-tested approaches such as TDD may not always be effective in the new microservices context.

Here are some key takeaways:

  1. Adaptability of approaches: Microservices bring significant diversity to the software development landscape and require a rethink of some traditional practices, especially in the area of testing.

  1. Collaboration and Transparency: By working closely with Company T, we were able to help them move from their traditional testing approach to a new model that better suited their microservice architecture.

  1. Ongoing training: Every new project is an opportunity to learn and improve our methods. We continue to work with T and other clients, using our learnings to improve and refine our approaches.

  1. Long term strategy: It is important not only to solve existing problems, but also to anticipate potential future problems. We offered Company T tools and processes to ensure the quality and stability of their microservices in the long term.

As we can see, microservice architecture presents significant opportunities and benefits, but also introduces new challenges to the testing process. Cherish DEV's approach to solving this problem allows for increased reliability and testing efficiency while maintaining the benefits of Agile development that microservices provide.

要查看或添加评论,请登录

Cherish DEV的更多文章

社区洞察

其他会员也浏览了