Re: Client/Service relationships & Flow of Requirements.



Responding to Carter...

3) I regard the dependency tree as clearly involving the call graph in
two ways.
a) If the service explicitly names compile time symbols of the client,
do not be surprised if it becomes very difficult to ever reuse that
service. (The old cry, "Waah! We can't do software reuse, it's too
difficult. Yes, well. You didn't write reusable software so don't cry
now that you can't get software reuse.) If enough symbols are involved
change "difficult" to "impossible to reuse", even within the same
executable.

My response is: Don't Do That. B-) In fact, using the interface
approach I advocated on the blog for subsystems, it is impossible to get
into that situation because the interface will be pure message-based
(i.e., {message ID, <by-value data packet>}). As a result no element of
the implementation of one subsystem will be known to the implementation
of any other subsystem.


You keep saying that, other people keep saying that, but it still isn't
making sense to me.

The only thing that either the sender or receiver share is the structure of the message. Each can interpret the message ID and the data in the data packet in terms of their own implementation and those two semantic contexts can be quite different.

For example, consider a GUI subsystem sending a message to the main application. Let's say the user clicks the Save button on a form. The GUI generates a message with the values from the form. The two subsystems might interpret that message as:

Message GUI Main
message ID = 14 Save button clicked new data for objects
29 Form is Customer this is customer data
Frumpkin field 1 Customer::customer ID
12987452 field 2 Customer::account ID
....

As a result the GUI subsystem needs to know nothing about the semantics of the problem space or the specific objects in the main application that abstract it. Similarly, the main application needs to know nothing about forms or any of the other objects that abstract the display space.

Whether POD (plain old data) message passing or synchronous function
called based has nothing to do with the issue. As far as I'm concern the
most message passing allows is for some time slack, decoupling threads
from exact tooth-for-tooth intermeshing cog-like synchronous handling of
events. That's all, end of story.

That's true if one only thinks about the mechanism and not the semantics (at least in a synchronous environment). In 3GLs procedural message passing is effectively the only game in town, which is one reason why it is a bad idea to learn the OO paradigm by leaping directly into OOPL coding.

The trick is to avoid implementation dependencies by conceptually separating message and method. In the above example the message is simply an announcement of something that happened in the GUI subsystem. To send that message the GUI doesn't need to know who will receive it or what response, if any, there will be; from the GUI's perspective the main application doesn't even exist.

However, when one marries the message and the response that is not necessarily true. The entire procedural development paradigm was based upon messages being imperatives because the message is the signature of the response procedure. IOW, a message had an implied expectation of Do This; the caller had to know exactly what should be done next in the overall solution flow of control and that, when combined with functional decomposition, resulted in hierarchical implementation dependencies.

You are correct that I could construct a C program using synchronous calls that would have no hierarchical dependencies. (In fact, OOPLs like Eiffel do exactly that.) But to do so I need to conceptually separate message and response so that I can construct the caller without any expectation of a response. IOW, I methodologically need to design that procedural application as if all the messages were I'm Done rather than Do This.

If one uses the pure message interface above, then there isn't even a temptation to create hierarchical dependencies between the implementations. That's because the implementations have been logically decoupled by the encode/decode of the message on each side. So...

The issue is who owns the message ID? Who owns the the structures and
enums and types that make up the definition of the message packet? The
client? Or the Service?

The short answer is neither. The interface is a distinct design element provided by the developer that each side needs to understand. But each side interprets {message ID, <data packet>} in terms of its own unique context. Typically the interface messages are defined through shared header files, such as a Facade class header file.

[BTW, it is not uncommon to define messages externally in configuration files. That file might look rather similar to the table I provided above. Then each subsystem reads the file and uses it to guide encoding or decoding messages in its own terms. In that case one doesn't even need a mechanism like a shared message header file.]

Note that how one implements the messages doesn't really matter. At the OOP level the message mechanism could be a direct procedure call. However, it would be a procedure call to an API method, not to some intrinsic implementation function in the receiver. It is up to the API to provide the indirection into its subsystem implementation. That's why the GoF Facade pattern is so important to subsystem interfaces.

b) As an empirical observation, rather than a hard and fast rule. But
one with a high probability of being valid. Service code that makes
explicit synchronous function calls to the client often create thread
races and subtly smash invariants.

That is exactly why one prefers pure message interfaces for subsystems,
especially event-based interfaces.


Whilst that may help, in general there are still "gotchas" even in pure
message passing interfaces.
a) The first arises from the implicit mindset that says, "I'm thinking
about what I'm thinking about, unless my Client(boss) tells me to think
about something else. My own thoughts are inherently serialized and
sequential. No Issue. I expect my Client to behave in certain simple ways
gauranteed by my preconditions, which I can protect, queue, mutex into a
sane sequentiality. If now my worker services now start arbitrary complex
asynchronous conversations with me, I'm going to derail my train of
thought. I wish my workers would just do what I tell them, and deliver
single simple "done" or "failed" event on completion."

