July 15, 2004

1.0 Partitioning basics

Blog root page
next post in category

Many, many moons ago when I took my first training class in an OO methodology I got the worst advice of my technical career. The context was a Domain Chart (the equivalent of a UML Package Diagram where subsystems are identified). Someone in the class asked how much time one should spend on developing the diagram. The instructor's answer was, "Half a day would be too much."

Today I believe that for a large application the Package Diagram is the single most important diagram in UML. If you screw it up you will pay for it in major ways over and over during the entire life of the application. The Package Diagram defines the most basic structure of the application and it is essential that structure be stable throughout the application life. That structure defines boundaries for requirements allocation, communications, parallel development scope, subject matter cohesion, levels of abstraction, distributed processing, and "firewall" decoupling.

Clear definitions for partition subject matters and precisely defined interfaces are absolutely crucial to parallel development. Years ago the Group VP gave a presentation on a joint project between my division and another one three thousand miles away. The VP was originally from our division and he dropped by to say Hello after the presentation. My greeting was, "Hi, Joe! You do realize you haven't got a prayer in hell of carrying this off, don't you?" There main reason I felt that way was that we had tried the same thing a couple of years earlier with the same division and it had been a disaster because of huge cultural and technical differences.

A year later the product was delivered. It took three days to integrate our piece (~ 250 KNCLOC device driver) into the system on the West Coast and no one from our shop was even there are the time. In other words, it essentially Just Worked. The irony here is that what made it work was good application partitioning. The VP pushed back on us that exactly the sort of partitioning we were currently preaching (we had only recently converted to OO development) was the solution that would allow the different divisions to work largely independently. We defined the subsystems and their interfaces and then went merrily off to build them in an autonomous fashion.

[The real irony here is that the VP was a Hardware Guy, not a Software Guy. He was just a good listener when we extolled the virtues of good application partitioning. He then hoisted us on our own petard and made us practice what we were preaching. (He was also a very smart guy, which is why he went from Rookie Hardware Engineer to Group VP in about fifteen years.)]

Good applicartion partitioning is based on four simple concepts: cohesive subject matters, consistent level of abstraction, client/service relationships, and interfaces. The key lies in getting them to play together properly. Subject matter defines the semantics of the partition. A well defined level of abstraction ensures that objects implementing the subsystem have the same level of logical indivisibility so that peer-to-peer messaging can eliminate heirarchical spaghetti dependencies. Client/Service relationships provide a consistent base for allocating requirements to subsystems. Intefaces provide the same sort of logical encapsulation of subsystem implementations as they do at the class level. One view of an subsystem is that it is a Class on steroids.

These concepts sound good, but putting them into practice is a whole other thing. Subsequent posts to this blog will address each of the Big Four concepts and will offer some practical insights on how to do application partitioning properly.

Blog root page
next post in category

June 08, 2004

2.0 Subject Matters

Blog root page
previous post in category
next post in category

Using the phrase ‘subject matter’ to describe cohesion is convenient but the term is one of those that seem to be meaningful but when one drills down into the details it turns out to mean very different things to different people. Unfortunately I do not have a precise definition so all I can do is provide some guidelines that have been useful in the past.

The simplest guideline would be to regard subsystems as analysis classes with rather broad scope. The subject matter of an analysis class is a unique, self-contained, readily identifiable, indivisible entity or concept in the problem domain. The same view applies to subsystems except that they have broader scope and can be further subdivided.

I believe that the most important characteristic of a subject matter is that it represents something in the problem space. When one shows a Package Diagram to a domain expert that expert should have an immediate, high level, intuitive understanding of what each subsystem represents within the context of the problem being solved by the application. (Note that for a complex application there may be multiple domain experts, each providing expertise for only a portion of the application.)

I believe this is important because the gross structure of an application should parallel structure in the problem domain. The reason is that change is inevitable and customers don’t like it any more than software developers. When changes occur in the problem space customers will instinctively adapt to those changes along a path the represents minimum disruption to their existing structures. If the overall application structure parallels the customer’s problem space structures, then the software disruption should be minimized as well.

The second most important characteristic of subject matter is cohesiveness. A subsystem encapsulates a particular collection of knowledge and behavior. The elements of that collection should be intimately related within the context of the problem being solved. In general I would prefer to err on the side of identifying too many subsystems rather than too few when trying to identify the boundaries of cohesiveness. The more narrowly that subject matter is defined, the easier it becomes to provide generic interfaces that will be invariant as the details of the implementation change. As a fairly general rule it is easier to combine trivial subsystems than it is to subdivide complex ones once there is substantial design investment.

