Photo from Unsplash
Have you ever wondered about the importance of software architecture for full-stack engineers?
If you’re a full-stack engineer like me, who initially was not convinced of its relevance in my daily existence, you might find this article intriguing. After reading “Clean Architecture” by Robert C. Martin, my inner skeptic was convinced.
In this article, I’ll share some key takeaways I got from my reading, covering design and architecture relations, your confidence’s impact, equal treatment principles, assignment prioritization, the role of tests, independence value, and immutability benefits.
By diving into these topics, I aim to provide you with practical knowledge and experiences that resonate with your own journey.
Design and Architecture go hand in hand, but we could draw a dotted line where architecture is considered for decisions at higher levels, whereas design deals with lower-level details. Sounds simple, but how would the implementation look like? Details will always have an impact because they support all high-level decisions. Nevertheless, high and low-level decisions are all part of the same whole. Thus, no clear dividing line could separate them. One simple goal is defined by Robert C. Martin, “…to minimize human resources required to build and maintain the required system”.
Reminds me of the story in “How I met your mother”, where an architect forgot to count in the weight of books while designing a library. Made up or not, it would be an engineer’s job to count in the details.
Being a software engineer is probably not the most stress-free career you can choose. Implementing feature after feature, because clients feel the pressure of the market, is our day-to-day life. The trap is to believe you will be able to clean your code up later; however, the market pressure never decreases. You would think you are faster in not cleaning up right away, you will probably finalize your tickets quicker, but the mess builds, and productivity does not grow well in messes. Test-driven-development (TDD) is one way of writing clean and testable code. An experiment performed by Jason Gorman even shows that, no matter which time scale is being used, with TDD, he was always able to perform better: he used test-driven development on the first, third, and fifth day and on the other days, he wrote code without that discipline. Even on the slowest TDD day, he was faster than on the fastest non-TDD day.
However, developers often live on a changing spectrum of confidence, sometimes on an hour-to-hour basis, depending on how much they accomplish. This feeling can lead to the thought that if they got the chance and time to re-design a whole system from scratch, they would do it better. More often than not, overconfidence will drive the project into the same mess.
Recognizing overconfidence and taking the quality of the software seriously will lead to better results. Self-awareness is, in my opinion, the key because you need to know where one’s limitations lie. Learning how good software architecture looks like is one part, but also checking oneself and being honest when one needs the help of others is as important.
Behavior and structure are the two values that should be top priorities in every software system — ensured by the work of developers who are responsible for the principle of equal treatment. So what exactly should we, as developers, look after? After all, it is not just implementing requirements and fixing bugs, or better said, it should not be.
The first value of software is behavior, the purpose of the software. The second value is structure, or by definition, software architecture. As a counterpart to the architecture of buildings, software is not solid, and its behavior should always be easy to change and in proportion to the scope and not the shape of the change.
If Mr. Martin were in the position to save only one of those values, he would save the structure. Seems crazy, but he has good arguments: “There are systems that are practically impossible to change, because the cost of change exceeds the benefits of change.” Business managers would probably disagree and prioritize current functionality over later flexibility. Balancing those two values is hard, but selling them to the customers is worth solid gold.
Even though it is not an easy task, we can help with prioritizing assignments with a four-tier scale:
Eisenhower Matrix from “What Happens Now?” — John Hillen & Mark Nevins
The difficulty now lies that often problems that are actually urgent and not important and should be 3. priority, are prioritized over the 2., which leads to the unwanted problem that unimportant features take over the architecture of the system.
How would such a dilemma look in real life?
Example of an urgent but not important issue:
A web application has a minor styling issue on a page that is rarely accessed by users. The issue does not affect the functionality or usability of the application in any significant way, and only a small number of users are likely to notice it. However, the issue has been reported as urgent because it affects the overall aesthetics of the application, but it is not considered important as it does not impact the core functionality or user experience.
Example of a not-urgent but important issue:
A web application has a database query that is inefficient and causes a noticeable delay in retrieving data. This issue affects the overall performance and response time of the application, potentially leading to a slower user experience. However, it does not impact the core functionality of the application, and the delay is still within acceptable limits for normal usage. In consequence, the issue is considered important because it affects the performance and user experience of the application. However, it is not deemed urgent as it does not prevent users from completing their tasks or hinder the overall functionality of the application.
It is also important to keep in mind that priorities can change over time. For instance, what at one point in time has been declared as not urgent, like the delay in retrieving data example, can suddenly become very urgent when the number of users increases.
I once attended some physics lectures, and one part that stuck with me was that scientific theories and laws are seen as true until someone says the opposite. The technical term is “falsifiable”, which means they are not provable. For example, my statement “I can fly” is true until someone proves my statement wrong (please don’t).
It does humble oneself if we think that really nobody can manage to keep an eye on all the details a program contains. Without help, one may say; this is the part where tests should move into focus. Since concentrating on one part of the system that works perfectly, can still result that the software fails overall.
Thus, software is comparable to science. Programmers create, by definition, falsifiable units. Tests help us to not show the absence of bugs but their presence. How hard we may try to make a software perfect, the only way to show correctness is by failing to determine imperfection.
In this sense: “falsifiable” = “testable”.
Object-oriented programming has a special significance for a software architect, namely, to control every source code dependency in the system.
Control over dependencies gives us independence. Comes across as ironic that to be independent, you first need to control your dependencies. However, the more you control dependencies, the less they control you. As software engineers, we love independence in every aspect. In the field of software architecture, we are talking about independent deployability and independent develop-ability.
Independent deployability means if we change the source code of one component, only this component needs to be redeployed. As a consequence comes independent develop-ability because, if modules can be deployed independently, it is also possible to develop them independently.
Immutability has kind of a bad reputation when in the end, many problems we face in applications, which require multiple threads and multiple processors, would not happen if variables were not mutable. However, we still are not in the position to have infinite storage and process speed. Therefore, to take advantage of immutability, it is possible to segregate an application, or its services, into mutable and immutable components. The mental work is to figure out a well-structured application.
The idea behind event sourcing is closely linked to a scheme which requires no mutable variables. The strategy consists of storing transactions but not the states. In situations where a state is needed, we simply apply all the transactions from the beginning. It is possible to save a state in a specific time interval. Therefore, if state information is needed, we only need the transactions since our last saved state.
It sounds kind of crazy to me; if this is the case for you as well, it is good to remember that this is precisely the way the source code control system works.
With the right mindset, developers have the power to create beautiful and maintainable software. As software engineers, it is important to be aware of the limitations and to prioritize quality. Achieving this requires self-awareness, understanding the importance of high-level design decisions and the impact of lower-level details. Test-driven development and event sourcing are a couple of the strategies we can use to ensure the software is testable and maintainable. By controlling dependencies and using immutability, developers can create independent deployability and develop-ability. The balance between two values, behaviour and structure, is key to creating a successful software system.
We at & are building software with clean architecture principles in mind. & prioritizes quality above all because we believe in long-term success and deliver robust, scalable solutions that stand the test of time.
If you have any questions, feel free to contact me.