Understanding Problems and/or Finding Solutions
Among the many quotes falsely attributed to Albert Einstein, there is one that says: “If I had an hour to save the world, I would spend 55 minutes defining the problem and only 5 minutes finding the solution.”
To briefly “translate” this statement, we could say that the problem space is the set of all problems that our software application should solve, while the solution space is the set of all solutions that address each specific problem.
While my computer science teacher argued that coding was the final phase — a view I don’t fully agree with even today — my math teacher emphasized that to solve a problem, one must first thoroughly understand it: memorizing everything is futile. She was so advanced that she allowed you to consult the book during the analysis exam; but, despite this, passing her exam was a nightmare!
As human beings, we have a tendency to be problem solvers: as Allen Newell and Herbert Simon argue in their book Human Problem Solving , this is evident in our daily teamwork while developing software. We tend to seek and find a solution as quickly as possible; we tend to exit the problem space as soon as possible, as if we were uncomfortable staying in that zone. The problem space is where the needs of our clients reside; it is the zone that allows us to acquire more information about the problems our clients want us to solve through the software we will develop for them. Essentially, this space constitutes the foundation on which the solution space will rest. Staying in the realm of mathematics, the problem space is always a subset of the solution space: therefore, we should not be in a hurry to abandon the former to enter the latter with a limited understanding of the domain. It’s not surprising that, as developers, we tend to provide a quick solution; it’s our nature that leads us to act this way.
So, we don’t care if we don’t spend enough time thoroughly understanding the problem, staying out of our comfort zone for a long time, resulting in incomplete requirements for the complete problem solution. What matters is that the result obtained will always be incomplete or, worse, wrong; and it is in this space that the failures of our software projects begin.
Stay in the Problem Space
As I often like to repeat, creating software means solving business problems, and when it comes to problems, this time I fully agree with my math teacher: you must understand them thoroughly to solve them.
Not providing an adequate solution from the start means starting off on the wrong foot. We all know that our applications start small but are destined to grow over time, some very quickly: as customer demands grow, a small deviation at the beginning of the journey will lead us to a destination different from what was imagined, making it increasingly difficult to align our solution with the actual problem solution.
How can we avoid all this? By staying in the problem space for as long as possible. Just like in A. Einstein’s quote. Even E. Evans, in terms closer to our world, argues that the first version of our domain model will surely be wrong and that if we think we have found the solution on the first try, we have definitely missed something, not considering all factors of the problem. Not surprisingly, he proposes an exploratory model, consisting of continuous iterations of analysis, proposals, and feedback.
Figure 1 – The exploratory model proposed by Evans.
The Block of Early Solutions
Evans himself defines the action of finding a solution from the beginning to the entire problem as what we colloquially call big front design: a block (“locking in our ignorance”). The problem with this type of approach is that we are forced to make a lot of important decisions about our solution just when we have less knowledge of the problem itself.
Dan North also suggests extreme caution in moving towards a definitive solution. In his article Introducing Deliberate Discovery , he suggests maintaining an approach of lack of awareness of the domain knowledge we are facing. Lack of awareness, for those who want to delve deeper, is the second level of ignorance according to Philip Armour, who wrote an article in 2000 on the five orders of ignorance in software development 
Some Guidance to Avoid Hasty Solutions
But if ignorance is the opposite of knowledge, how can we stay in the problem space to try to bridge the first and increase the second? How do we control our tendency to propose a solution too quickly? We can consider some useful tips.
- Maintain control of the discussion: It doesn’t mean you have to talk continuously, but you have to avoid the discussion converging too quickly to a conclusion. Often, a signal to a colleague is enough to change the direction of the team members. The client, or whoever represents them, takes a lot of information for granted while telling us about their problem, and it is up to us to bring them out clearly in the discussion.
- Listen to different solutions: During the discussion of the problem, after the initial moments of embarrassment, many solution ideas usually emerge, sometimes conflicting. It is important to maintain a healthy and positive environment by including everyone’s suggestions in the solution space.
- Keep the main solutions hidden: As moderators of the problem exploration, your task is to keep the solutions that gradually prove valid hidden for as long as possible. For example, if you use EventStorming as an exploration tool, let participants continue to attach Post-its to the wall, and perhaps guide them in a direction so that it emerges autonomously from the exploration itself. Highlight solutions that seem similar and focus attention on them to understand if they are different ways of expressing the same solution or if they are two different solutions to explore.
Knowing the Client’s Situation
Staying in the problem space is certainly uncomfortable for both parties, the client, and the supplier. The former feels a bit under pressure due to the countless questions they keep receiving, and the latter — often us — can’t wait to propose a solution. Completely addressing the client’s problem takes time, and it’s not always convenient to insist more than necessary in the first meeting. The important thing is to identify the boundaries of the problem and start proposing a solution that can be revisited for feedback and continued after potentially adjusting the course. Our domain will always be in constant evolution, if only for everything that belongs to the level of unknow unknows, i.e., the things we still don’t know we don’t know but will discover as we become more and more experts in the domain we are working on. At this point, we need to understand how to approach, from an architectural point of view, such a solution.
The architecture, like the software we are implementing, must be able to evolve; the time of the metaphor between the architecture of a building and the architecture of software is over. We cannot afford to create an application that we will then abandon because it is trapped in a rigid architecture that does not allow adaptation to market needs, such as scalability, resilience, elasticity.
Pros and Cons of Microservices and Monoliths
If we start with a microservices solution, we will almost certainly find answers to these problems easily, but we will have to pay the price of bringing a lot of accidental complexity into the project at the beginning. Microservices come with some problems:
- Decomposition of the database
- Distributed workflows
- Distributed transactions
- Process automation
- Deployment through containers and orchestrators
The issues mentioned above also involve significant economic burdens for a project that could continue or be interrupted for various reasons.
A solution that considers the good old monolith seems more approachable; the monolith is indeed:
- Easy to implement
- Easy to test
- Easy to deploy
- Quick to realize
Of course, in addition to these merits, it has its own flaws, precisely those that we would like to avoid and have mentioned above.
In recent years, a new architectural solution has been gaining popularity: it is a middle ground between microservices and monoliths, called the Modular Architecture.
The purpose of this architecture is to create a production-ready solution in every respect, not just a simple Proof of Concept (PoC). The basic idea is to identify modules within the solution, and to do this, we have the Strategic Patterns of Domain-Driven Design (DDD), such as Bounded Context, Context Mapping, and, of course, Ubiquitous Language. These patterns are often associated with microservices because they help define the boundaries of the individual responsibilities of each subdomain in our system.
Unlike microservices, in this case, we don’t design different solutions for each identified Bounded Context, but rather a module within a monolith. This will serve us in case our initial boundary has not been identified correctly and needs some adjustment; having to perform these code adjustments within a monolith is certainly simpler than having to touch multiple microservices.
In this article, we have seen that staying in the “problem space,” as uncomfortable and potentially unnatural as it may be, helps us enter the “solution space” with a better understanding of the domain and a greater ability to make the right decisions.
We have also briefly discussed the advantages and disadvantages of architectural solutions in monoliths and microservices, introducing the concept of modular architecture.
In the next article, we will explore exactly what is meant by a module and the advantages that a similar architectural solution brings.
-  A. Newell – H. Simon, Human Problem Solving. Echo Point Books & Media, 2019
-  Dan North, Introducing Deliberate Discovery Link
-  Phillip G. Armour, Five Orders of Ignorance. Viewing software development as knowledge acquisition and ignorance reduction. “Communications of the ACM,” October 2000, vol. 43, n. 10, pp. 17–20 Link