To enable good encapsulation the boundaries of the subject matter should be well defined. When you ask the domain expert if a particular responsibility belongs to the subject matter the ideal response should be a quizzical look and, "Are you nuts? No way, Huck! That clearly goes over in XYZ." Of course real life is rarely that clear cut but, like zero defects, it is the direction in which one should be running. In the introductory post I mentioned that half a day for identifying subsystems was nowhere near enough. The time one spends is not so much around identifying subsystems as deciding exactly what the subject matter includes. That definition is essential to defining precise interfaces.

The notions of indivisibility, cohesion, and levels of abstraction are closely related and they depend a great deal on context. For example, at some level of abstraction the concept of a ‘software test’ is indivisible and cohesive. At another, lower level of abstraction concepts like ‘unit test’ and ‘system test’ might each be indivisible and cohesive though they are both software tests. In some contexts ‘unit test’ and ‘system test’ might be specializations of ‘software test’, where certain knowledge or behavior is shared between ‘unit test’ and ‘system test’. In others ‘unit test’ and ‘system test’ might be quite independent but it is convenient to gather them into a ‘software test’ subsystem that presents a common interface to the rest of the application. Whether there is even a need to model ‘software test’ or ‘unit test’ and ‘system test’ as subsystems will depend upon the context of the problem being solved.

Indivisibility, cohesion and levels of abstraction clearly interact in complex ways within a given problem context. Understanding such interactions is important to the overall structure of the application because they provide a basis for partitioning along modular lines. The Package Diagram and related descriptions is an ideal means for documenting that understanding.

As an example, I have seen ATM controller software models in textbooks that had Account classes in them. This has some serious practical implications for the implementation. If both the ATM software and the bank accounting software share knowledge of an Account class, then instances of Account will have to be shared across the network port. This necessarily introduces substantial overhead and complexity to the network connection because one needs something like CORBA or DCOM to handle the object references rather than a simple data transaction interface.

Perhaps more importantly, having the ATM software updating Account instances introduces a whole new suite of data and referential integrity issues. Some of those issues will probably have to be addressed in the ATM software itself. In effect the ATM software will have to understand something about the persistence mechanism of the bank’s accounting software. All these potential maintenance headaches and complexity problems are directly attributable to a high degree of coupling through sharing an implementation abstraction.

The problem here is that at least three different subject matters are evident when one looks closely. The bank itself deals with banking concerns where entities like Account and Customer were paramount. When it processes a message from the ATM Controller, it infers very specific semantics for the information.
The communications were handled by networking software whose subject matter was simply passing messages without the slightest interest in their content. The netwroking software would be deeply concerned with the relevant low-level communication protocols but really doesn't care about the semantics of the data transported. Similarly, the ATM Controler software’s subject matter is concerned with communicating with a customer through a hardware interface (the ATM machine). Those communications were limited to moving data between the ATM hardware and a network port. Unlike the network software it does care slightly about content (e.g., message types and numbers vs. strings) but it needs none of the specific semantic knowledge of what the data represented that the bank’s account software required.

