There’s no denying it: at some point in your career, you’ve likely heard about Clean Architecture and how valuable this pattern can be. Your colleagues may have discussed use cases, abstractions, and layers that promote code maintainability.
In my honest opinion, this is a canonical event for every developer. This exposure influences how we think about processes and architecture in software development. My primary goal with this article is to help you avoid blindly following trends and going with the flow, especially if you don’t have the experience to discern what fits your specific project.
Ultimately, I want to help you choose the right approach for your needs, not simply tell you what’s right or wrong.
What the f*** is clean architecture anyway?
Clean Architecture, popularized by Robert C. Martin, advocates for a structured, testable, and maintainable way to design software. Its core principles of decoupling, dependency inversion, and separation of concerns form the bedrock of many successful applications.
From the definition above, we can see that this pattern is quite outstanding in terms of the technical features and resources it brings to our SDLC (Software Development Lifecycle). However, many of the developers I’ve worked with treat every word in the paragraph above as gospel, with some adopting Clean Architecture as if it were their religion.
What’s currently lacking is the ability to consider potential drawbacks. There is no one-size-fits-all solution in software engineering. The sooner engineers understand this, the better.
Every software decision requires choosing what to optimize for. Here are a few questions that help me decide whether to use Clean Architecture or opt for a simpler approach:
- Do we need to ship this fast? If so, what tradeoffs are we willing to make?
- How often will this part of the software change? Will this feature be reused elsewhere within the company?
- What are the availability requirements? How many users will interact with this specific part of the software?
- How quickly do we need to iterate on this software layer? Is it a ‘write once and forget’ situation, or will someone be responsible for ongoing maintenance?
Common lies of clean architecture
Let’s get real for a moment, clean architecture is useful, quite establishes a pattern of development that developers can follow with ease, but that’s not the case for “all projects” or the majority of them that don’t have what it takes to take advantage of clean architecture. It’s like you are trying to fit a shoe of size 12 in a project that uses the size 5.
- What if the project grows and becomes complex?
- Most projects grow in size and complexity over time as features are added. While gradual growth allows you to determine the best approach for each feature, introducing Clean Architecture from the outset doesn’t inherently optimize for future complexity. Instead, it introduces complexity immediately. You lose the flexibility to adapt and choose the most suitable approach for different parts of your project as it evolves.
- We can have software that is not too tangled together
- A common claim is that Clean Architecture leads to less coupling in software. However, this isn’t entirely accurate. While each layer within the system can be designed in isolation, they cannot function independently if your goal is to deliver features.
- These layers work together only when arranged in a specific dependency structure and enabled to interact with other architectural elements. You inevitably have use cases relying on entities, databases reliant on controllers, and so on.
- While Clean Architecture might offer packaging flexibility, you ultimately must connect these components to achieve your desired functionality, and that inherently creates coupling.
- Write testable code
- It’s often argued that Clean Architecture promotes better testability. But, what if I told you its reliance on dependency inversion means you’ll often use mocks – those artificially created, ‘dumb’ versions of classes designed to mimic expected behavior?
- The problem with mocks is they’re ideal for catching errors where expected behavior doesn’t exist or doesn’t align with what’s deployed in production. While not a problem exclusive to Clean Architecture, its structure pushes you towards using mocks for many of your tests.
- It’s tempting to mock every dependency in a complex system. However, over-mocking makes tests brittle. If implementation details change, your tests may need significant maintenance, leading to false positives. Finding the right balance between unit tests and broader integration tests is essential.
- We can optimize for change
- How often do you replace implementations in production? How often have you participated in a team migrating from one database to another, or replacing an entire library? These changes can happen in software engineering, but they are often infrequent.
- A common argument for Clean Architecture is that abstractions allow for easy replacement of concrete implementations. This is not always accurate.
- Consider a simple system using Clean Architecture with drivers written specifically for a non-relational database like MongoDB. If your team decides to move to a relational database, simply inheriting and creating a new concrete implementation likely won’t suffice. If you believe otherwise, that may be overly optimistic.
- These types of changes are often too complex to be handled by abstractions alone. You might need to significantly redesign interfaces to accommodate new requirements or deprecate old ones. This can render the argument of optimizing for change less compelling.
The Drawbacks of Clean Architecture
- Increased Complexity: Separating concerns into layers (Entities, Use Cases, etc.) can increase the complexity of your codebase, especially for smaller projects. Developers need familiarity with the pattern, and navigating through multiple layers can decrease readability in some cases.
- Overhead: Clean Architecture’s focus on interfaces and abstractions introduces additional boilerplate code. This overhead might be significant for smaller projects or where development speed is a top priority.
- Performance Considerations: While not a dealbreaker, excessive layers of abstraction can potentially introduce minor performance overhead compared to more monolithic structures. This is most likely relevant in performance-critical areas of your application.
- Over-Engineering: For simpler projects with limited business logic, the extensive structure imposed by Clean Architecture can feel overly engineered. Simpler architectures or patterns might offer faster development times with adequate maintainability.
When to Reconsider Clean Architecture
- Small-Scale Projects: If your project has a predictable scope, limited development time, and minimal complexity, the benefits of Clean Architecture might not outweigh the additional setup and mental effort it demands.
- Rapid Prototyping: Experimenting and iterating rapidly sometimes necessitates sacrificing structure for agility. Clean Architecture’s rigidity can get in the way when initial requirements are highly fluid.
- Performance-Critical Modules: In specific performance-sensitive parts of your system, direct coupling and eliminating some abstractions might be necessary to squeeze out maximum performance.
- Team Skills: If your development team isn’t fully acquainted with Clean Architecture, the learning curve and potential misinterpretations could compromise development efficiency and clarity.
Alternatives to Consider
- Layered Architecture: A simpler version of the same concept, with less strict dependency rules, can offer balance between structure and agility.
- Model-View-Controller (MVC), Model-View-Presenter (MVP), Model-View-ViewModel (MVVM): These tried-and-tested patterns work well for applications heavily focused on user interfaces.
- Domain-Driven Design (DDD): If understanding and modeling complex business domains is central to your project, DDD offers a different angle compared to the more technical focus of Clean Architecture.
Conclusion
Clean Architecture is a powerful tool that has its place in software development. However, recognize that it’s not universally applicable. Assessing project size, team expertise, and the importance of strict testability over raw development speed will help you make informed architectural decisions. Remember, the best architecture is the one that best serves your project’s specific needs and constraints.
Leave a Reply