The preceding posts in this blog category identified several important characteristics of subsystems: a unique, cohesive subject matter based upon the problem space; a well defined level of abstraction; a clear participation in a client/service relationship; and an ability to encapsulate the subject matter readily within an interface. Alas, these are rather theoretical notions that can be difficult to put into practice without the tempering of practical experience.
The following are suggestions for things to look for when trying to identify subsystems:
Reuse. The issue here is whether the subsystem might be reused rather than whether it will actually be reused. For a variety of reasons good reuse boundaries tend to make good modularity boundaries for an application even if the reuse never happens. Applications will tend to be more robust if modularity is achieved through disciplined internal interfaces. UML Subsystems are ideal for this.
Roles. Most user environments are littered with roles. In every process there will be interactions between people and those participants are usually fulfilling roles of some kind. Since most software replaces manual systems, one way of paralleling the user’s structures is for the software to emulate the manual roles it is replacing.
Concepts. The problem space is also littered with concepts. For example, the notion of ‘an arbitration’ in some contexts might imply archiving evidence and transcripts, issuing complex judgements, publishing results to interested parties, and a host of other activities.
Distributed boundaries. Generally an implementation issue like distribution should not affect how one models the logical solution to the customer’s problem. However, in many common situations the distribution boundaries are very well known in the problem space and they represent a transition from one set of concerns to another (e.g., the bank ATM example used elsewhere in this category). In those situations one must have a compelling reason for having such boundaries within a subsystem.
Knowledge. Typically the problem space has collections of large amounts of information that is closely related but needs to be spread over many classes. It may be useful to collect those classes in a subsystem. An example might be a schematic diagram of an electronic circuit that is required for diagnostic software.
Persistence. Performance and other considerations often lead to persistence formats that do not match up exactly to the way information is used in the application. This is especially common when an application accesses information shared across the enterprise. Encapsulating the persistence mechanism in subsystems is often a good idea.
UI. Most applications have some sort of UI, even a programmatic one. So having some sort of UI subsystem is practically an idiom.
Hardware Interface. Applications that do hardware control usually have a subsystem to isolate the actual register reads and writes. That's because the register addresses and fields tend to change at the bit sheet level with hardware upgrades even though the functionality remains the same.
Behavior. Generally functional decomposition is not a good idea for class level design because it leads to long, rigid chains of nested method calls. However, at the scale of subsystems it can be quite effective at partitioning, especially if one employs the client/service paradigm for dependencies. This is because the system requirements are almost always functional requirements.
Implementation issues. If one is using elaboration as a development technique, then inevitably one must deal with computing space entities (e.g., TCP/IP protocols). One of the problems of elaboration is the difficulty in separating OOA from OOD. By encapsulating computing space support in low-level service subsystems one can ameliorate this problem considerably.
Architecture. Things like error handling, initialization, debugging logs, and other nuts-and-bolts infrastructures are good candidates for subsystems.
Subclassing. Subsystems can be subclassed in terms of generalization and specialization just like classes.
Ultimately it is the problem space that provides candidate entities for enshrinement as subsystems. One can execute exactly the same sorts of blitz techniques used for identifying objects. The scale is just larger.
In addition to looking for problem space entities there is another important aspect to identifying subsystems. It is a very good idea to seek out entities that are invariant in the problem space. That is, entities whose basic definition and cohesion is unlikely to change over the life of the application. Modularity is only valuable if it is stable over time. While subsystems may be enhanced over time their intrinsic nature should not change. The key idea here is that subsystems should provide an enduring skeleton that facilitates maintenance by providing stable interfaces to partition the application.