To TCA or Not to TCA: Weighing the Pros and Cons of Adopting The Composable Architecture
With the growing popularity of The Composable Architecture (TCA), it undoubtedly offers several advantages, including structured state and event management, precise side-effect handling, testability, and streamlined dependency management. Notably, its architecture simplifies testing, allowing developers to efficiently and reliably validate functionality.
However, with the increasing number of dependencies, one of the most significant challenges in maintaining code over time is managing the ever-changing set of frameworks. As popular frameworks like RxSwift rise and fall, it’s crucial to pause and carefully consider the implications before introducing a new one.
Challenges and Considerations
To understand the potential issues that might arise after a framework’s popularity declines, we can examine a well-known example: RxSwift. Its popularity has significantly diminished, primarily due to Apple’s introduction of its proprietary reactive programming solution, Combine. Although TCA may not encounter direct competition of this magnitude, it could face similar challenges in the long term:
- Architectural Dependence: Both RxSwift and TCA are architecture-heavy, with RxSwift typically pairing with MVVM and TCA aligning closely with Redux principles. Committing to a specific architecture due to a dependency can lead to technical debt if adjustments or an architectural shift become necessary later on.
- Talent Acquisition: Popular frameworks can initially attract skilled developers, as many are keen to use the latest tools. However, if a framework’s popularity wanes, finding new talent with relevant experience may become more challenging.
TCA also presents unique challenges inherent to Redux-style architectures:
- Setup Complexity: Setting up each page requires creating distinct State, Action, and Reducer components and binding them to the view, which can increase initial setup time.
- Risk of Shared State Complexity: As the app grows, shared state can become overly complex, introducing dependencies that increase the risk of unexpected behaviours, as changes to shared states can affect multiple areas of the app.
Points for Discussion
- Architecture-Heavy Requirements: Relying on frameworks like MVVM or Redux can lead to technical debt if changes are needed later.
- However, as software evolves, some degree of change is inevitable. Dependencies will always be part of the process, even if they shift over time. Even first-party dependencies shift over time (e.g., Apple’s transition from UIKit to SwiftUI). Rather than focusing on exit strategies, establishing clear and unified architectural standards can simplify maintenance and reduce complexity in the long run.
- On the other hand, the architecture-heavy nature of TCA can actually be beneficial, as the added complexity allows for more granular testing and helps prevent unasserted states.
- Reliance on Trends: The popularity of a framework can impact hiring; if its use declines, finding developers with relevant skills may become more challenging.
- Investing in training and comprehensive documentation is essential for sustainability. Before adopting a framework, teams should conduct thorough research and prepare resources to facilitate onboarding.
- High Setup Cost Per Page: Structuring State, Action, and Reducer for each page may increase development time.
- In complex apps, this is often manageable, particularly if components are already organised in an MVVM structure. TCA further separates state, action, and reducer from the view model, with clear definitions for each. This enables future maintainers to locate state and logic easily, in well-defined locations.
- The upfront cost per page can be mitigated with Xcode templates and built-in macros, which streamline the setup process.
- Risk of Bloated Shared State: Without disciplined state management, shared state can evolve into a tangled web of dependencies, similar to the pitfalls of using global variables throughout an app.
- Defining clear guidelines for shared state usage and implementing a review process can prevent unintended state sharing and help maintain codebase clarity.
- Avoid adding anything to shared state until it is necessary. Instead, place everything in a domain-specific state.
Conclusion
When considering TCA adoption, it’s important to evaluate its fit for both small-scale projects and large commercial applications:
- Small-Scale Projects: For smaller apps, TCA may feel like overkill if the goal is basic functionality. However, if learning or testing TCA is the objective, fully committing to the framework—with complete testing—is ideal. TCA is particularly well-suited for developers exploring and experimenting with it or for teams or individual developers who rely heavily on unit tests, especially in the absence of dedicated QA support.
- Large Commercial Applications: In commercial applications, TCA can be highly effective, provided it is supported by thorough documentation, onboarding resources, and unified standards:
- Commitment to a Single Standard: Any framework works best when it is consistently applied across the app, minimising fragmentation and simplifying maintenance. Ideally, legacy components are gradually updated to the new standard to create uniformity.
- Onboarding and Training Resources: Developing onboarding materials, such as coding standards and training documentation, ensures that new hires integrate smoothly into the team’s technical stack.
- Clear Guidelines for Shared State: Defining and reviewing shared state usage prevents unintended dependencies and keeps the codebase manageable.