Architecture, Abstractions and Implementation Details
Software engineers like to talk about things in terms of architecture, abstractions and implementation details. Architecture is your big idea - how the system fits together at a macro level. Abstractions are generalizations - how can you reuse code or entire components within your architecture. Implementation details are things you figure out as they arise - how you actually implement the concrete version of your abstraction.
This hierarchy allows software engineers to ignore various amounts of detail and still make sense of the systems they work on. In general, everyone working on a software project should be familiar with the overall architecture. Engineers should be comfortable working with the abstractions in the components their team works on. Only those involved in implementing and testing a specific part of a component need to have a deep knowledge of the implementation details.
You might argue that every engineer should be able to understand all of the implementation details in the software project they are working on. This is a great goal and something that separates elite software organizations from average ones. The key to this is making it easy for an engineer to get up to speed on implementation details when she needs to. Ideally, an engineer can reason about and work on a software system just fine without understanding all the details. In fact, having to understand all the details means that there is a broken abstraction somewhere or even worse a flawed architecture.
Humans have a limit to the amount of complexity that they can deal with at any given time. We’re limited in how many ideas we can hold in our mind simultaneously- usually between three and seven. Having to deal with unnecessary information or distractions from the task at hand also adds to the amount of complexity we have to deal with. Finally, complexity can come from trying to integrate ideas and concepts to develop understanding. These three things make up the cognitive load a person experiences when they are trying to perform a task. In psychology, each type of load is given a name:
- Intrinsic - the load due to to the inherent difficulty of the task
- Extraneous - the load due dealing with distracting or unnecessary elements
- Germane - the load due to building understanding about a task
(The formal definitions are more precise.)
As people become more familiar with a task, the intrinsic load reduces as they integrate understanding and build a stronger mental model about a task. For example, the first time you got in the driving seat of a car, you were probably overwhelmed by the task of driving. Everything required extreme concentration in order to simply drive round the block. (Driving has a very high intrinsic cognitive load for new drivers.) After a period of learning, driving becomes almost effortless. You build up a strong mental model of how driving a car works and so the cognitive load is reduced. However, your ability to drive well can still be affected by extraneous cognitive load such as using a mobile phone or or trying to quiet kids in the backseat.
Everything is an Implementation Detail
Modern software is a highly complex, multidisciplinary and collaborative effort. An application developer specialised in building responsive web based UIs does not necessarily know how to configure the Kubernetes cluster that serves her application. Ideally she stays focused on building awesome UIs and keeps her extraneous cognitive load low. Configuring a Kubernetes cluster would distract her from her core task. Ideally Kubernetes would be an implementation detail and hidden behind an abstraction so she doesn't even care if it exists.
A multidisciplinary team works best when everyone is focused on their areas of expertise. Everyone can ignore things that are not relevant to the task at hand - meaning they have low extraneous cognitive load. They only worry about implementation details that are relevant to them - everything else is abstracted away.
The key thing about a multidisciplinary team is that everything is an important detail to someone. While the front end developer does not care about Kubernetes, her colleague who is an SRE does. She wants to have control of exactly how the workloads are run, what their network policies are and be able to configure monitoring for the cluster. She probably is not so interested in what language the software running as workloads in the cluster is written in. That is not an implementation detail that matters to her.
Processes Improve Things Until They Don’t
When organizations are small, everyone can pitch in easily to help a teammate out. As organizations get larger, it becomes harder to provide the same level of support. Different teams start taking ownership of different parts of a project and more people means it’s harder to find the person who can help. Typically a few super helpful people end up being the “go-to people” for helping others get unstuck. If not managed well, these go-to people become overwhelmed and resentful of always being asked to help people out.
Most organizations solve the challenge of helping out at scale through processes. They can be informal - maybe a page on the internal wiki site explaining how to get help or they can be formal - a full blown ticketing system to track and route requests. In general, the idea of processes is well intentioned: standardize and make it easy to repeat. A wiki page explaining who is on call to answer questions or a ticketing system that spreads the load more evenly across a team are examples with the best of intentions.
However, most processes are designed to simplify life for the supplier of the service not the consumer. An operations team might install a ticketing system to handle the flood of requests more efficiently. However, the experience of the developers making the request gets worse. Rather than walking over to Jane’s desk and asking her for help setting up infrastructure, the developer now has to fill in an online form and receives no feedback other than an automated reply with a ticket number.
Internal Developer Platforms exist to provide the abstractions that are needed for a multidisciplinary team to work on the same software project together. It makes the infrastructure that software runs on an implementation detail to developers. At the same time, it makes the software that runs on that infrastructure an implementation detail for operations teams.
It reduces the cognitive load of doing the day job. Making things self-serve for a developer removes the extraneous cognitive load caused by having to deal with seemingly arbitrary processes like ticketing systems. It also reduces time spent figuring out how to do something yourself that can be more easily and quickly done by someone else.
At its core, an effective Internal Developer Platform compartmentalizes complexity. Each person has their own little area of complexity that they are expert at dealing with, and that everyone else can safely ignore. Everyone works on their own implementation details against common abstractions and an understanding of how everything fits together. For an operations team member, the implementation details are networking, pod policies in a cluster, and managing database instances. A software engineer’s implementation details are the business problems she is solving. Both work with abstractions: containers are the units that get run on various systems, resources like databases are available for storing data as needed. The architecture is a shared agreement that the whole system needs to run so the customer is happy and the internal developer platform is how everyone communicates to achieve that goal.