Of the many ways that you can build software two are used most often: based on use cases (vertical slices of end-user functionality through the system) or layers (horizontal sets of related technical functionality). Either way, when done, you’ll end up with a set of cooperating components that implement the system’s business.
The key insight is this: to build a high-quality large system you need to start with high-quality components. Take high-quality components and assemble them into the finished system.
To build high-quality components start with code library writing principles: make your components highly cohesive units of code that are used through well-defined interfaces; ensure that they are independently tested and versioned. A better term for these code libraries would be software parts, since they can be of varied sizes.
A Structure for a Component-Based System
The term component is often used generically when we speak of a software part. It helps to standardize the language when talking about component-based development. Here is one that works well:
- Class is an Abstract Data Type (ADT) that extends the language’s vocabulary. A class is still firmly in the computer language realm.
- Component is an abstractions from the problem domain. This is where the language of the system transitions from the computer domain to the problem domain.
- Module and subsystem represent composite abstractions from the problem domain.
Each part is built from about 5 to 10 subparts. The top part is the system. A system is built out of subsystems. Subsystems are built from modules. Modules are built from components, and components are built from classes. Here is a rule of thumb for sizing these parts:
- Class: 100 to 200 LOC
- Component: 1,000 to 2,000 LOC
- Module: 10,000 to 20,000 LOC
- Subsystem: 50,000 to 100,000 LOC
- System: 100,000 to 500,000 LOC
Following this method of sizing and naming allows you to build a moderate sized system of about half a million lines of code. If you need to build a larger system, then build it as a set of cooperating systems. Use these ideas as a starting set of terms and rules for your next component-based development effort.
Update on August 8, 2010
When I wrote the first version of this article, I wrote it from the perspective of a rather small team where each of us had to write more than one component that we assembled into a finished system. Therefore integration was assumed and obvious: I designed the component in a way to fit perfectly its intended use by another component that was also my work. Given that we were a small team, it was also self evident that whatever I design will have to work well when used by my teammate’s component.
Our emphasis was on componentization. The need for integration was evident and obvious. After all, a system that doesn’t integrate is no good. We were replacing the idea of building the system as a monolith with the idea of assembling the system from components.
Over the years I saw systems built out of components that don’t come together, especially as team size grows. As a reminder, let’s make sure that we heed Deming’s admonition: “Why did the parts work together? Because they were designed to work together.” It is the responsibility of each component designer to work with his or her clients to ensure that the component they design will fit the intended use. And as Dijkstra advises us: “programs should be composed correctly, not just debugged into correctness.”