|
So continuing yesterday's entry about SOA for Swing and event buses, I thought
it could be a good idea to take the Swing Event Bus first. Just to explore how far
we can go with it.
I received some emails (and just one comment, thanks Tim!) on this so
I thought it could be a good idea to share my thoughts with you (well,
after all that's what a blog is about, right). But, more importantly,
I would like you so thare ideas with me. So let's participate, suggest
(and criticize, of course).
Our starting point
To clarify let's assume we have an application with just four major
macro-components:
- A Filesystem Viewer (FV). A component that visually shows the
contents of the file system. Say with a JTree or a JTreeTable.
This component is responsible or detecting selections of files,
double clicks and drag-and-drop operations.
- A Status bar (SB). Responsible for showing status messages.
- An editor window (ED). Responsible for opening text files.
- A Menu bar (MB). With the usual actions for opening files, etc.
Of course some of the items in the menus are enabled and disabled
depending, say, on the type of the file and the status (saved/unsaved)
of the editor.
Yesterday I described some of the horrors of the Observer pattern in
complex GUIs. This is, how the event-source to event-listener one-to-one
relationship is a nightmare for big applications. There's another
description of some of the problems at
Martin Fowler's site (see "Observer Gotchas"), just if you're interested. (By the way,
you may find the event
aggregator pattern of interest too).
So, to summarize, we want do build a Swing Event Bus that efficiently delivers
events generated by these macro-components. Either synchronously or asynchronously.
Our main objective is to make these macro-components as loosely coupled
as possible. Ideally each one of these four macro-components will know
nothing about the rest, although they will be orchestrated and
will jointly cooperate to build the user interface of a simple text editor.
(I would like to add a feature here too: the whole thing should be
extremely simple to use).
Design Idea I: Different bus lines?
Now let's see how other people are doing this. Let's see further
by standing on the shoulders of giants. Let's learn from what it's
already done.
The very first feature that comes to my mind is the need for
different bus lines. This is what JMS calls Topics and what Werx calls
Channels.
The benefit of this, of course, is that we don't have to deliver
the events to all bus listeners. We don't have to stop the bus
in all bus stops in the city. We reduce the number of bus stops to those
in the bus line. Events are delivered (through a channel/topic) to
a subset of listeners. And the traffic is more efficient, and our qualities of
service improve.
Design Idea II: Separating concerns
I like separation of
concerns. Each macro-component must be responsible for its own GUI. Events
internal to the macro-component should be handled internally, using
event sources and event listeners as usual.
The macro-components should communicate with the outside world by posting
specific events to a topic/channel in the bus. For instance, the FV could
generate FileSelectedEvents and FileDoubleClickedEvents to the "FV" topic
in the bus. And the editor could post EditorSaveEvent, or whatever other,
in the "editor" topic in the bus. Don't you think?
Design Idea III: Mediator Pattern?
As Tim suggested there should be a mediator somewhere to coordinate
events and macro-components. I think the Mediator Pattern fits nicely:
The Mediator pattern uses an object to coordinate state changes between other objects. Putting the logic in one object to manage state changes of other objects, instead of distributing the logic over the other objects, results in a more cohesive implementation of the logic and decreased coupling between the other objects
Do you think this is a good idea? I see the controller in the MVC here, right?
If so, shall this Mediator have
direct references to the macro-components? I mean, there're two ways for
the mediator to command something to the macro-components:
- A) Having references to macro-components. Say the mediator has
an instance of the "EditorPanel" and just invokes "Editorpanel.open( aFile )".
- B) On the other hand this mediator could just send an EditorOpenEvent
through the "edit" topic of the bus, so that the editor opens it.
I'm afraid that A) above is asking for trouble. I was wondering if that by
using "B)" above things should be easier to test functionally? I mean,
by storing (and then repeating in order) events received
in the bus we could make functional
tests easier to reproduce. Am I right here?
Design Idea III: Inversion of control
I received a pointer to Mike Aizatsky's article about Inversion
of Control with listeners. I'm not very good at IoC so I don't know if this
applies or not here. Does it? How?
Well, that's all today (I'm too tired). I'd like to hear from you!
Cheers,
Antonio
|
I think you're on the right track.
I wrote yesterday about separating the events into COmmandEvents and StateEvents. Now that you've clarified the needs of the app I can give you pertinent examples.
Let's use the nomenclature of a Topic for a category of events. You can create series of events for File's as such:
CommandEvents: SystemSelectFile, SystemEditFile, SystemCloseFile, SystemMoveFile, SystemSaveFile, etc...
StateEvents: SystemFileSelected, SystemFileEdited, SystemFileSaved, etc...
StateEvents can be used to ...(i.e)
SystemFileEdited
Update the status bar with name of the file etc... (SystemFileClosed as well)
SystemFileSaved
Update the State of the system to indicate that the file does not need to be saved?
For the file Topic your CommandEvents may need to be vetoable, in the case where a file is already being edited, you send SystemEditFile and prompt the user to save and they hit cancel. Your event would be vetoed and you would not continue on to Edit the file.
SystemEditFile could go something like this:
Fire the SystemEditFile (vetoable, System Topic) event from the FileViewer (after a sucessful SystemSelectFile event?) which will be handled by the SystemMediator. The SystemMediator checks to see if there is a file currently being edited (in some SystemContext perhaps?)
The SystemMediator Displays a SaveDialog (Y, N, Cancel)
If the user responds Y: fire a saveFile (File Topic) event to the editor fire a fileSaved (File Topic) event from the editor or mediator (your choice) If N: fire a closeFile (File Topic) event to the editor fire a fileClosed (File Topic) event from the editor or mediator (your choice)
If cancel: veto the SystemEditFile Event back to the FileViewer
Keep in mind that all CommandEvents can be vetoable even if they wouldn't normally veto (in case of an error). It's the mediator's responsibility to handle or not handle vetoes, as well as the series of Macro Component events that will be dispatched and their order... This would give you a pretty good implementation of design ideas 1, 2, and 3. I hope that helps, or at least makes sense... Tim
Enviado por Tim Osten en septiembre 22, 2005 a las 04:16 AM CEST #
One issue I am not sure about is how is this much different than Swing event thread other than its for pieces of an application instead of components that make up those pieces. I fully agree that each module should use the normal Swing event system for components/models/view as they do now. But it seems your analogy of a bus is identical except that it interconnects modules.
This leads into the next problem I have with this. How can one module not know/care about another and yet listen for specific events that are generated by a module it has no clue about? If module A defines and fires off A_EVENT_TYPE, then module B would have to know about A to accept A_EVENT_TYPE and do something with it. That makes B dependent on A. Personally, I hate dependencies. I would much rather avoid plugin dependencies, although our engine does support it. I would much rather have a "Hey, A, if you are there, I want to listen and handle for your A events. If you are not there, I'll either 'unload' cause I depend on you to function, or I'll sit and wait for other modules that I depend upon that ARE present". I like this type of dependency far better, as it's easier to manage in my opinion. This would almost work in our engine, except that we also want to support library plugins. For example, an Ant library wrapper so other plugins can make use of the ant.jar through depending on the plugin. We of course do our classloader magic. Every plugin has its own classloader, and we delegate to other plugins to look for classes that are depended upon and all that jazz. Works very well.
On another angle of dilema I am having with my framework, I want plugins to easily add menu items that are visually context sensitive. Meaning, if I add a navigator like plugin, I want to control buttons like copy, paste, cut, etc to enable/disable them when certain events become true. But this all has to be dynamic. If one plugin provides a GUI menu and toolbar system, ALL other plugins that want to add menus/toolbar items as well as have them update when certain events occur, must be aware of and depend on that plugin. This is fine, I believe. But it makes it less decoupled as you suggest with the event bus. It seems to me the best of all three worlds may work out in the end. Each plugin (in my case) that provides some sort of GUI (generally a JPanel or JComponent of some sort that is displayed) will use the normal Swing MVC. It may also hook into other plugins via the extension point/extension mechanism made so popular by Eclipse. Finally, using an event bus (possibly provided by a plugin), any plugin can listen to all or specific events sent down the event bus.
I do have one other issue with all this. The ability to veto events. Meaning, if plugin A, B and C all subscribe to the same Open File event. And lets say A and B both try to do something with the file that locks it, well, the 2nd one that tries to do something will end up with some exception, probably due to file locking. However, how do you handle the "who wins" situation? What decides that A's handling is more important than B's handling of the event, thus A should veto any other plugin from receiving the event?
I really wish I could finish my dang framework! I think a lot of Java Desktop developers would find it helpful in handling a lot of the grunt work, much like Eclipse RPC only with some differences as well.
Enviado por Kevin en septiembre 22, 2005 a las 05:50 AM CEST #
In response to your questions...
"One issue I am not sure about is how is this much different than Swing event thread other than its for pieces of an application instead of components that make up those pieces. I fully agree that each module should use the normal Swing event system for components/models/view as they do now. But it seems your analogy of a bus is identical except that it interconnects modules."
The method of event delivery can vary from implementation to implementation. Some apps may deliver events that come on the Event Dispatch Thread (EDT) others may have to convert the event to the EDT, or both (Consider firing SystemEditFile to an open application when a user double clicks on the file in Windows Explorer, the event could be passed to the existing app via TCP/IP). It is different because of that fact. Events may or may not be delivered on the event thread (i.e. server apps)
"How can one module not know/care about another and yet listen for specific events that are generated by a module it has no clue about? If module A defines and fires off A_EVENT_TYPE, then module B would have to know about A to accept A_EVENT_TYPE and do something with it. That makes B dependent on A."
In this case B would not necessarily need to know about A, only about the A_EVENT_TYPE. For example: A fires connecting, connected. B doesn't care what A is only that B needs to respond to A_EVENT_TYPE: connecting, connected. Those are state events, A is connecting, A is connected. CommandEvent listeners don't necessarily care who tells them what to do, only that they have to do it (like a good soldier!). “A” would only care that someone told him to connect...
"I do have one other issue with all this. The ability to veto events. Meaning, if plugin A, B and C all subscribe to the same Open File event. And lets say A and B both try to do something with the file that locks it, well, the 2nd one that tries to do something will end up with some exception, probably due to file locking. However, how do you handle the "who wins" situation? What decides that A's handling is more important than B's handling of the event, thus A should veto any other plugin from receiving the event?"
To answer this question I'll start by saying that CommandEvents, which OpenFile would be classified as, usually have only one listener (barring a logging listener or something of that nature), the component that will perform the CommandEvent. This is an issue of responsibility, i.e. one component has all the facilities with which to perform the command. Vetoing a CommandEvent is acceptable because the CommandEvent is either completed or not, in which case subsequent CommandEvents may not be fired (see my SystemEditFile example).
Having multiple listeners for a CommandEvent would mean that multiple components would have responsibility for performing the CommandEvent. This is counter to the idea of cohesion, where one component has definite responsibility for a task. Additionally, you would have to be careful in the ordering of the listeners to ensure that the components responded in the correct order to the CommandEvent (not always guaranteed). A more effective means of handling this problem is throught the use of a mediator, having the mediator handle the CommandEvent and using it to fire separate CommandEvents (in order) to the interested components. This would look like the System Mediator firing an OpenFileEvent to a FileMediator who fired distinct events to the components in order. It seems as if your plugins should be listening for the fileOpened event and then interacting with the file.
I hope that makes sense.
Tim
Enviado por Tim Osten en septiembre 22, 2005 a las 07:49 PM CEST #
I guess I never answered the math question earlier so I'll repost :) Nice discussion, and something I've not seen addressed much.
I've been using the event listener framework with much success on my recent project.
It basically allows the creation of your own events and event listeners. The events can have multiple types that get translated into calling different methods within the listener, much along the lines of awt events like MouseListener. This I've found is very useful for grouping events by application symantics. They also have a channel concept to group event dispatching. Allows my various macro components to be completly decouped. They just fire off events and/or listen to the events they are interested in.
The only tricky thing I've found is in designing the granularity of the events and event types. ie when to have a different event rather than another type of the same event.
JonnyEnviado por Jonny Wray en septiembre 22, 2005 a las 09:05 PM CEST #
Hi Antonio,
At http://superficial.sourceforge.net you will see that I have been working on very similar problems, and it is interesting that Superficial uses several of your 'design ideas' ie separation of concerns, mediator and inversion of control.
Perhaps the main difference is that Superficial abstracts away the GUI toolkit and event loop as the 'facet layer'.
Superficial is sufficiently complex that it needs a white paper, so I've attempted to make it clearer with a detailed worked example based on Martin Fowler's GUI patterns papers.
Perhaps Superficial includes ideas you can use.
Regards, David Wright
Enviado por David Wright en septiembre 30, 2005 a las 10:29 AM CEST #