So basically we have (at least!) three distinct subject matters that participate in a customer transaction: accounting, networking, and hardware control. Each has a unique view of the world, a unique mission in life, and -- most important of all -- a very limited set of concerns. By separating those views, missions, and concerns we are able to provide better focus in the software that makes it simpler to implement each subject matter. [That focus also helps to identify invariants, which has a whole other suite of benefits. But that's a story for another blob category...]

Another interesting example of subject matters is related to GUI based applications. An unfortunately common design mistake is to treat the message loop for GUI messages as the central driver for the application (i.e., as a ‘spider web’ subsystem with dependencies radiating out to most of the other subsystems in the application). This causes the software structure to be built up around the GUI paradigm rather than the solution to the user’s problem. It also tends to result in things like window handles being blithely passed throughout the application, making it a nightmare to port.

Recognizing that providing communications with the user is simply a distinct subject matter easily averts this sort of mistake. The GUI hgas a limited mission, talking to the user, and a very well defined paradigm, Window/Control. Identifying the GUI as a separate subject matter allows it to be encapsulated. The information and requests the user provides and the responses of the application merely define a generic interface to a subsystem that happens to contain the message loop. In fact, to solve the user’s problem the application should not care whether the user communicates via a GUI, a command line, or by optical recognition of smoke signals. An obvious example of the benefit of this encapsulation is that porting can be achieved by simply replacing a single subsystem.

More importantly, identifying the GUI as a separate subject matter allows it to be viewed properly as a service to the rest of the application whose requirements are defined by the application’s needs for solving the user’s problem. This allows the application’s main structure to reflect that of the problem in hand rather than the computing space (i.e., display mechanisms).

Blog root page
previous post in category
next post in category

June 07, 2004

3.0 Level of Abstraction

Blog root page
previous post in category
next post in category

Traditional application layering has its roots in real-time/embedded applications, particularly operating systems. Such layering isolated detailed functionality (e.g., the processing of network protocols) under an interface that provided generic access for multiple applications. Structured Programming added layers to the general application development toolkit. At the same time the notion of a subject matter was developed so that a layer represented detailed functionality that was cohesive.

Because object technology is largely about abstractions, it is not surprising that the view of layering has further evolved in that context. Thus the notion of levels of abstraction is simply a more sophisticated view of layering that accounts for the key abstraction characteristics of knowledge and behavior. Paying attention to levels of abstraction is important to the consistency within OO applications.

There are two views of the notion of level of abstraction. One is the common one that correlates strongly with the amount of detail. When we think fo something as abstract, we think of it as generic and unspecialized. Thus the notion of Vehicle is much more general than the notion of Honda Accord. The second view of abstraction is the notion of essence or intrinsic qualities that are unique to an entity or a grtoup of similar entities.

Both views come into play when forming a Package Diagram. We organize the diagram vertically in order of decreasing abstraction in the layers or subsystems. This reflects the association of the amount of detail with abstraction. This is similar to traditional techniques where complexity is managed by decomposing high-level, overall, general responsibilities into low-level, narrowly defined, specific responsibilities. If only behavior is considered when doing this, we have classice functional decomposition.

In OO development, though, the decomposition is based upon much more than just behavior. We abstract entities in terms of both knowledge and behavior in equal measure. In addition, we use abstraction to extract the essence of the entity that is relevant to the problem. It is this second view that causes us to diverge from the simple one-dimensional model of layering. Thus it is possible to have subject matters that reflect the same level of detail but are about quite different things.

This second view is closely allied to subject matters because subject matters are necessarily about different things. However, level of abstraction in this sense is a bit more subtle. It is about the sorts of things that a subject matter knows or does. Often this is reflected in abstracting a paradigm from the subject matter. For example, in a GUI the dominant paradigm is Window and Control. If we wish to represent a problem space entity for display in a GUI we should abstract it in terms of that paradigm. In contrast, the accounting view of the world is dominated by Account and Transaction. So an Account entity with a Balance property in the accounting subject matter might be represented as a Widnow instance for display that had an associated Control instance for the balance property.

One simple example of this sort of thinking is the software for a bank’s ATM machines. The software to actually control the ATM machine itself is quite different than the software used to manage accounts in the bank or the network software used to transmit messages between the ATM and the bank. In addition the networking software that allows them to communicate is quite different than both. Traditional layering would yield a diagram such as:

[ATM Controller] [Accounting] | | | | [.................. Network.................]

where the ATM software and the bank’s accounting software were connected through a layer of network software.

This makes it clear that the ATM software and the bank’s accounting software have quite different subject matters but it doesn’t capture the idea that the ATM software is a service of the bank’s accounting software. In practice the bank accounting software defines the information it needs from customers (account number, etc.) and what customer requests (withdrawal, etc.) it will honor. In that context the ATM is nothing but a service (i.e., simply an interface to the customer). In UML we could capture this in a subsystem Diagram such as:

[ATM Controller] <----------- [Accounting] | | | | | | +----------> [Network] <-----+

where the arrows represent relationships between a client (stem) and a service (arrowhead). The bracketed entities are now Subsystrems in the sense of a UML Package Diagram.

While syntactically correct the figure does not capture a very important aspect of application structure. It doesn’t capture the fact that the ATM software has no business dealing with an Account class of any flavor. The ATM lives at a very different level of abstraction where it simply moves data back and forth between the hardware and the network port while reacting to message types. Unlike the bank’s accounting software, it does not have to have any understanding of the semantics of the data. It should not even know that the bank’s accounting software exists on the other side of the network port.

So the ATM Controller software deals with the processing at a much more detailed level than the accounting software. In addition, it operates with an entirely different paradigm than the other two. The bank's accounting software deals with the high-level semantics of accounts, customers, and transcations. The ATM Controller deals primarily with hardware control and moving message back and forth between the network port and the ATM hardware. Because it constructs the messages it sends, it operates at a semantically more sophisticated level than the network software whose operations are primitive by comparision.

The network software also deals with message and hardware, but in a very different way. The ATM Controller is dominated by the card reader, cash dispenser, display, keyboard, and envelope processor hardware in its local environment. The network software deals routers, domain servers, ports, and distributed protocols. More important, it processes messages on a pass-through basis with no concern at all about content while the ATM Controller's main function is to encode and decode messages. So each type of software deals with a different paradigm and thinks (abstracts) about the world differently.

One can correct this by providing a vertical ordering of the levels of abstraction in the subsystem Diagram, as in:

+------------ [Accounting] | | | | V | [ATM Controller] | | | | V +-------------> [Network]

Now the diagram accurately reflects the levels of abstraction. This will become an important visual cue when it is time to allocate requiremetns to subsystems.

Caution: this is a superficial solution with limited practical value because it does not describe how the levels of abstraction are different. That needs to be done in supplementary model documentation that describes the various subsystems. It is extremely important to the overall application’s internal consistency to ensure that everyone on the development team understands the level of abstraction of each subsystem.

Blog root page
previous post in category
next post in category

June 06, 2004

4.0 Client/Service relationships

Blog root page
previous post in category
next post in category

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...

Blog root page
previous post in category
next post in category

June 05, 2004

5.0 Subsystem Interfaces

Blog root page
previous post in category
next post in category

Subsystem interfaces are important to the overall structure of the application because, among other things, they address coupling among subsystems. One view of object technology is that it exists to formalize the management of coupling. The literature of OT has been greatly concerned with the quantity and direction of coupling. However, subsystem interfaces are more concerned with the degree or nature of coupling. There are basically five forms of coupling in communications between subsystems:

1. A message with no data other than a message type.
2. A message containing data passed by value.
3. A message with data passed by reference.
4. A message containing an object (e.g., a Java applet).
5. A message with an object passed by reference.

All five forms share a potential algorithmic problem in the sequence of the delivery of the message with respect to the overall application processing. For the first this is the only problem while the other forms all introduce additional potential problems with data integrity. For the second the possible data integrity problems are limited to the relatively rare situation where multiple messages bear dependent data. So the first two forms are relatively benign because there are few ways to engage in foot-shooting.

The remaining forms introduce major potential for rather nasty problems. The third and fifth forms always require explicit coordination of updates and deletions between the subsystems tov ensure referential integrity. The fourth and fifth formas are dangerous because there is no limit on what a behavior can do (e.g., its interactions with the sender are unlimited). However, passing object references is, by far, the worst form of coupling because -- as if the other problems wren't bad enough! -- it also introduces the possibility of side effects in one subsystem’s implementation when the other subsystem invokes its behaviors.

While these problems are typically manifested by run-time errors, the real coupling problem lies in the side effects of application maintenance. For example, passing a reference to an object in one module to another module essentially opens a window into the implementation of the first module. Thus the same maintainability benefits of encapsulation achieved by classes should apply to larger application units. The degree of coupling between subsystems will directly reflect their level of encapsulation and the overall maintainability of the application.

That's why good application partitioning demands that one employ "firewall" interfaces between subsystems. Therefore good application partitioning demands that all subsystem interfaces be built around only the first two sorts of messages. That is, subsystem interfaces should be pure messages (e.g., events) where the message has a message identifier and an optional data packet containing only by-value data elements. The key idea here is that subsystems can be viewed as classes on steroids and all the same OO principles like encapsulation and implementation hiding apply to them as apply to classes.

I'll take that further: subsystem interfaces should be even more stringent about such principles that class collaborations because one can better afford the overhead of providing good decoupling to the subsystem level. There is no free lunch and good decoupling for maintainability has a preformance price. [John Lakos (in "Large Scale C++ Software Design") measured three orders of magnitude performance degradation when certain forms of dependency management were employed.] Sometimes that price is prohibitive at the class level, but it is rarely a major problem at the subsystem level.

Using pure data transfer interfaces entails some extra work to encode and decode data packets on each side of the interface. However, a few attribute assignments is generally a very small price to pay compared to the Pandora's Box of potential problems one introduces for long-term maintainability when one exposes the implementation of one subsystem to another.

[In fact, I can argue that for distributed systems limiting interfaces to pure data transfer can substantially improve performance over distributed boundaries. Interoperability infrastructures make life a whole lot easier for developers but one can pay an enormous price by allowing them to be a bit too convenient.]

When designing subsystem interfaces there are a few important considerations. One is that the interface should be designed around the client's needs rather than the contained subject matter implementation. This is essential to providing large scale reuse of service subsystems but it tends to enhance maintainability even without reuse. In particular, the interface should be designed around generic access to the subject matter as a whole.

That segues into the second considertaion. The interface to a subsystem should focus on the invariants of the subject matter. This has obvious benefits for large scale reuse but it also enhances long-term maintainability through providing a stable interface over time. If only semantic invariants are captured in the interface, then the itnerface should not change as the detailed implementation changes. (There is a separate category in the blog for invariants.)

Fortunately both these considerations are relatively easy to deal with if one sticks to a pure data transfer interface. That's because the behavior that responds to the interface message is not defined in the interface; only the mesage type and data is defined. This becomes especially easy to do if one makes incoming interface messages announcements of something the client has done. This completely removes the semantics of any responding behavior from the interface. The response, quite properly, becomes the sole responsibility of the subject matter semantics.

Note that at the OOPL level one cannot separate message and method in an object interface (unless one employs state machines and an event queue manager to deal with behavior). That's because the OOPLs are all 3GLs that employ procedural message passing and type systems. The message becomes the method signature and we name methods by what they do.

However, a subsystem interface is typically a Facade design pattern or event-based so one does have the opportunity for separating the message from the method. Thus one can name interface methods or events in terms of the client context (i.e., what happened in the client that caused the message to be triggered). The subsystrem interface can then dispatch to the subsystem object method that provides the approriate response. If you practice this announcement vs. imperative teechnique in subsystem interfaces you will be astonished with how easy it is to construct interfaces with long-term statbility once you get the hang of it.

Blog root page
previous post in category
next post in category

June 04, 2004

6.0 How to Identify Subsystems

Blog root page
previous post in category
next post in category

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.

Blog root page
previous post in category
next post in category

June 02, 2004

7. 0 Subsystem Interface Model

Blog root page
previous post in category

There is a quite general interface model that is commonly used for subsystems. This model addresses a common problem found in large scale reuse. Because there can be multiple syntaxes for accessing any complex semantics, it is always possible that a new client in a reuse situation will want to access the semantics using a different syntax than the service actually provides. In that case context-specific "glue" code is needed to resolve the syntactic mismatches. (In the context of application partitioning, access syntax is defined as the interface.)

The basic model is:

+-------------------+ +-------------------+ | Subsystem A | | Subsystem B | | +------+ +------+ | | | Ain |<-----------------| Bout | | | +------+ +------+ | | | | | | +------+ +------+ | | | Aout |----------------->| Bin | | | +------+ +------+ | | | | | +-------------------+ +-------------------+

The basic idea here if that subsystem interfaces are actually two-way and they provide both an input interface and an output interface. The input interface, Xin, is a traditional interface that accepts messages and dispatches them to the appropriate objects within the subsystem. The output interface, Xout, does the same thing in reverse; objects within the subsystem that wish to send messages to the outside world address them to the Xout interface. Both Xin and Xout are typically implemented as Facade design patterns. That is, the interface is a class with its own implementation that provides dispatching services to other classes.

The Xout interface dispatches to the other subsystems Xin interface. If any "glue" code is required to resolve syntactic mismatches, it is provided in the Xout implementation. This isolates the "glue" code to the implementation of the Xout Facade class. This is important because the objects that implement the subsystem "see" only the Xout interface, which is always the same, regardless of reuse context.

The isolation of the "glue" code completely decouples the subsystem implementation from the outside context. This is also very practical because it is consistent with modern development environments. For example, the Xout interface class can be implemented separately, provided in DLL form, and simply linked into the application. It also allows things like networking protocols to be isolated when a subsystem is deployed in a distributed environment.

However, the main benefit lies in the decoupling of the subsystem implementations. The only part of the subsystem with any knowledge that the other subsystem even exists is the implementation of the Xout Facade. This essentially provides "firewall" interfaces within the application that modularize and isolate the application's partitions. Of course that only works well if the interfaces themselves are highly disciplined (e.g., event-based). But if the partitions and interfaces are defined according to the ideas expressed in the other posts in this category, then one has an excellent architectural framework that will be stable over time.

Blog root page
previous post in category