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.