How exactly do we design for minimal complexity when it comes to developing APIs? Well, we can accomplish this with proper interfaces and encapsulation.
Join the DZone community and get the full member experience.Join For Free
In our previous article, we discussed complex systems. Complex systems are a sum of simpler systems put together. A system's complexity is based on the number of things it must logically do to accomplish its intended purpose. We must understand how to manage complexity at every level of engineering, from the design to the actual building, to make the higher-level systems accessible to the market.
A system's complexity should only increase when absolutely necessary for a component to function and not because of its design. A poor design places an unnecessary burden on its builders and can reduce efficiency. This is wasteful. Given two different plans for a system that should accomplish the same thing, the design with the least amount of complexity should be chosen. In fact, a vital prerogative of a system's strategy is to achieve the absolute minimal complexity needed to accomplish its goal. How exactly do we design for minimal complexity? We accomplish this with proper interfaces and encapsulation.
Designing Minimal Complexity
Let's imagine we are handed an authentication system that was built without a design. This authentication system's goal is to correctly identify that a known entity has sent us a request. This system is one build. It is a monolithic, single package of source code that compiles into one single process. This system can take one of five different types of authentication tokens. The system can use each token type to look in three separate databases for an identity. Finally, if an identity is found, the system can send it to four different authorization systems. This combination of data and processes gives us sixty possible successful logic paths. Any upgrade or change to this system must consider all sixty of these paths, along with all of the unsuccessful paths not explicitly counted.
We can immediately recognize that handling upgrades and testing for this system will be more expensive than necessary. We start to think of this in a more modular fashion. If we broke this system down and created one component for each authentication token, we would end up with 5 modules, and each one would have 12 successful logic paths. If we further isolated such that one type of authorization was tied to a specific identity database, we would end up with only 4 logic paths.
Our module would only need to ensure the authentication token was formatted to one type. After validating the format, it would go to the one database holding identities for this authentication type. If the identity was found, it would simply forward the identity to one of four possible authorization systems. An update to a system that only manages four successful outcomes is orders of magnitude easier than updating a system that handles 60 possible successful results. Cyclomatic complexity is a method developed by Thomas McCabe in 1976 to mathematically measure software based on the number of linearly independent paths.
When we isolate and reduce the number of processes being handled by any system, we reduce the degree of uncertainty involved in the engineering process. If we increase the number of functions one module handles, we increase its uncertainty and lose the ability to manage predictable outcomes. Understanding and maintaining predictable results is the foundation of creating simple systems.
If all possible effects for a given system can be actively handled, then the system is not complex. Breathing is a simple process. You breathe in, and you breathe out; all outcomes can be managed. Driving is a more complicated process. As a driver, you must handle acceleration, steering, and all other drivers around you. While the acceleration and steering processes are simple enough, the need to maintain situational awareness about other drivers in a changing environment increases the driving process's uncertainty. One can argue that almost all of the driving's complexity is derived from this component. If it was just about acceleration and steering, driving may not be complex at all.
A Sum of Simpler Components
Suppose complex systems can be described as the sum of simpler components. In that case, the complexity of the simpler components affects the parent system exponentially. The parent system will be much more unpredictable if each individual part becomes less predictable. Remember that experts on a complex system can completely understand the simpler components which combine to create the larger system. This means that each individual piece, large or small, must reach a point where it can be thoroughly understood.
In addition to managing the complexity of simpler components, these components must also be easily accessible by the system as a whole. Let's go back to our authentication system. Here, we have a perfectly isolated part that predictably handles a specific authentication format and flawlessly retrieves identity information from a single database. Our authentication system is of little use if it can't pass the data to the appropriate authorization system.
Likewise, suppose it can give the data, but the authorization system cannot receive the information. In that case, it is also of little use. Clean and proper interfaces are just as crucial as well-designed encapsulation. Interfaces can be thought of as contracts between two systems. One system promises to hand off data in a specific structure, sometimes at predictable intervals. The other system promises to accept data structured to follow the agreed-upon interface. You add to the ability to manage a much larger system with predictable complexity when two systems can use a well-defined interface and hold to their promises.
Put together, proper encapsulation and interfacing with simple systems provide an outsized upside effect to the engineering of a much larger system. Poor encapsulation increases the uncertainty and the amount of work needed to maintain the more extensive system. Uncertainty can become downright unmanageable and require a costly redesign from the ground up when left unchecked.
Poor interface design prevents two well-encapsulated systems from operating well together and can reduce all of the benefits received from the encapsulation. The more extensive system must understand where each sub-component stops and where the next one starts. When all components are dependable, well-encapsulated, and have a clean interface, the engineering tasks become enormously efficient.
In our next article, we will explore the history of open source development. A historically significant process in which humanity has collectively built these components together.
Opinions expressed by DZone contributors are their own.