Choosing a software framework
A software framework (a framework), or a software development framework, is a standardized set of tools that aim to solve certain problems by consistent approaches.
A software system typically needs quite a few frameworks, so that they can focus on the business functions instead of lower-level concerns such as logging, JSON transformation, and configuration management. These frameworks provide a proven way to achieve the target software architecture. Choosing a framework is a part of architectural decision-making.
It is rare that organizations build every framework themselves these days. The major reason is that most of the frameworks are open-sourced and supported by the community. It would take a lot of justifications for an organization to decide to develop its own framework while there are similar competing frameworks that can be used for free.
Some technology companies develop their own frameworks when there are no existing ones that suit their needs. Some companies develop their own frameworks with the intent to compete with the other frameworks, and to potentially monetize from consulting business or to cross-sell their other products. It would take a lot of research effort and talent to achieve that.
The other option would be to choose a framework that already exists in the market, commercial or open-sourced.
The new framework paradox
New frameworks are released every month with the intent to solve the age-old problems of existing frameworks. Usually, there are one or two popular frameworks on the market, and the new frameworks advertise that they solved the old ones with an approach that everyone has always wanted.
Of course, there are true paradigm-shifting new frameworks that made engineers more productive and really have moved the industry forward with an innovative approach. For example, Ruby on Rails has transformed the repetitive and boilerplate code configuration of web development into inference and conventions, hence vastly reducing the number of lines of code.
But there are also a lot of cases where the new frameworks started with innovative approaches that did not go too well. And here comes the new framework paradox.
If a new framework aims to replace a framework that has been around for many years, the new framework will need to cover a lot of areas and keep the “new approach” in each area. This is a huge undertaking for the contributors.
For example, the Spring frameworks were created in 2002 to simplify dependencies of code by using Dependency Injection (DI) and Inversion of Control (IOC). But now, the frameworks have evolved to cover an extensive range of features, such as web, messaging, security, persistence, and so on. A next-generation framework to replace Spring frameworks would have to cover over 20 years of development, with a very comprehensive coverage of technical areas.
The most significant risk is that the new framework may have solved one of the longest-standing problems of a framework but it falls short of the areas that are fundamental and essential. It traps the engineers who adopt the new framework and makes them face the dilemma of whether to fix the new framework or return to the old one.
Another risk is that the community may not agree on what the “new approach” should be, and therefore, multiple new frameworks are created to solve the same age-old problem of the old framework. Engineers who want to try a new framework face the choice overload problem. And sometimes, it becomes a choice paralysis as there is no single definitively better choice to choose from.
Let us say your team has chosen a framework and everyone is quite happy with it. However, for whatever reason, the major contributor has decided to not work on this project anymore. Then, your team is at risk of the framework not being kept up to date with the fixes and planned enhancements. Not to mention that most open-sourced frameworks are contributed by normal engineers who spend their personal time for free on this.
How to compare and decide between software frameworks
However, in a real situation, the team would still need to choose some frameworks to move forward. An example situation would be a framework for logging messages for a Java application. Do we use the Java Logging framework that comes with the standard Java Development Kit (JDK), Apache Log4J, or Logback? How could we make the most sensible choice? Unfortunately, there are no golden rules that guarantee the best choice, but there are several aspects that the team should consider before making the decision.
Community
Community is the most crucial factor in your consideration. People are the reasons the framework is created, used, and maintained. Without people, the framework will not continue. There are at least three areas of the community for the framework to look for:
- Firstly, the bigger the community, the more likely the framework will have someone to continuously support and enhance the framework. A framework should be like a living being, powered by the people in the community. Also, reasons for having a large community for a framework are likely that the framework is universally applicable and of acceptable quality for general usage.
- Secondly, we need to look at how well the framework is supported by the community. It could be as simple as getting help from another user on how to use the framework. It could also be the quality and quantity of technical blogs written by the members of the community to share their tips on how to apply the framework to problems. It could be measured by suggestions the community made for new features and enhancements.
- Thirdly, we need to see how the members of the community communicate with each other. Do they have a Slack channel, a Discord server, an email distribution list, or any instant messaging platform? How responsive are the members of the community when people post their questions out there? Are the people helpful and positive in receiving feedback?
Contribution
Every commit to the source code repository made up the framework the way it is now. It is worth checking some statistics to understand how actively the framework is being maintained.
When was the last commit? Was it recently updated? How many commits have been made so far? Also, we can check the number of commits in the last month, the last 6 months, or the last year. Moreover, we can look at the variety of contributors. A good sign is that the commits are done by a variety of contributors, not only the usual ones. It indicates a diverse and healthy growth from contributors putting their efforts into the framework.
How many forks and branches are there? Bigger numbers usually indicate healthy growth that either some members of the community are working on a change or there could be a variant of the framework soon. It is likely that there are useful features already in the code base that people are willing to spend their effort on.
The number of tags indicates historical releases and may give a hint about the evolution and growth of the framework. However, be careful of versions under 1.0 (e.g., 0.67), or simply just build numbers. The contributors in this case may not want to commit to the current shape of the framework, and there may be breaking changes in the future.
Versions under 1.0 also could mean contributors may not have confirmed their commitment to keep the framework running for long yet. Extra caution must be taken if you intend to put a 0.x library dependency in your production system. It is going to be difficult if the library discontinues or introduces breaking changes.
We should also look at the source code and get an impression of the code’s quality and test cases. We should glance at the test coverage to understand how deep and broad the code was tested. This would help us predict the reliability and stability of the framework.
Tooling and documentation
We should also consider whether the framework uses mature tooling to manage itself. It may include an issue tracking system that members of the community can submit bugs and track how long it takes for a bug to go from reported to fixed.
The framework may also use an established Continuous Integration (CI) system. This is also a good sign of a healthy, long-running, and mature framework since there is a need to automate builds to handle the number of commits, control the quality, and release the framework.
Documentation is a key factor to consider since this is where engineers learn how to use the framework. The documentation does not necessarily need to be polished or automatically generated. It is the quality of the content that matters. And diagrams would be nice if they help engineers understand the concepts.
Interoperability with other frameworks
Many frameworks were designed to work with other frameworks, and some of them have innate dependencies on other frameworks. This is common and not a bad sign; however, caution must be taken on the impact.
Adopting a framework that uses or works with another framework implies we are also indirectly adopting the other framework. Is the other framework compatible with the engineering approach the team has taken? Do we allow engineers in the team to use the transitive dependencies directly in the code?
Even if we are OK with the other framework, we still need to ensure that the versions are compatible. For example, framework A may have used the Apache Commons IO library, version 2.14.0, and our project currently uses 1.4. Importing framework A to our project would bring version 2.14.0 as a dependency to the project. Luckily, build frameworks such as Gradle and Maven provide a graceful way to explicitly specify a version and exclude a particular version from the transitive dependency. In this example, we will upgrade our dependency on Apache Commons IO to 2.14.0 from 1.4 to use framework A.
Building instead of choosing a framework
Engineers might want to build their own framework instead of choosing an existing one. Under certain conditions, this could be beneficial.
If the software has unique requirements that cannot be met by existing frameworks, then it would justify building a bespoke framework. It could be a very specific domain, or it could have very strict non-functional requirements. For instance, engineers for High-Frequency Trading (HFT) software might write their own framework to meet ultra-low-latency requirements.
Building a bespoke proprietary framework might also be justified if the organization treats it as a competitive advantage in the market with cutting-edge technology.
It may also be the start of a new open-sourced framework in the community if no such framework has existed before. In this case, it may be beneficial to gather engineering talents among the communities and collaborate.
What if we made the wrong choice?
Despite all our best efforts, we might still have chosen the wrong framework. The framework may not have delivered the intended behaviors. The contributors may have given up on the project. The framework may have taken a novel approach that no longer suits our needs.
The adoption of the wrong framework becomes technical debt. Unfortunately, we need to source a replacement framework and plan the refactoring works to remove this dependency.
The technique of refactoring is beyond the scope of this book, though. And it is not always possible to avoid choosing the wrong framework. All we can do is exercise our due diligence in the process of decision. If appropriate, we can also create interfaces so that only minimal classes in the code base have direct reference to the framework, while the framework usage to the rest of the code base is transparent.