The rest of this message is mostly about concurrent implementations, which I think is largely OT. That's because concurrency is a pure OOP issue since it depends on tactical design issues around things like the available OS services available. In addition, the techniques for doing things like identifying threads and pausing them are well known. In the OOA/D one doesn't think concurrency at all. [As a translationist, I /only/ think about the OOA/D since I never have to mess with 3GL code. B-)]

However, one does employ a methodological approach to the OOA/D that is designed to ensure that implementation in synchronous, asynchronous and/or concurrent environments is greatly facilitated. If one employs good encapsulation, separation of message and method, and an asynchronous behavior communication model in the OOA/D, one will /always/ be able to implement in a concurrent environment during OOP. In addition, one will be guaranteed of minimum hassle in doing so.

However, to achieve that one must eliminate hierarchical dependencies between behavior implementations. That is exactly what things like object state machines and events do. They decouple implementations by separating message (I'm Done) from method (Do This) and the rules of FSMs require that behaviors be self-contained and encapsulated. So I would argue that the place to start is with the design of the subsystem where one avoids thoughts that are "inherently serialized and sequencial". B-)

<Hot Button>
A pet peeve of mine is the resistence one gets from IT people to using object state machines routinely. The fact is that with interoperability, networking, multi-tasking, distributed processing, mutli-user systems, etc., etc. IT is no longer one's grandfather's IT. It is much more like R-T/E. However, to avoid thinking about asynchronous processing, the industry is making major investments in layered model infrastructures that serialize asynchronous processing so CRUD/USER developers can think serially. The cost of that is outrageous overhead. Then people wonder why they need to upgrade their desktop to the equivalent of a 1984 mainframe just to run a spread*** that ran fine on a TRS-80 in 1984.
</Hot Button>

The asynchronous interactions between object state machines also require proper handshaking protocols in the communications so that the receiving object state machine will be in the proper state to process the event. Those protocols are very important to managing concurrency. Thus the protocol ensures the object is ready to execute a response, not when the sender and receiver are executing relative to one another. In addition, the arbitrary delay between message generation and response maps well into notions of parallel processing. IOW, one is not "locked into" hard-wired synchronous sequences of operations.

But for that to work one needs pure messages. The data packet can't contain object references because that expands the potential scope for blocking mechanisms in a concurrent context because the receiver might access behaviors in the referenced object. The data packet can't even contain references to data because that means that data may have changed since the context where the event was valid; any data in the message packet must represent the context that the message announces and that context may have changed be the time the message is consumed. Finally, the message cannot be an imperative (Do This NOW).

Thus pure messages in the OOA/D actually facilitate the implementation of concurrency during OOP by ensuring a narrow scope around individual behaviors and an independence of execution sequence. If one breaks those rules about message content and expectations, one introduces potential problems when trying to implement in a concurrent environment. That facilitation is not directly due to pure messages but do to the construction methodology that must be applied /around/ pure messages.

[An additional advantage of pure messages is application maintainability. That is because the message has no expectation of what the response will be (if any), which is what the GUI example above is about. But that maintainability is an orthogonal issue to concurrency.]


Otherwise after the "if (A)" the value of A can change under you and you
promptly do exactly the wrong thing.

To make it explicit...
Client sends message to service "Do this"

Service sends message asking client, "Ok, but what is the value of 'A'?"

Client sends message to service, the value of A is..."

In the time gap between sending and acting on the message, the value of
'A' may change.

Service receives now outdated message from client and....
Does exactly the wrong thing.

I think this is another orthogonal issue. But it brings up an interesting point about knowledge access in the OOA/D. This depends on whether one is talking about objects interacting within a subsystem or subsystems are interacting.

When objects interact within the OOA/D their behaviors are assumed to be asynchronous but knowledge access is assumed to be synchronous. That's because the behavior needs to access timely data and there could be an arbitrary delay between when an asynchronous message is generated and when the response is executed. Fortunately when one has good OO behavior encapsulation, that synchronous access of data is <relatively> easy to manage if the OOP implementation needs to be concurrent (i.e., blocking mechanisms on the object owning the data are fairly easy to implement and the scope is quite limited to the duration of the behavior needing the state data).

As a result, in a well-formed OO application the messages between objects within a subsystem very rarely have data packets. Typically the only time that occurs is when needs a "snapshot", such as data from multiple sensors that must be processed from the same time slice. In that case the requirements themselves determine the integrity of the snapshot. IOW, one has data in the message because the receiver needs to process data _from the context triggering the message rather than the current context_.

OTOH, at the subsystem level it is quite likely that a service's responsibility is to process data that the client provides. In that case the client is triggering the processing based on a particular context and the data that is processed should reflect that context rather than any that might prevail subsequently when the service actually processes the data. IOW, one commonly /wants/ a snapshot and the A in your example would usually be provided with the message.

This, in turn, segues to...


ie. The correct solution has nothing to do with message passing or not
message passing but "Tell, don't Ask" or the "Law of Demeter".

ie. Design the system so the correct service owns the needed state and all
you need say is "Do this, I am asking you since you are the owner of the
needed info, tell me when done".

I think this is yet another issue. State data goes in the subsystem where it logically belongs. Like allocating specific requirements, that is part of defining the nature of the subject matter.

As it happens, though, there is an interesting issue. Quite different functionality (subject matters) can operate on the same data so it is quite possible that two subsystems will abstract the same data. One then has two choices. One can have one subsystem "own" the data and it is passed around in message to other subsystems. This has potentially nasty data integrity problems because the data can be out of date by the time it eventually gets to a subsystem the processes it. (The "snapshot" above is ab exception.)

A more aesthetic issue is that objects marry knowledge and behavior responsibilities and they are always abstracted from identifiable problem space entities. If, say, a service subsystem abstracts only certain behaviors of a problem space entity but the knowledge is owned by a client subsystem, one is breaking the cohesion of the object. (Which one can argue is why one has the potential for data integrity issues.)

The other alternative is for each subsystem to maintain its own view of the relevant knowledge. That is, each subsystem abstracts the relevant problem space entities fully (for the subject matter needs). In that case the values of the shared data attributes need to be synchronized between the subsystems. However, that is a pure data integrity issue and there are pretty standard techniques that will ensure synchronization. (Since knowledge access is always synchronous in the OOA/D, the subsystems will be synchronized before additional asynchronous behavior messages from the subsystem making the data change are processed.) Again, this is just an OOP implementation issue; so long as one uses pure messages, it is fairly straight forward to manage.

Generally one prefers the second alternative because it is much easier to ensure data integrity in that situation using quite mechanical techniques (e.g., an object setter in one subsystem automatically generates a synchronous update of the other subsystem).

[Note that the situation of synchronizing state data in subsystems is very much like synchronizing data between an application and a database. The designer must know when they need to be synchronized. But the applciation designer doesn't need to understand things like two-phased commit that ensure data integrity on the DB side in an asynchronous environment. Similarly, the OOA/D developer doesn't need to know about how blocking is achieved for a distributed subsystem when a "synchronous" request for a data update is issued.]


*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@xxxxxxxxxxxxxxxxx for your copy.
Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



.


Quantcast