TL;DR; Computers are re-usable machines that can be applied for many functions, by studying the architecture of a computer we can understand or associate with how to future-proof our applications following a similar pattern.
How to future proof your application?
When you start to build an application or product there are many things to think about. What is the purpose of the product, what is its mission, what are the features we need, how will it be used, what is the best technology stack to use, etc? And these are all important questions, gathering requirements, understanding your goals, understanding the skillsets of your team. One major thing to think about is architecture or design, a well-designed application is an application that does not increase cost and decrease productivity over time.
Thought should be given about how to organize your application in such a way, that your team can continue to maintain the application based on economies of scale. This means that as your application scales the productivity should not decrease and cost should not increase in a relative proportion to the scale and complexity of your application. If the development effort continues to be more than the revenue of your product then you will not have a product or business for long.
How are computers architected?
So, how should we think about architecting our product's code? Many architectural patterns are available. As a thought experiment, let's look at is the architecture of a computer. The design of a computer is a great example of an architecture that has lasted for many years and scales.
If you think about it, the computer is the most useful tool we have, yet without instructions it is a brick that does not do anything. When given peripherals, operating systems, and applications, it can serve many purposes.
A computer is made up of the following components, CPU (central processing unit) this component is responsible for executing instructions, Memory RAM (random access memory) which is responsible for storing data in a temporary state for very fast reads and writes, Storage DISK (solid-state drives) which is responsible for storing data in a permanent location that can be accessed. These three core components make up the modern computer. Then you have a keyboard, monitor, printer, network, etc. These are peripherals that the computer uses to interact with the physical world.
You also have an operating system and applications that can be loaded on the computer. These systems are what transform the computer from a brick to a set of specific machines or applications that actually do something.
There are significant benefits to this architecture, it allows for the computer to be used and re-used for many different applications with many different form factors and functions, from watches, phones, virtual reality, productivity, appliances, drones, etc. Using this architecture, we are able to create utility from idea to instruction to apply instantly. How does this relate to software architecture?
Clean Architecture is the concept of separating side-effects from business rules or business logic. In computer architecture, we can think of side effects as peripherals and storage systems, and memory and CPU. The operating system as the bridge and applications as the business logic. This separation creates a highly reusable architecture, if the keyboard goes bad, you simply replace the keyboard, or if you want to swap the display for a VR Headset, you change the driver. Since drivers are injected into the computer, the computer does not have to know the implementation details of the VR Headset, it just needs to talk to a driver or specific API surface.
Separation of Services
Leveraging design patterns like dependency injection, closures, and functional programming, you can provide the basic model of a driver or bridge. These patterns allow the business rules/logic to perform functionality in a pure side effect free zone, then when the application sends the result of all of that calculation to the external service, whether that service is the presentation or a given external service, like a database, cache, or storage system. This separation increases the effectiveness of testing and organizes your application into a system of replaceable and upgradable sections. On the interwebs you will see a lot of talk around micro-services versus monolithic architecture, but regardless of the style of your architecture the details are in defined boundaries and separating your logic/rules from your services and side-effects.
Clean Architecture is not easy, because it requires an extra abstraction at the time of creation may seem to be worth the extra effort. At hyper we have built a suite of services that makes this easier, checkout https://hyper.io for more information.
Unfortunately, the majority of software applications are not designed this way and it is creating huge costs and lots of churn in the industry, we are not building on the shoulders of giants as much as we should be. For reference, check out The Developer Coefficient Report. Over 40% of a developers time is spent dealing with technical debt. This should not be the norm and a significant amount of that technical debt is create by tightly coupled services and the spread of business logic and rules over several layers of an application.
Over time as the product needs more features and becomes more complex, this separation will become a time-saver, by creating boundaries between your business logic and services.
The proof is right in front of us, while the computer started with the input of punch cards and the output of green bar paper, we were able to evolve the external devices and services without changing the core architecture. And the cost of the computer went down and the productivity of creating the computer went up.
Designing applications to separate presentation and service layers from your business logic will empower your teams to change and modify services without having to change or break the application over time.
There are many solutions that support this design, at hyper we use FP and transform async functions that contain side effects into pure Task/Async functions then execute them at the edges of the boundary of the application using lazy execution.
We also use closures for dependency injection so that our business logic inverts the dependency of the service by injecting the pure Task/Async function into the business rule function. As you will learn by reading the above-linked book and articles there are plenty of ways to approach this design.
Check out hyper's collection of services
Regardless of your approach, having clear boundaries between your services and your application is a great way to get started, check hyper a service that gives you this separation by giving you access to core application services using a general-purpose API.