To Publish Or To Run, That Is The Question

By Peter van Dam (published in 1999)

Everybody who is getting involved in version 9 will sooner or later ask himself or herself the following question: what are good uses for the new publish/subscribe mechanism? I have met programmers who tried to replace virtually all run statements with publish statements where others are convinced that pub/sub is unreliable and therefore useless.

As usual, the truth is somewhere in between. There are particular situations in a program that lend themselves to pub/sub where others don't. I will try to illustrate that with the following metaphor.

Consider a radio station specializing in weather forecasts. . The people who work there are responsible for collecting and broadcasting the latest developments on the weather. They don't know how many listeners they have or who they are. As a matter of fact, there may be times that nobody has tuned to their frequency at all. It does not bother them in the least. They will still receive their salaries at the end of the month.

On the other hand consider the listeners. They don't always have their radios on and even if they do they might listen to a different station. However, if they need to know about the weather they'd better tune into the weather channel and make sure they receive the information they need. These listeners might be airline pilots or ship captains whose responsibilities include knowing about the weather conditions.

We can clearly identify two distinct parties with two distinct responsibilities: the responsibility of the publisher ends with the publication and the responsibility of the subscribers is to make sure they get the information.

There are many situations in programming that fit into this model. For example, consider a main window with zero, one or more child windows. On every iteration of the main window the children should be updated. The main window can accomplish this by publishing a window-update event. The main window does not know and does not even care if there are any running children and if so who they are. The only obligation of the main window is to publish the window-update event on every iteration. The child windows, however, are pretty useless if they are not running in sync with their parent. Therefore they will make sure they subscribe to the window-update event of the parent and process it properly.

Another example is the window-close event of the parent; children subscribe to this event it so they can close themselves whenever the parent windows is closed.

A third example helps to manage library handles. Before version 9, a common way to remember if a library was started already was by storing the handle in a global shared variable (and to be safe, the corresponding UNIQUE-ID as well). Julian Lyndon-Smith devised an elegant solution for this by means of the pub/sub mechanism. This works as following. When the library MyLib is started it subscribes to the event "getMyLibHandle" anywhere. This event maps to an internal procedure that simply returns the value of THIS-PROCEDURE.

subscribe to "getMyLibHandle" anywhere.

PROCEDURE getMyLibHandle:
define output parameter mylib# as handle no-undo.
assign mylib# = this-procedure.
END PROCEDURE.

Any procedure that needs to access the library contains the following piece of code (which in turn, of course, resides in another library):

define variable mylib# as handle no-undo.

publish "getMyLibHandle" (output mylib#).

if not valid-handle(mylib#) then
run mylib.p persistent set mylib#.

Look mama, no shared variables!

So when is pub/sub not appropriate?

All of the examples above share one important property: the business does not depend on whether or not the pub/sub mechanism works. If a child window is not synchronized with its parent that might be a nuisance but the business logic is not jeopardized. If the children don't close when the parent closes it looks a bit silly but it does not break any code. If a library does not respond for any reason a second copy will be started. That would occupy some extra memory but does not hurt in any other way.

However, if your business depends on it, don't use publish/subscribe. If you need to delete all orders and order lines when a customer is deleted you'd better not rely on publish/subscribe because your business will get into trouble if that is not handled properly for any reason. For example, you might want to block the delete if there are still open orders for the customer. This can easily be achieved with "run in" because an error will be raised if the run statement fails for any reason and the transaction will be undone. Contrary to this, with pub/sub the event will be lost if no one listens to it or the parameters of the subscriber do not match.

Smart Objects

The publish/subscribe mechanism in the v9 Smart Objects is implemented exactly according to the rules outlined above. That does not necessarily mean that PSC completely agrees with those rules. It merely illustrates one of the current limitations of pub/sub in Progress v9: the inability to cross session boundaries.

An event will only be published within the current Progress session and will not, for example, cross the boundary from Client session to AppServer session. Therefore, pub/sub can be used perfectly for communication within the User Interface (which always resides in the client session) but not for communication with the Business Logic part of the application, which potentially resides in a separate AppServer session.

Publish/subscribe and transactions

From the previous discussion the following rules-of-thumb can be drawn:

 

  • Inside a transaction "run" should be used
  • Outside a transaction pub/sub can do no harm

 

However, keep in mind that publish/subscribe was added to the language to facilitate something that is referred to as "loose coupling". This means that two objects can work together but not depend on each other. The opposite of loose coupling is "tight coupling" that we have always had in Progress. With tight coupling objects run methods in each other. Therefore they depend on each other because they can only operate together. As a result, modifications to one object will generally require modifications to the other object as well.

The advantage of loose coupling is that multiple objects can work together without knowing about each other. A software supplier might open up their proprietary product by publishing events such as "addItem" and "deleteItem". This enables their customers to add and maintain an ItemExtra table without any modification in the standard software.

However, this approach raises the question of when to publish these events: before, after or during the Item transaction?

Publishing before the Item transaction could result in a transaction committed in the ItemExtra table but not in the Item table (if that one subsequently fails).

Publishing after the Item transaction could, conversely, result in a transaction committed in the Item table but not in the ItemExtra table (if that one subsequently fails).

Publishing during the Item transaction will keep the database consistent but allows a third party to break the standard code. Apart from technical and functional issues I personally don't think the term "loose coupling" still applies in that case because a dependency is introduced. However, it may still be a very practical solution to what you need to achieve: extend existing code without modifying it.

The future

An answer to the transaction question might be found by widening our horizon a little bit.

First, compared to other programming environments there is a serious limitation in the current Progress pub/sub mechanism: it is synchronous. This means that the code after a publish statement gets executed after all the subscriber code is completed. This in turn means that the current pub/sub mechanism is limited to direct on-line communication.

Second, as already mentioned above, there is no guaranteed delivery mechanism. This also limits the use of pub/sub.

Other programming environments that are more targeted towards distributed processing offer one or both of these capabilities and that greatly increases the possible applications of the pub/sub mechanism. It is likely that PSC will add support for these mechanisms as well some time in the future.

If that were to be the case, I would argue that publishing after the transaction would be the way to go. With asynchronous pub/sub there is no way to find out when and where the subscribers' code will be executed and therefore we have to start thinking about transactions in a completely different way ("Fire and Forget"). A guaranteed delivery mechanism with proper exception handling would aid tremendously in this process.

Recently PSC announced SonicMQ, a new 100 % Java-based Java Messaging System that has the potential to quickly become an industry standard. It will be integrated into Apptivity first but it is to be expected that the Progress 4GL will follow. SonicMQ addresses all of the above issues and on top of that enables crossing the Progress boundary, leading us into the true world of distributed processing and Component Based Development (CBD).

If we look at pub/sub from this perspective we get an entirely different picture. The addition of pub/sub in version 9.0 is merely the start of yet another way of thinking and programming. So if you get a chance to develop in version 9 try to explore the current possibilities with the big picture in the back of your mind. It will once more change the way you build applications forever.