Subsystems are related through client/service relationships. These relationships form a directed, acyclic graph of dependencies. While this is superficially very similar to a classic functional decomposition tree, there is a subtle but very important difference.
The client/service relationship does, indeed, reflect a dependency between the client and the service. However, that dependency is reflected in a flow of requirements rather than a function calling hierarchy. That is, the client(s) define the requirements for the service. In particular, communications between the client and service subsystems are entirely orthogonal to the flow of requirements. This is an enormously important point in understanding the structure of OO applications.
The most obvious example is that of a GUI for communicating with the user. The GUI message loop will actually initiate all messages that cause the application to do anything useful. However, it is the application itself (i.e., the solution to the customer's problem) that actually defines requirements on the UI. The application solution defines:
(1) what data is needed to solve the customer problem
(2) what results will be displayed to the user
(3) what set of user instructions it needs from the user
(4) when it will respond to user instructions
So what is the actual mission of the GUI here? All it does is provide a mechanical mapping of messages and data from one context (the problem solution) to another (the user). In addition that mapping is very narrowly defined in terms of the GUI Window/Control paradigm. So a subsystem that provides a GUI interface with the user provides a very low-level service solution to the rest of the application.
This becomes more apparent when one considers that the application solution does not care whether the actual interface to the user is a GUI, web browser, command line, or heliograph. The requirements on the UI are exactly the same from the problem solution view and the subsystem interface it uses to acces the UI will be exactly the same. Since any of the application solution subsystems may have a need to communicate with the user, the UI subsystem becomes a very low-level, reusable, narrowly defined service to the application as a whole. So the UI subsystem will always be very low in the OO application Package Diagram, usually at the same level as a persistence access subsystem.
Note that this contrasts with the traditional layered models used in RAD development systems where the UI is always at the top and the DB is always at the bottom. That is because those layered models were developed to support CRUD/USER pipeline applications between the DB and the UI. Such applications are dominated by the UI and DB because the "problem" that they solve for the customer is essentially converting the DB view of the data to the UI view of the same data and vice versa. The Business layer in such models essentially does nothing more that provide error checking for UI data entry. The RAD IDEs have already automated the development of such pipeline applications so that using OO techniques there is overkill. Thus the context here are true OO applications where the problem being solved for the customer is complex so that persistence and UI are peripheral issues. In other words, OO applications target problems where the Business layer has multiple subsystems, each with complex processing.
Though the UI makes a nice example because of the clash with conventional layered wisdom, it is important to recognize that the same separation of concerns between requirements and communications applies to all subsystems. This is particularly important when one begins to design client subsystems. One does not think in terms of invoking services. All messages that are sent to the outside world from a subsystem should be announcements of something the subsystem did rather than imperatives to another subsystem to do something. The service will respond to those announcements by doing something. That, in turn, may result in it sending a message back to the client with its results.
The requirements flows are related to the levels of abstraction. Higher-level subsystems perform high-level tasks and delegate the detailed grunt work to services. However, that delegation is at the subsystem definition level, not individual activities. One assigns high-level requirements to the client subsystem based upon its subject matter and level of abstraction. Any requirements not handled by that subsystem but related to it must be allocated to service subsystems. It is important to do that allocation before worrying about the message traffic in order to avoid implemetnation-level dependencies between the subsystems.
Since most of the requirements being allocated are functional requirements it is natural to think of the requirements flows in terms of delegation of functionality. That's fine so long as the delegation is done purely at the functional requirements level. As soon as one starts thinking in traditional imperative terms around particular sequences of solution operations, one runs the risk of bleeding cohesion across subsystems and creating true hierarchical dependencies between the subsystem implementations.
This separation of requirements flows and communications is made possible by good subsystem interfaces that allow the subsystem implementations to be completely decoupled. But that's a story for the next blog post...