August 7, 2016

The Framework Dilemma

Software development to me is creative and innovative work that rapidly changes the world we live in. This is a great part of what I love about it. At least in enterprise application development, however, software itself is rarely the source of innovation. It is merely an enabler for the business to change and innovate.

One of many interesting insights I took from a seminar with Tom and Kai Gilb was owed to Tom's casual remark that the business domain is the most enduring aspect of our software. After all, many business domains predate software development by hundreds if not thousands of years.

A century ago, I could also have managed my bank account, bought insurance or request that a taxi pick me up. Nowadays I can do so from a mobile phone, wherever I am and whenever I want. Well, almost. The point is, software makes business processes a lot faster, cheaper and more available. And it is this gain in efficiency that allows companies to pursue previously unviable ways to do their thing.

Companies need to adapt to an ever-changing economic environment. Those who can adapt and evolve faster are generally expected to be more successful. The speed of adaptation is limited by a number of constraints. In modern enterprises the evolvability of critical software systems is just one of those constraints. And it is our job as developers to ensure that it never becomes the bottleneck.

It is from this perspective that I look critically upon frameworks, especially those for distributed enterprise or web applications (e.g. JEE, Spring, Rails).

A Double-Edged Blade

Frameworks give us a huge jump start when kicking off development on a new project. They provide solutions to many common problems like messaging or consistency of distributed state. They also come with their own opinion on how applications should be designed. We are well advised to share that opinion if we want to get the most out of a framework. Indeed, I think it is fair to say (as Luke Vanderhart does in this post), that frameworks themselves are the application and our code is merely a plugin and some configuration that makes this application work for us.

Now, as long as we develop within the framework and not against it, we will be able to make fast progress. The problems start when our business needs drive us to do something that is not well supported by the framework. Simply saying we cannot do it would mean failure. So let's look at our options:

  • we can look for a convoluted solution that somehow manages to do what we need,
  • we can extend (or hack) the framework, or
  • we can change to a framework that better supports our needs.

The first option is probably the most common. I can't use dependency injection in my @Singleton @Startup beans, because our app server initializes those before the CDI container is brought up? But I am injecting configuration values via CDI! Well, time to implement a workaround that uses some form of static lookup. As these workarounds accumulate, the complexity of the code base rises and the initial speed advantage that the use of the framework brought us is gradually lost.

Extending the framework, if even possible, is also a raw deal, as we now have to maintain the extension as well. It also requires a lot more skill. An example that I actually like is bean testing, a useful approach to test JPA queries against databases without having to bring up a slow app server.

Changing the framework in the middle of an application's lifecycle is not very common. Once we have some extensions and workarounds in our code, it can be very hard to even figure out the actual cost of a migration. Of course, once we reach a point where we have to migrate, we will likely be the bottleneck that hampers our company's agility.

To Use Or Not To Use?

In an ideal world, we would already know at the start of development which problems we will have to solve. We could pick the most suitable framework and quickly develop while always remaining within the premises it sets out. In some cases we can confidently decide not to use a framework at all. Instead we would proceed with a smart choice of libraries and write the necessary glue code ourselves.

In the real world, of course, we do not know at all what we will face. Instead we have to reduce the risk of making a wrong decision. As always, when managing risk, there are two aspects to be considered: the likelihood of occurrence and the consequences.

We can reduce the likelihood of choosing the wrong framework by studying different frameworks, their design philosophy, how they solve common problems for us, but also which they don't solve. A good understanding of the problems themselves, grounded in theory, is really useful to debunk bogus claims of vendors. Ultimately though, the lack of predictability in software development means that we can never be sure that we make the right decision.

Which brings us to the question of how we can minimize the damage done by a wrong decision. The damage depends on how strongly we are affected by and how long we have to live with our mistake. If an application is small and short-lived, using a favorite framework to bring it up quickly is an attractive choice. If we are about to build a huge monolith that is expected to live for the next twenty years, any framework will be horribly out of date at less than half of the expected lifetime. That's a situation we'd like to avoid.

With the world of enterprise applications turning more and more towards cloud-based microservices or even serverless architectures, the huge, long-lived monolith is an unlikely choice for any new greenfield project. Unfortunately, these monstrosities of our past linger on and demand maintenance. But there are still ways to tame them.

Around 2005, Alistair Cockburn described the hexagonal architecture pattern. Uncle Bob went on and built upon this and other ideas when describing his Clean Architecture. The basic idea is to push the framework and other cumbersome dependencies to the outer boundary of the application. Importantly, this is a boundary we control! Besides great testability this gives us framework and technology independence. Even migrating between frameworks then becomes a very viable option.

If only we had used that architectural approach from the outset! The knowledge was available in 2005! But that would have slowed us down a bit. And speedy initial development is one of the main attractions of frameworks. Perhaps then, we should start to consider strong coupling to a framework a technical debt. Maybe it was worth to incur such a debt initially, but now we will continue to pay interest until we get rid of it.

Tags: Design