Re: Client/Service relationships & Flow of Requirements.
- From: John Carter <john.carter@xxxxxxxxxx>
- Date: Tue, 30 Jan 2007 16:00:16 +1300
On Mon, 29 Jan 2007 20:29:33 +0000, H. S. Lahman wrote:
Responding to Carter...
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.
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.
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.
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?
If any of these are owned by the client, then such message (or function
call) from the service to the client inflexibly and unreusably creates a
cyclic dependency that couples the service to that particular client. Why?
Since any other client must have explicitly the same message ID's (name
clash!) and same structures (name clash or non-DRY maintainance horror) as
the first.
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."
Where the Wheels fall off and programmer after programmer delivers deeply
subtle thread races if the both the service and the client are attempting
to send structured events to each other, which clicks the other through
asynchronously through a complex state machine.
The key to spotting thread races is to remember complete transactions must
be serialized, not accesses. eg. Anything that is doing
if (A)
doB
else
doC
It doesn't help ever to serialize access to A, doB and doC individually
whether by mutexes or making everything message passing.
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.
ie. The entire transaction must be serialized. ie. No message passing
interface is going to help you if the events are walking a state machine
through a transaction. You still can get thread races and invariant
smashes.
ie. Making a design pure message passing masks some thread races, but is
really an entirely orthogonal notion.
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".
ie. Explicitly, and in words of few syllables...
If your service must send information in a form or at a time or in a
sequence that requires explicit knowledge of the client, even as you have
clearly said is OK, using an asynchronous structured message, you are
heading into the Land of Trouble.
BTW, I am uncomfortable with the use of 'call graph' in the first
sentence of (3). First, the idea of synchronous calls to an API is not
a real good way to think about message-based communications in an OO
context. One is better off thinking in terms of asynchronous
announcement messages for inter-subsystem communications. If the
interface is designed that way at OOA/D time, it can always be
implemented synchronously during OOP. But if it is designed around
synchronous calls, it may not be possible to implement it in a truly
asynchronous and/or concurrent environment.
Point taken. Although currently I'm working at the point where the design
is long done, the code has been written, it is now a matter of reverse
engineering the true architecture as built rather than as designed, and
then refactoring and enhancing.
I will explore the rest of your extensive reply later.
Thank you for your time and attention.
John Carter Phone : (64)(3) 358 6639
Tait Electronics Fax : (64)(3) 359 4632
PO Box 1645 Christchurch Email : john.carter@xxxxxxxxxx
New Zealand
.
- Follow-Ups:
- Re: Client/Service relationships & Flow of Requirements.
- From: H. S. Lahman
- Re: Client/Service relationships & Flow of Requirements.
- References:
- Client/Service relationships & Flow of Requirements.
- From: John Carter
- Re: Client/Service relationships & Flow of Requirements.
- From: H. S. Lahman
- Client/Service relationships & Flow of Requirements.
- Prev by Date: Re: Critique of Robert C. Martin's "Agile Principles, Patterns, and Practices"
- Next by Date: Re: Wrapping SQL (Was: Critique of Robert C. Martin's...)
- Previous by thread: Re: Client/Service relationships & Flow of Requirements.
- Next by thread: Re: Client/Service relationships & Flow of Requirements.
- Index(es):
Relevant Pages
|