Re: What doesn't lend itself to OO?
From: H. S. Lahman (h.lahman_at_verizon.net)
Date: 07/27/04
- Next message: H. S. Lahman: "Re: new bie"
- Previous message: Steven Wurster: "Re: How C++ NULL pointers can violate LSP"
- In reply to: Mark Nicholls: "Re: What doesn't lend itself to OO?"
- Next in thread: Mark Nicholls: "Re: What doesn't lend itself to OO?"
- Reply: Mark Nicholls: "Re: What doesn't lend itself to OO?"
- Reply: Mark Nicholls: "Re: What doesn't lend itself to OO?"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Date: Tue, 27 Jul 2004 17:35:18 GMT
Responding to Nicholls...
> To be honest I find OO literature and software engineers in general
> very week on system/subsystem design. Booch barely mentions subsystem
> decomposition over and above a diagramming technique, Rumbaugh not
> much better, Wirf Brock gives half a page. I suspect this is because
> OO can say little helpful about it over and above tradition SD.
You have to go to the translation methodologies for that (Shlaer-Mellor,
ROOM, ROPES, MBSE, etc.). They all grew up in R-T/E where
modularization with disciplined interfaces is a matter of survival. So
they provide a lot of formalism around application partitioning that
results in long-term architectural stability and allows subsystems to be
developed semi-autonomously with lots of large-scale reuse.
Alas, even there it is mostly in the vendor training classes. [In the
book I am writing I figure it is important enough that I devoted six
chapters to it. Alas, progress there is rather rather glacial. B-)
There is a thumbnail overview on my blog.]
>
> I posted a message here several weeks ago about the nature of
> decomposing systems especially 'enterprises' into seperate subsystems,
> I am trapped by BPR consultants on one side doing top down analysis
> who see a holistic lump of a system and software engineers on the
> other who also see the ideal as one super system. I suspect because of
> a history of being a R/T engineer, the idea of breaking a system into
> cohesive subsystems before designing those systems is natural.
Right, application modularization and layering were developed in R-T/E,
dating back to OSes like Multics. While those notions migrated to other
arenas, it was pretty spotty. Layering made it for CRUD/USER processing
because it was a good basis for automation. Modularization made it in
the procedural world but got lost when migrating to OO because class
encapsulation stole the show. The whole idea that a subsystem is just
an object on steroids where all the same notions of encapsulation,
implementation hiding, decoupling interfaces, etc., etc. apply just got
lost in the shuffle.
>>Just out of curiosity, though, why shouldn't the clock service be
>>stateful? It isn't really a resource issue because there only needs to
>>be one instance.
>
>
> I think we need to be clear about what 'stateless' programming means
> in this context, I don't particularly think it means state rather than
> specific identity.
>
> If the clock service has identity then the client looks like...
>
> // create a specific instance of the clock service on the server
> CGMTClockService clock = new CGMTClockService();
> // invoke GetTime on that specific object
> Console.WriteLine(clock.GetTime());
The first line exists in the server (presumably in the setup code).
Where does the second line live?
If, as I suspect, it lives in the client, then I don't like
"clock.GetTime". The client should be making a generic request for the
time from the server with no knowledge that the server implements that
request by instantiating a clock object.
<Digression>
A good model for subsystem interfaces is that they are two-way. The
external interface is the traditional input interface whose
implementation dispatches external requests to appropriate objects that
implement the subsystem. The internal interface is the one that the
objects implementing the subsystem talk to when they want to send a
message to the outside world. That interface's implementation provides
any glue code to resolve syntactic mismatches between the interface the
subsystem expects and the interface the other subsystem actually
provides. This addresses a common problem in reuse where different
contexts can demand difference access to the same service semantics. It
allows the glue to be isolated in a manner that is completely separate
from either subsystem implementation. Better yet, it does so in a very
convenient way so that it can easily be implemented (e.g., linking in a
Facade class for the glue code).
I mention this because my assertion above needs to be qualified. A
common technique at the OOA level is to provide surrogate objects in the
model that represent the subsystem's view of the interface. So one
might have a clock object in the model and one invokes clock.GetTime()
from it. However, that is the local semantic view of the interface and
has nothing to do with the semantics of the server that actually has a
clock implemented. In the OOD/P that clock object will go away and
clock.GetTime() will be replaced with interfaceFacade.GetTime() or
however the internal interface Facade class is defined.
</Digression>
>
> Now what happens on the server if the client crashes after the 1st
> line, there is an object with no client, and we need to create all
> sorts of dreadful strategies for making sure it is still needed -
> timouts, pinging etc.
>
> What happens if I want to create a farm of two servers if clock is
> created on server A, the load balancer needs to know this else it may
> send the request to server B and this specific clock doesn't exist
> there.
I agree, we have different views of statelessness. B-) I see this as
an orthogonal problem related to registration, protocols, and
implementing a bunch of interoperability requirements.
I see stateless objects as a mechanism to deal with limited resources.
If objects have state variables, each instance needs to be created to
persist those variables. If there are a lot of them (e.g., many
shopping carts active at a POS site or, worse, many cached web pages),
one will run out of memory and begin page faulting and the DB is enough
of a bottleneck. One also has the problem of dealing with discarded
objects (i.e., the client went away leaving stuff behind), otherwise
physical disk space becomes a limitation.
So the answer is to implement objects on the server that don't have
state variables. All the necessary state data to process is provided
with the request or accessed as needed from the DB. More important,
when the particular request processing has completed, the state data
disappears. Of course once the objects have no state variables, there
is really no reason for multiple instances; the instance identity can
also be supplied as input data. So one abstracts single instances of
generic objects. IOW, stateless objects are just a variation on the
ancient idea of reentrant device drivers.
>
> Note clock really has no 'state' except it's class and this is
> important because
> CGMTClockService does something different to CCETClockService.
>
> So don't do it, just do the equivalent of
>
> Console.WriteLine(CClockService.GetTime(GMT));
We are back in agreement here, except that I interpret CClockService as
the server interface Facade (or a surrogate for it that knows how to
reach it over the network).
>
> there is only one request so it can be farmed, load balanced and any
> state data required is created and released in a single request.
>
> a functional API.
Right. Subsystem interfaces tend to be indistinguishable from
functional APIs when developing synchronously. [In the translation
world our interfaces are always event-based so there is technically only
one interface function, acceptEvent(event). So it tends to be less
obvious. But the set of events accepted by the subsystem interface can
be regarded as a set of functional requests.]
>>>If possible on the client BUT I accept there are a whole raft of
>>>scenarios where this is unreasonable and unwanted i.e. the raison
>>>d'etre (!) for the service is to supply some sort of persistence.
>>
>>There's no free lunch; wherever state is maintained persistently there
>>is a downside. If it is in the server, then one has resource issues in
>>a multi-client environment. If it is in the DB, then one has the
>>performance hit of additional physical I/O access and one increases the
>>likelihood of needing deadlock resolution. If it is in the client, then
>>the client needs to know a lot about the persistence limitations and
>>there will be encoding/decoding overhead into generic formats like XML.
>> (Not to mention that most of the client state will originally come
>>from the DB through the server anyway; all one is really doing is
>>optimizing the access.)
>>
>
>
> If no persistence is required, then there would seem to be no need for
> the extra headache. Keep the state on the client and make the
> interface a completely stateless one, all state needs to be passed in
> to get a given result.
You've only removed the server headache. In doing so you have given the
client a brand new set of headaches. Client requests that were
conceptually separate before because of inherent sequencing in the
problem solution are no longer independent and each request may have to
be aware of others. Basically we have:
Request A provides state data {S1, S2, S3}
Request B provides state data {S4} but there server needs {S2, S3} as
well to process {S4}.
If B always follows A in the solution, there no reason for whoever
generates B to worry about any data other than {S4} -- providing the
server persists {S2, S3}. Otherwise, whoever generates B must be aware
that the {S2, S3} data from A is needed and include it.
>
>
>>Essentially one has made every client responsible for also managing and
>>optimizing DB access in addition to solving the customer's problem.
>>That duplication fine if all the customer is doing is ad hoc reporting
>>and data entry because it can be largely reused as in RAD IDEs. But
>>when the applications are also solving significant problems, one has to
>>touch /all/ those solutions when something changes in DB-land. That's a
>>maintainability problem, which must be important because one is doing OO
>>in the first place.
>>
>
>
> If you need to put stuff in the DB, I would put it in and keep that
> state in the DB server.
Then there will be a whole lot more physical I/O on the DB that makes
the critical path performance bottleneck even worse.
>>>I don't know, you wouldn't like my code - sometimes I don't - some of
>>>it has to do with working in an inherently multithreaded world where
>>>changing state is a nightmare...I usually just push everything into a
>>>single threaded queue.
>>
>>Use state machines. B-) If the only problem is data consistency during
>>concurrent execution (as opposed to hard R-T constraints), then all one
>>needs to do is provide multiple instances of the event queue manager.
>>The event doesn't get on the queue until the data is consistent,
>>regardless of whether there are one or many queues, so data consistency
>>comes for free in just getting the asynchronous processing to work for
>>the object state machine interactions.
>
>
> I am currently either creating objects with readonly state, and/or
> creating a single threaded queue and/or passing data by value.
>
>
>>One can then allocate object state machines to event queue managers for
>>concurrency and put each event queue manager in its own thread. Simple
>>blocking constraints can even be supported via semaphores in the event
>>queue managers rather than invoking the <expensive> thread pausing
>>mechanisms.
>>
>
>
> hmmm, can't quite see this.
Which part, blocking for data integrity or queue manager per thread for
concurrency? (The answer might be lengthy to either, so I don't want to
answer until I know which one.)
>
>
>>[If blocking is necessary, it has to be managed somehow. Doing it by
>>pausing state machines, though, is more aspect-like (i.e., the code
>>looks the same everywhere) and is easier to manage because the scope is
>>at the state action (method) level and one can count on the FSM rules to
>>ensure that scope.]
>>
>
>
> interesting but not with it.
Do you mean I didn't explain it well or that it is irrelevant to your
applications?
>>We seem to have different views of COM. B-) To me the primary
>>mechanism of COM composition is mixin-like inheritance. In one wants to
>>add a streaming facility to the component in hand, it is inherited from
>>Streamable (or whatever it is called -- it's been a long time since I
>>was exposed to COM and I wasn't paying much attention even then).
>
>
> How would you do it in C++. If you would use MI in C++ then you can MI
> the interface and message forward (weak delegation), if you would do
> it another way, and I expect you would, the COM would allow you to do
> that.
The way I see COM is that it is equivalent to C++ MI of implementations.
I am not a fan of MI on cohesion grounds (Printable Sony Walkmans).
Nor am I a fan of implementation inheritance; it seemed like a good idea
at the time but opened a Pandora's Box of foot-shooting.
>
> You can't do implementation inheritance - so delegate.
This would be my choice for a variety of reasons beyond MI and
implementation inheritance. Among other things, subclassing relations
are static so they can't be modified without touching the code.
Delegation, OTOH, is very useful for parametric polymorphism. That
allows one to encode invariant behaviors while parameterizing detailed
differences in external data. Then dynamic relationship instantiation
can be used to change the solution without touching the code.
>>>>In contrast, individual applications are abstracted to a very particular
>>>>problem in hand. So if one routinely applies the COM composition
>>>>paradigm one ends up bypassing OO maintainability in favor of the view
>>>>that it is easier to cobble together a new component for a specific
>>>>change than to worry about modifying the existing application to be in
>>>>synch with the problem space. The result is enormous code bloat.
>>>>Worse, it leads to architectural drift in the applications away form the
>>>>problem space leading to a maintainability nightmare when the
>>>>application matures.
>>>>
>>>
>>>
>>>Bad implementation does not make a bad tool. COM may encourage this
>>>because it encourages composition over inheritance, there is less
>>>coupling within an application and so it becomes much easier to coble
>>>a bodge together than have to reengineer the system due to a new
>>>requirement.
>>
>>And when one has collected together a flock of
>>almost-but-not-quite-the-same widgets and the requirements change, how
>>does one decide which widgets need to be modified or replaced? One can
>>guess that only a specific context is affected and modify that. Then
>>one must have a whole lot of faith in one's test suite.
>
>
> Again to "... collect together a flock of
> almost-but-not-quite-the-same widgets", would not be good practice, to
> have them available is nice and better than not having them available.
You are losing me here. The only way to avoid that is by modifying the
existing widget for the new requirement rather then composing a new,
very similar COM widget. But then one is back doing what you argue COM
avoids -- re-engineering the application. That's because the only way
one widget can serve all contexts is by ensuring all contexts are
consistent with the requirements change vis a vis side effects.
>
> I would not subscribe to the view that a tool should be complex and
> difficult to understand and use, and not have as many 3rd party
> components available as possible - if the cost of this is that less
> experienced people can build bad software then so be it, I don't blame
> the tool for that, it is an unfortunate consequence of it's success.
>
>
>>Alternatively, one can go examine every context where each flock member
>>is used to see which widgets are affected by the requirements change.
>>That puts one knee deep in Spaghetti Code. B-).
>
>
> Hopefully, being a wise and sensible developer you would choose
> components from reliable sources.
Again we seem to be talking past one another. I am talking about the
COM objects that the developer cobbles together as building blocks to
form the application, not the fundamental library entities. Those
objects are constructed from other COM objects via composition in a
bottom-up fashion.
>
>
>>But the real issue here is architectural drift. That increases the
>>chances that a relatively minor change in the problem space will trigger
>>a massive refactoring of the application. That sort of refactoring is
>>independent of whether COM techniques were used; is is a direct result
>>of the software structure not matching the problem space structure,
>>regardless of the construction techniques. My point is the the COM
>>composition paradigm strongly encourages architectural drift. IOW, it
>>may be somewhat more difficult now to implement an individual change
>>while preserving the problem space structure than doing so while
>>ignoring the problem space structure, but one will pay for it later.
>>
>
>
> Again, it encourages decoupled code, and this can be abused - but I
> would hope that you were a champion of high cohesion and low coupling,
> we can if you want return to the days of deep inheritance tree's where
> every small shift in requirements triggers the avalanche - I prefer
> that my architecture to be robust and flexible enough to absorb many
> shifts before it collapses around me.
Alas, I see COM objects developed for a specific application as the
antithesis of high cohesion! I agree COM reduces inheritance breadth
effectively to one level. But that first level is a killer because it
is arbitrarily broad due the MI of composition. More important, that MI
is completely arbitrary; there is no notion of the cohesion resulting
from problem space abstraction around unique and well-defined entities.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions -- Put MDA to Work
http://www.pathfindermda.com
(888)-OOA-PATH
- Next message: H. S. Lahman: "Re: new bie"
- Previous message: Steven Wurster: "Re: How C++ NULL pointers can violate LSP"
- In reply to: Mark Nicholls: "Re: What doesn't lend itself to OO?"
- Next in thread: Mark Nicholls: "Re: What doesn't lend itself to OO?"
- Reply: Mark Nicholls: "Re: What doesn't lend itself to OO?"
- Reply: Mark Nicholls: "Re: What doesn't lend itself to OO?"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|