Tuesday 28 May 2013

Part 4: Adding a broker

This is a repost of my old tutorial I wrote in late 2007. I'm presenting them as they where.

Simply looking at our current application we already see a problem prop up. I try to enter a new contact and I find out there is no contact type that suits my needs. I open up the window where I can add a contact type and I do this but when I return to my contact window, my new contact type is nowhere to be found.
A broker is one way to solve this. A broker is a mechanism where objects can subscribe to messages (events) that they would like to know about and to which they can send messages when a certain action has been performed.
Our contact maintenance window can tell the broker system it wants to be informed whenever the content of the contact type table changes, and our contact type maintenance window can tell the broker system when its changed it.
The contact maintenance window and the contact type maintenance window do not need to know anything about each other nor about their individual needs. The broker system needs to know nothing about how contact types are created or what the contact maintenance window requires to do when a contact type changes. All it needs to do is figure out who needs to be told about what.
Before we implement our broker system however, we're first going to readdress and old issue :) we're going to auto number the contact number. So how to implement this? Well very simple, we'll handle it at the source.
Our tContact table class handles the insert logic for our contact records so every time we need to insert a new record, we call its $insert, so every time we need a new contact number, we call our $insert, so why not put the logic to determine a new contact number in our $insert of our tContact table class.
First we change our $validateRecord and remove the check that validates the number, the user won't be filling in the contact number. Then we change our $insert

tContact.$insert
----
local variable tmpNewID kCharacter(25)
local variable tmpResult kBoolean

;  Get a counter value for our contact number

Calculate tmpNewID as $cinst.$getNextValue('ContactNumber')
If tmpNewID<0
  Quit method kFalse
End If

Calculate $cinst.Number as jst(tmpNewID,'-8P0')

Do inherited Returns tmpResult
If not(tmpResult)
  Calculate $cinst.Number as ''
End If

Quit method tmpResult

We also change the contact maintenance window so the contact number field is always disabled and we change the record browser base window to redraw on a successful save.
Now it's time to build our broker. Now this broker is going to be really simple, it's only going to support windows and it's only going to support messages to inform of record updates. You can build a more complex broker if you want.
I've created an object base class with some basic error handling that we can extend later on. I won't detail it here right now as we're not really using it for the broker.
We start with the constructor for the broker.

class variable cSubscribeList kList
oBroker.$construct
----
Do inherited 

If cSubscribeList.$cols.$count=0     ;; only init our subscription list once
  Do cSubscribeList.$cols.$add("instanceName",kCharacter,0,250)
  Do cSubscribeList.$cols.$add("tableName",kCharacter,0,250)
End If

Our broker class contains a class variable in which we keep the information about the windows that have subscribed to the broker. This is a class list since we will have an instance of our broker within our window that is subscribing and an instance of our broker in the window that will be sending the messages. You can think of the broker object as a doorway to our subscription list that we can construct and throw away as we please.

oBroker.$subscribe(pInstanceName, pTableName)
----
If cSubscribeList.$search($ref.instanceName=pInstanceName&$ref.tableName=pTableName,kTrue,kFalse,kFalse,kFalse)=0
  Do cSubscribeList.$add(pInstanceName,pTableName)
End If

Quit method kTrue
This method allows us to subscribe to our broker. It tells the broker we want to receive information about changes to a specific table.

oBroker.$unsubscribe(pInstanceName[, pTableName])
----
If cSubscribeList.$search($ref.instanceName=pInstanceName&($ref.tableName=pTableName|pTableName=''),kTrue,kFalse,kTrue,kTrue)>0
  Do cSubscribeList.$remove(kListDeleteSelected)
End If

Quit method kTrue

Our unsubscribe method unsubscribes our window. We can unsubscribe all our subscriptions at once by only specifying the instance name. Note that in the window base class $destruct method I've put the following code:

wBase.$destruct()
----
local variable tmpBrokerObj kObject(oBroker)
Do tmpBrokerObj.$unsubscribe($cinst().$name)
This will ensure any subscriptions still active for our window will be unsubscribed when a window closes, I never have to worry about this anymore.
Now for the code that will actually send the messages around:

oBroker.$sendmsg(pSenderName, pTablename, pDataList)
----
;  pDataList could be a list of primary keys that have been effected
For cSubscribeList.$line from 1 to cSubscribeList.$linecount step 1
  If cSubscribeList.instanceName<>pSenderName&cSubscribeList.TableName=pTableName
    ;  inform each of the subscribed windows
    If $iwindows.[cSubscribeList.Instancename].$methods.$findname('$receiveBrokerMsg')
      Do $iwindows.[cSubscribeList.instanceName].$receiveBrokerMsg(pSenderName,pTableName,pDataList)
    End If
  End If
End For

Our send logic simply loops through the subscription list and sends the message to any instance that has subscribed to the table that has been changed unless it's the window that send the message.
pDataList is a list variable that should contain at least the primary key column of each record that was changed in the table.
For our example however we'll make it easy for ourselves. We'll change our wBaseRecordBrowser.$evSave method to automatically call the $sendmsg method of the broker when we save a record and just send the row being changed in its completeness:

wBaseRecordBrowser.$evSave
----
local variable tmpBrokerObj kObject(oBroker)
[...]
If tmpSuccess
  Do gDatabase.$commit()
  ;  Tell our broker, we updated something...
  Do tmpBrokerObj.$sendmsg($cinst().$name,iRecordRow.$servertablenames,iRecordRow)
[...]

Now all we need to do is use our message. When a user changes an exist or adds a new contact type, we want our contact window to reflect this. So we handle the message on our contact window. Two changes are needed:

local variable tmpBrokerObj kObject(oBroker)
wContact.$construct
----
;  First define our record row for the correct type!
Do iRecordRow.$definefromsqlclass('tContact')

;  Tell the broker we want to know about ContactType changes
Do tmpBrokerObj.$subscribe($cinst().$name,'ContactType')

;  Then do the default construct!
Do inherited

wContact.$receiveBrokerMsg(pSenderName, pTableName)
----
Switch pTableName
  Case 'ContactType'
    ;  Rebuild our contact type list!
    Do $cinst.$objs.iContactTypeList.$buildList()
  Default
End Switch

Note that we've also changed the $construct code and added a $buildList method to the iContactTypeList field on this window. The $buildList will rebuild the contents of the contact type list and make sure the correct line is reselected. In this case its easier to just retrieve the updated data from the database but we could also have looked up the data within the contact list using the pDataList parameter.
Now open both windows, look up a contact record and change the description of the contact type for that contact and you'll see it gets updated.

No comments:

Post a Comment