Big Ball Of Mud talks about a software system that has evolved over time without a clear, or consistent architectural structure, which results in a codebase that is difficult to understand, maintain, and extend.
Let me explain this with an example of the Book-My-Ticket application, which is responsible for booking bus tickets. Imagine you want to develop a bus ticketing platform where agents are tasked with booking bus tickets on behalf of users. In this scenario, a user would approach an agent to book a ticket. The agent would then use the Book-My-Ticket application to complete the booking process on the user's behalf. Once the booking is successful, the user would receive notifications via email and SMS. For SMS notifications, the system might be integrated with Twilio, while email notifications would be handled through SendGrid. Additionally, payment processing would be facilitated through integration with PayPal.
Assume this project is started with 5 developers.
1. User Interaction: The process starts with the user approaching an agent with a request to book a bus ticket. This interaction could happen in person, over the phone, or through an online chat.
2. Agent's Role: The agent logs into the Book-My-Ticket application and enters the necessary details such as the user's name, travel date, destination, and preferred bus service.
3. Booking Process: The Book-My-Ticket application processes the booking request. It checks seat availability, confirms the booking, and processes the payment. The agent handles any issues that arise during this process, such as unavailability of seats or payment failures.
4. Notifications: After a successful booking, the system triggers notifications. An email is sent to the user confirming the booking details, leveraging SendGrid for reliable email delivery. Simultaneously, an SMS is sent to the user's phone, providing instant confirmation and details of the booking via Twilio.
5. Payment Integration: The payment for the ticket is processed through PayPal, ensuring secure and efficient handling of financial transactions. This integration allows users to pay through various methods supported by PayPal, enhancing the platform's versatility and user convenience.
By using these integrations, the Book-My-Ticket application ensures a seamless experience for both agents and users, combining efficient ticket booking with reliable notification and payment services.
Given the short timeline for launching the "Book-My-Ticket" app, we decided to develop an application using a straightforward Java and JSP technology stack. We created a WAR file and deployed the application as a single unit. Consequently, all functionalities, including ticket booking, payment processing, booking confirmation, booking cancellation, and user interface, reside within the same codebase.
Requirement: Allow users to book tickets
Over time, new requirements emerged for this application, allowing users to book tickets themselves. Essentially, we want users to perform the same tasks that agents previously handled on their behalf.
We now need to develop a user ticketing system quickly to stay competitive. To achieve this, we hired five additional developers skilled in Java, JSP, and HTML, to complete the development within a month. As a result, both the user and agent ticketing systems were integrated into the same codebase and packaged into the same WAR file to serve both use cases. Additionally, payment processing, email, and messaging services are also included in the same codebase and WAR file.
As the "Book-My-Ticket" application continues to grow, further enhancements are necessary to meet evolving user demands and market conditions.
1. Addition of Mobile App Support: To capture the growing mobile user base, a mobile version of the app needs to be developed. Instead of creating a separate, optimized backend, we decide to reuse the existing codebase. This results in additional layers of code to handle mobile-specific functionalities, increasing the complexity of the system.
2. Integration with Third-Party Services: Users request integration with third-party services like social media logins, external payment gateways, and travel insurance providers. Each integration adds more code and dependencies to the already cluttered codebase, making it harder to manage and test.
3. Advanced Reporting and Analytics: Businesses demand advanced reporting and analytics to gain insights into user behavior and sales trends. This leads to the addition of complex data processing and reporting modules directly into the existing application, further entangling the code.
4. Multi-language and Multi-currency Support: To expand into new markets, the application needs to support multiple languages and currencies. Implementing these features introduces numerous conditional checks and additional resource files, adding to the complexity and potential for errors.
5. Enhanced Security Features: As security threats evolve, more robust security measures such as two-factor authentication, encryption of sensitive data, and regular security audits are required. These enhancements are patched into the existing code, making it even more convoluted.
6. User Feedback and Review System: To improve user engagement, a feedback and review system is introduced. This feature adds new user interface elements, database tables, and business logic, increasing the interdependencies within the codebase.
7. Real-time Notifications: Users and agents request real-time notifications for various events (e.g., booking confirmations, cancellations, payment updates). Implementing real-time communication mechanisms like WebSockets or push notifications adds another layer of complexity to the system.
8. Performance Optimizations: To handle increased traffic, performance optimizations such as caching, load balancing, and database indexing are implemented. These changes are made directly in the existing codebase, making it even more intricate and harder to maintain.
To cater these needs, we recruited additional Human resources and started contributing to the same source code. However, these ongoing changes are contributing to the application becoming a "Big Ball of Mud".
These continuous additions and enhancements, made without a clear architectural vision, further made unmaintainable code and contribute to the application's transformation into a "Big Ball of Mud."
Let’s see the problems with this Architecture
System is Too Complex to Understand
a. Summary: The tightly coupled and integrated features within the same code repository create a large and complex codebase that is difficult for any developer to fully understand.
b. Impact: This complexity makes it challenging to make changes or add new features, leading to longer development cycles, a higher risk of bugs, and increased maintenance burden.
Development is Slow
a. Summary: The large codebase can slow down IDEs, making tasks like code navigation and building changes on local systems time-consuming.
b. Impact: This reduces developer productivity, increases frustration, and extends development timelines.
Single Repository (Merge Conflicts Problem)
a. Summary: With many developers working on the same repository, merge conflicts are frequent.
b. Impact: Resolving these conflicts takes time and effort, slowing down the development process and potentially introducing errors.
Scaling is Difficult
a. Summary: Different features have varying requirements (e.g., CPU for analytics, memory for email and language modules to cache email template, language labels etc.,), but the monolithic nature of the application forces a compromise on server configuration.
b. Impact: This leads to inefficient resource utilization and challenges in scaling the application effectively.
Stuck with Technology Choices
a. Summary: The monolithic architecture forces the use of the same tech stack for all modules, even when other technologies might be better suited for specific tasks (e.g., Python for analytics, React for UI).
b. Impact: This limits the ability to optimize individual components, potentially leading to performance issues and reduced flexibility in adopting new technologies.
Difficulty in Testing
a. Summary: Comprehensive testing is challenging because changes in one part of the system can affect unrelated features. Most of the times testers endup in testing entire Application.
b. Impact: This increases the effort and time required for testing, leading to longer release cycles and a higher risk of undetected bugs in production.
Deployment Complexity
a. Summary: A monolithic deployment means that any change, no matter how small, requires redeploying the entire application.
b. Impact: This increases the risk of downtime, complicates the deployment process, and makes frequent updates more difficult.
Reduced Fault Isolation
a. Summary: In a monolithic application, a failure in one component can potentially bring down the entire system.
b. Impact: This decreases the overall reliability and availability of the application, leading to a poor user experience and higher operational risk.
Limited Parallel Development
a. Summary: The monolithic structure hampers parallel development efforts because different teams working on different features can easily interfere with each other’s work.
b. Impact: This reduces the overall development speed and can lead to coordination challenges and bottlenecks.
Strategies to avoid Big Ball Of Mud
Modular Design
a. Description: Break the system into smaller, well-defined modules or microservices. For "Book-My-Ticket," split the application into separate modules such as ticket booking, payment processing, booking confirmation, booking cancellation, user interface, and reporting. Each module should have a clear responsibility and communicate with others through well-defined interfaces or APIs.
b. Benefit: This reduces complexity, makes the codebase easier to understand, and allows teams to work on different modules independently.
Refactoring
a. Description: Regularly refactor the code to improve its structure and reduce technical debt. Continuously refactor the existing "Book-My-Ticket" code to improve readability and maintainability. For example, isolate business logic from presentation logic and move duplicated code into common libraries and reuse them.
b. Benefit: This helps keep the codebase clean, making it easier to add new features and fix bugs without causing unintended side effects.
Code Reviews
a. Description: Conduct thorough code reviews to maintain code quality and consistency. Implement a peer review process for all changes in the "Book-My-Ticket" application. Reviewers should check for adherence to coding standards, potential bugs, and areas for improvement.
b. Benefit: Code reviews ensure that high-quality code is merged into the codebase, reducing the likelihood of introducing new issues.
Documentation
a. Description: Maintain clear and up-to-date documentation for the codebase and overall system architecture. Document the architecture, individual modules, APIs, and key business logic of the "Book-My-Ticket" application. Include inline comments for complex code sections and maintain a developer guide for onboarding new team members.
b. Benefit: Good documentation helps new developers get up to speed quickly and makes it easier for existing developers to understand and modify the code.
Automated Testing
a. Description: Use automated tests to ensure that new changes do not break existing functionality. Implement unit tests, integration tests, and end-to-end tests for the "Book-My-Ticket" application. Use continuous integration (CI) tools to run these tests automatically whenever changes are made.
b. Benefit: Automated testing provides confidence that the application works as expected, catches bugs early, and makes it safer to refactor code.
Separate Codebase for Each Service
a. Description: Maintain separate codebases for different services or modules. Create separate repositories for ticket booking, payment processing, email notifications, user management, and reporting in the "Book-My-Ticket" application.
b. Benefit: This minimizes merge conflicts, allows teams to work independently, and makes it easier to manage dependencies and version control for each service.
Each Service is Small and Separately Scalable
a. Description: Design each service to be small and independently scalable. Ensure that services like ticket booking and payment processing can be deployed and scaled independently based on their specific load requirements. For example, the reporting service might need more CPU power, while the email service might need more memory.
b. Benefit: This allows efficient resource utilization, improves performance, and ensures that scaling one service does not affect others.
Each Service Can Be Written in Its Own Tech Stack
a. Description: Allow each service to use the most suitable technology stack for its needs. For the "Book-My-Ticket" application, use Python for analytics and reporting, Java for core ticketing functionality, and React for the user interface, if those are the best tools for the job.
b. Benefit: This flexibility enables teams to use the best tools for specific tasks, improving development speed and service performance.
Independent Deployment Cycles
a. Description: Ensure that the deployment cycles of different services are not dependent on each other. Deploy changes to the user interface or ticket booking service without needing to redeploy the entire "Book-My-Ticket" application. Use containerization and orchestration tools like Docker and Kubernetes to manage deployments.
b. Benefit: Independent deployments reduce downtime, allow for faster releases, and make it easier to roll back changes if something goes wrong.
By implementing these strategies, the "Book-My-Ticket" application can avoid becoming a Big Ball of Mud. The result will be a more maintainable, scalable, and robust system that can adapt to changing requirements and market conditions.
System Design Questions
No comments:
Post a Comment