Good design and architecture practices, a solid testing policy, and an agile methodology are necessary, but sometimes they are not enough to find the best solution to a particular business logic, as we will see in this article.
For better or worse, over the last few years I have had to tackle and lead projects that are, to some extent, complex and scalable.
I recognize that this complexity does not lie only in having to solve genuinely difficult problems, with large volumes of data or systems that must withstand heavy workloads or many users, but rather in maintaining a sufficiently flexible design and architecture vision so that these applications can be maintained for years, because some of these projects consist of systems that, by their nature, once installed for a client, will remain active for a very long time (even decades, I would venture), due to the degree of integration and dependency of those businesses with this kind of product.
Without going any further, one of them is the IRIS Telegestion Platform, which today has been in operation for eight or nine years in different installations.
As I sometimes say, an application that is going to evolve a lot and last many years must be built differently from projects quoted by the hour, delivered, and then never changed again.
When you build applications like this, you naturally rely on a set of frameworks and libraries of many kinds (in the example I am discussing, .NET, KendoUI, NInject, and a long etcetera), like any other application, of course.
However, when you implement the specific functionality of the product, in the form of its business logic, we tend to implement it ad hoc, based, of course, on all those frameworks and libraries, as it cannot be otherwise.
We tend to think that a framework in software consists only of that: something you use as a base to build the specific parts of a project, and that is fine and necessary, there is no doubt about it. But that conception of a framework is too limited.
All the over-spaghetti projects I have seen (and they are unbearable) had this defect: too much explicit code to resolve their business logic and an absolute absence of even a minimum abstraction, that is, the implementation of utilities or rules that allow the particular business logic to be implemented.
Programming, in a way, consists of applying a working paradigm that will eventually generate the code for a solution in one form or another. For example, designing and solving a problem using object-oriented programming is not the same as using a functional language. Depending on the language you use, you will use one set of artifacts or another.
But it is not only the tools you use that change (in terms of syntax and utilities), but also your mind learns to approach a problem in one way or another. Let me explain.
Given certain tools, the way of implementing something changes; that is, you think about how to solve the problem based on them. The limitation is not only based on what a specific working environment offers you (objects, functions, lambda expressions, certain APIs, etc.), but also on the fact that you adopt a mental paradigm with which to find the correct solution.
This is why many people accustomed to functional languages, when they learn object-oriented programming, do not use this approach correctly, because they continue to think from the perspective of a functional language (and often conceive classes as simple boxes to put related functions in without exploiting all the abstractions and modeling richness that OOP allows).
An artist who has a palette with many colors and several types of brushes will be able to paint a canvas with a different technique than if they had only a ballpoint pen and an A4 sheet of paper (this is just an example). The same painter, obviously, but the tools condition the way they think to solve the same painting as closely as possible. In the same way (and following with another somewhat strange example), depending on the tool you have to hammer a nail into a wall, you will do it one way or another: it is not the same to do it with a stick as with a proper hammer, is it? Although perhaps it would be much better to have an automatic nail gun.
In software, we can think with the mentality of a framework: when you have to solve truly complex business logic, we tend to implement it directly instead of approaching it from the perspective of a framework. This way of thinking and this way of tackling some software solutions has been very useful to me.
To give a trivial but illustrative example: suppose you are asked to implement an application to solve the operation “2 + 5,” and you write the direct calculation for that operation. Then the application must add another different operation, such as “4*3 - 12,” and you program the calculation of this expression ad hoc (explicitly), and so on, continuously.
Would it not be better to implement a generic calculator library to solve that type of expression? It seems obvious, but that is how the high-level functionality of most applications is often implemented: with direct, explicit code, when, in reality, in many scenarios (and I am not saying in all of them), there is a better solution.
Following the example: domain to solve: simple mathematical operations. Abstraction to implement: calculator (this is your framework for resolving your business logic).
What will be smarter and more productive: implementing the direct calculation for each type of operation, or building the calculator library once and then using it whenever a new expression needs to be calculated? You understand the idea.
I fear that business logic (that functionality that is specific to the domain of a particular application) is often implemented the former way: writing the code directly to make it work without abstracting anything, so that the evolution of that application consists of explicitly writing any new business logic as it appears, even if it is related to the previous one.
That is perhaps why we see fragments of code with files containing hundreds of lines (or thousands, yes, I have seen them) with if, if, if, if… etc.
The abstraction consists of realizing that within the business logic of a particular domain, patterns can always be found, similar logic, similar rules, or functionality suspiciously similar to another.
That is what the framework mentality is about: your job is no longer to directly implement that business logic or high-level functionality with explicit ad hoc code (the mathematical expressions in the example), but to create a framework that makes it easier to implement all those related business logics (in the example, create the calculator).
It is not always possible to apply this approach to software, but if you think this way, I would venture to say that the solutions will be more robust, more flexible to change, and will allow a higher development speed.
Without going into too much detail, in the IRIS Telegestion platform I mentioned earlier, many different but similar messages from certain devices must be managed, each with a more or less homogeneous structure: instead of managing them explicitly according to each type (about forty, if I remember correctly), I created a kind of framework to handle what is common to each of them so that when devices evolve and add a new one, its inclusion in the system is trivial.
Similarly, in another project I participated in, Picly (a server for on-the-fly image processing), there is an extensible plugin mechanism for incorporating every imaginable transformation of images (the framework in this case is the implementation of that plugin manager).
These are just two examples that come to mind; if they had not been approached this way, the development of those two products would have been much more expensive and much more difficult to evolve and maintain.
Think with this framework mentality when you have to resolve business logic; your solutions will be more robust, less expensive, more evolvable, and more professional.