Programming
Dataworks Enterprise Programming
Dataworks Enterprise.
1
Obtaining
data using a Dataworks Enterprise Client3
Record
lifetime and automatic request cancellation. 9
A
last look at our program?... 12
Providing
data using a Dataworks Enterprise Server13
A
last look at our program?... 18
?Dataworks Enterprise? refers to all the modules & tools that collectively
make up the entire Dataworks Enterprise Distribution as supplied by Thomson Financial.
Also included in a standard distribution is various documentation, configuration
information, and also Dataworks Enterprise software development kit. This document is intended to supplement Dataworks Enterprise SDK and assist
developers in getting started in using Dataworks Enterprise to develop their own client and
server programs. Later in this document discussion moves toward more
advanced topics regarding Dataworks Enterprise development and recommendations for your Dataworks Enterprise
programs. It is assumed that the reader is familiar with the basic principles of
COM programming. Further, since, for all its shortcomings, Visual Basic is
the most widely used system for COM development, and is relatively easily
understood, discussion and samples in this document will use Visual Basic except
where specifically referring to other development environments. Where the
developer prefers to develop using other languages such as C++ the translation
from VB should be readily apparent. Further, it is assumed that the reader has Dataworks Enterprise installed on their
workstation.[1]
It is recommended that at least the ?Documentation? and
?Examples? be selected when installing Dataworks Enterprise for development purposes.
This document does not discuss the general Dataworks Enterprise architecture from an
operational perspective. If you want a discussion of Dataworks Enterprise? data
distribution, resiliency, fail-over and load balancing features, or details
regarding the use and configuration of the various Dataworks Enterprise modules please refer to
the ?Dataworks Enterprise Operations Guide?. Dataworks Enterprise has been designed from the outset to be a high performance
system. As such it has been built with careful optimisation and tuning to
run natively on the Windows NT platform, however, a portable Java version,
referred to as ?the JPress?, is soon to be released which will allow Dataworks Enterprise
functionality to be available on non-NT platforms such as Unix systems, and most
other platforms that have a standard Java implementation. However, the native NT implementation remains the preferred way of
running Dataworks Enterprise. The Java implementation is primarily intended as a facility
that allows the functionality of the native NT implementation to be accessible
on other platforms. Thus, it is expected that most development against
Dataworks Enterprise will be done using NT. Where development is done using the Java
interfaces the principles remains the same with a few caveats that will be noted
in the appendixes of this document. This document primarily discusses developing against the native NT
implementation. At the heart of Dataworks Enterprise is the cache. The cache provides the
core messaging framework for communication between Dataworks Enterprise components. An
extremely minimalist installation of Dataworks Enterprise need only consist of one file,
RTCache.dll. This DLL exposes a collection of COM objects that implement
the cache?s messaging functionality and provide the external interfaces to
it. Any program that connects to the cache will need to use these COM
objects; there is no ?lower-level? way to access the cache functionality.
The COM interfaces exposed by all of the core Dataworks Enterprise components are
?automation compatible?. Thus it is possible to build fully functional
systems that use Dataworks Enterprise using just about any development system or language
that can use COM, including scripting clients such ASP web pages, C++, Visual
Basic, etc. In this section we will demonstrate how to obtain data from Dataworks Enterprise
by building a simple program that will connect to a Dataworks Enterprise source and collect a
few items of data from it. First, we?ll need a Dataworks Enterprise source. Although there are many server
modules that are distributed with Dataworks Enterprise, we?ll start by using a sample
server called ?TestSvr.exe? that will give us some sample data to work
with. TestSvr can be found in the ?Examples\VB\TestSvr? subdirectory of
Dataworks Enterprise installation directory. E.g.: "C:\Program
Files\Primark\Platform\Examples\VB\TestSvr\TestSvr.exe" When you run TestSvr you will find a few dialog options that allow you
to adjust the type of Source to be created. To get TestSvr to create a
normal source specify the name of the source you wish to create (e.g. ?TEST?)
and ensure that the options ?Standard Source? and ?Record Server? are
selected. Then press the Start button. We can verify that this source has been created successfully using the
?Client.exe? program. This is installed in Dataworks Enterprise Installation
directory under the Bin directory. When this starts it displays a list of available sources and a test box
into which you can type the symbol of the instrument you wish to request of the
selected source. In the case of TestSvr, all instrument names are valid, but lets take
the instrument ?ABC? for example. Select the ?TEST? source (or whatever
name you chose for your source in TestSvr) and then type ?ABC? for the
Instrument then hit return. The Client program should now look similar to this: What this shows is the dynamically updating list of fields available
for the ABC instrument of the TEST source. If you see this, all is well. Otherwise check that TestSvr is
running as described above. Lets start by running up Visual Basic and creating a Standard Exe
project. The first thing we need to do is reference the RTCache type
library. Follow the ?Project-References? menu, select the ?TOSC Real-Time
Cache?, and click OK. (When coding we can now reference all the types
declared in this Type Library using the prefix ?RT.
?). TIP: New versions of Dataworks Enterprise sometimes include
extensions to the original interfaces. If you write code that relies on
these new interfaces your program may not run successfully where an older
version of Dataworks Enterprise is installed. If you want to ensure that your program
does not rely on newer features, you can instead choose to reference one of the
older versions of Dataworks Enterprise? type library that are distributed as part of
Dataworks Enterprise SDK. Since our program is to be a client of Dataworks Enterprise, we need to create an
instance of RTClient object to connect to Dataworks Enterprise cache. There are two mains ways to use the RTClient: the synchronous method
and the event-based asynchronous method. The synchronous method is
slightly simpler to code however it is usually reserved for scenarios where we
don?t can?t handle events, such as in web server scripts. The event-based
asynchronous method is the default, and for most purposes the preferred
method. Initially though, we will use the synchronous method. The
RTClient has a Boolean property called Blocking that when set to true will cause
synchronous semantics to be applied to the retrieval of data. Now we can double-click our project?s form to view the code for our
form including the Form_Load event handler, and add code to create a Blocking
RTClient. Dim
rtc As RTClient TIP: Dataworks Enterprise installs a help file describing
all the cache objects. To get help any cache object or method just select
the object in VB and press F1. Alternatively, if you?re not running VB
then you can simply open the help file by typing its name - ?RTCache.hlp? - in
Explorer?s ?Start-Run? box. To make a request of this client we need to get it to create a record
that will represent the request for an individual instrument, then set the
appropriate attributes of that record, and ?bind? it to the cache.
Dim rtc
As RTClient The call to the Bind method is where the important stuff happens.
Having ?bound? the record, the cache requests the specified source for the
specified instrument. In this case, the cache knows that the ?TEST? source
is being provided by the TestSvr program, and sends it a request for
?ABC?. The resulting data will subsequently be available in the Fields
collection of our record. The term ?Bind? is used since a record does not actually
make a request until it is bound to the messaging system in the cache by calling
this method. It has been observed that either the terms ?Request? or
?Subscribe? might have been better names for this method. However,
changing it now would be much more trouble than its worth, so we must live with
it! Now that we have requested the record, we can access the fields that
have been returned for this record. Accessing an individual field can be
done by name using the record?s Field property. Alternatively, all the
available fields can be accessed from the record?s Fields collection. The
Fields collection can be indexed by numerical index or by field name, or it can
be iterated through using a standard COM enumerator. In the following example, we enumerate the fields and for each field we
trace out each fields Name and Value. Dim rtc As
RTClient The Visual Basic Immediate window should show the output similar to the
following (the values themselves may differ): Rather than iterating through the fields collection, if you?re
interested in a specific field it can be slightly more efficient to use the
?Field? property. The alternative of using the Fields collection means
that the cache must construct the collection object itself. So for
example, we could get the value of the BID field like so: somevariable =
rec.Field("BID").Value
We now have a simple working Dataworks Enterprise client. However, Dataworks Enterprise is
a dynamic cache that monitors the data for changes. (As such, we often
refer to ?subscribing? to an instrument rather than simply ?requesting?
it.) To be informed of changes to an instruments data we need to respond
to events from the RTClient. The RTClient can fire several different events but we will start with
the ImageFields and ModifyFields events. Consider the following reworked code: Dim WithEvents rtc
As RTClient Some important changes have been made here. Most notably, we are
now accessing the fields only when an event occurs that indicates that new field
values are available. (We?ll look at what the ImageFields and ModifyFields
actually mean in a moment.) Since we?re now using the event based asynchronous semantics of the
RTClient we set Blocking to False and tell VB that we?re interested in RTClient
events by specifying ?WithEvents? when declaring our rtc variable.
Finally, it is important that we keep a reference to our record around
somewhere (we?ll look at why later), so we create a records collection and store
the record in that. TIP: When using VB?s debugger to step though a program
or wait at breakpoints then VB handles events in ways that may be less than
ideal under some circumstances. The designers of the VB environment wanted
the program to continue to appear functional at all times, so if you use the
debugger to single-step through the code and an event such as ImageFields
occurs, VB just throws the event away. This is useful if the event
is something relatively unimportant like a button click, but our case failing to
receive an Image event can be most confusing. Therefore, I recommend that
you don?t single step through this program beyond the call to Bind, because you
will probably miss the ImageFields event. Rather, if you want to examine
what is going on inside the ImageFields event handler function, place a
breakpoint inside the ImageFields function and let the program run without
single stepping beyond the call to Bind. In summary, this is what actually occurs with this version of our
program: 1)
As the program starts the form loads and the Form_Load
function is called. 2)
We create our client, create our record and make the request
to the specified source using Bind
. 3)
The function exits, the form is displayed, and the program
begins processing windows events. 4)
The Cache receives data for the request and causes an
ImageFields event to be fired. Our ImageFields event handler loops through
the available fields printing each one out. The program then resumes
processing windows events. 5)
The Cache receives further updates for our instrument and
for each causes the ModifyFields event to be fired. Our ModifyFields event
handler loops through the fields that have changed printing each one
out. The program then resumes processing windows events, and so on.
Note the parameters to the ImageFields and ModifyFields
functions. Each has two parameters: a pointer to the record that is being
updated and the list of fields that are being updated. ImageFields is used when the source is supplying
the complete set of fields that make up the record. Providing there is not
a problem with the request then ImageFields is usually the first event that is
fired after a record is requested. ImageFields can be fired subsequently
for a record but the client program should consider the list of fields supplied
at that point to be the complete and correct list, ditching any previous fields
that it had for that record. As such the list of fields supplied as
the event function parameter will exactly match the collection of fields
obtained by examining the records Fields
property. ModifyFields on the other hand is used when the
source is supplying changes to the previously supplied fields for a
record. It typically will be just a subset of the list supplied for
ImageFields. By contrast, the records Fields
property provides a collection that
reflects the latest value of all available fields. Because of this when
our program receives a ModifyFields events it iterates through the fields
collection supplied as the event function parameter rather then the records
Fields
property, since we
don?t want to print out fields that haven?t changed in this case.
When you run this version you should see something like the following
in VB?s Immediate output window. -----
Image for ABC Although the values used here are rather meaningless from the
perspective of financial market data they do help to illustrate what is going
on. (In this case I let the program run for about six seconds. Note
that ModifyFields was called twice. This is because TestSvr has a timer
which it uses to update four of its fields every three seconds.) Note that the fields LAST and PRCTCK are not being updated by the
source so they aren?t being supplied to the ModifyFields. If we did need
to obtain the current value for the LAST field we could obtain it at any time
from the records Fields
collection (or using the record?s Field
property). Also, note that the source is choosing to send out the SYMBOL field
with each update even though its value remains the same. Just because the
value is the same as it was doesn?t prevent a source telling you that the client
should consider it to have a new value! This is useful in various
contexts, in particular when delivering financial market data ? a trade may
occur at the same price as the previous trade but clients still need to know
that a trade has occurred. In our previous example, consider what fields are available immediately
after we call Bind, but before our Form_Load function exits. We have
Blocking turned off so we?re using events to be notified of data availability,
but at that moment no events have yet been fired. Therefore, our record
simply won?t have any fields at that point. That?s why in this
example we have moved our code that uses the record?s Fields
property into the ImageFields event
handler. How then does it work in our earlier example when Blocking was tuned
on? In that case, when Bind is called the cache immediately sends off a
request to the source that then gets processed on another thread. If you
then access the Fields collection but no data has yet been received in response
to the request, the cache blocks the thread until such data is available.
Note that the thread only blocks when necessary, that is, as you attempt to
access properties of the record that would return data that isn?t yet
available. If you don?t access the Fields collection (or any other
property that returns the contents or state of the record) the thread won?t
block. If on the other hand, if you only access the record?s data after
the data has been internally received by the cache, then the data can be
returned immediately, without needing to block the thread at all.
TIP: When you are writing code that must be
synchronous yet efficient, such as building web pages using ASP script, it is
worth keeping in mind these blocking semantics. (In such circumstances,
setting Blocking to be true is essential since the script-processing engine
simply does not supply events at all.) In this case, try to make your
requests for all your records as early on as possible in the script, then access
the record?s data as late as possible. In the intervening time the cache
may be able to obtain all the required data for all the records, thus avoiding
thread blocking and minimising context switches. On the
subject of ASP development, do not be tempted to store references to the
RTClient, RTRecord or any other Dataworks Enterprise objects at the ASP Application or Session
scope. Since all Dataworks Enterprise objects use the COM Apartment Threading model,
you?ll cripple your web server performance if you do this. (See the
Advanced Topics section below and Microsoft Knowledge-Base article Q243543 for
more information.) Note that when you set Blocking to be true, the thread blocking
actually only occurs in the outer layer of the cache and only on the clients
calling thread. Internally the cache continues to run asynchronously,
efficiently servicing all other data handling required. In our last example, we created an RTRecords collection in which we
stored a reference to the record we created. This is very important because it keeps the
record in existence. COM objects are
reference counted ? this enables an object to delete itself when there are no
more external references to it. When this occurs for an RTRecord object it
first un-binds itself from the cache causing it?s request to be cancelled.
This behaviour is very convenient for most situations ? ?forget? about
a record and it will quietly clean itself up and stop pestering you with
events. On the other hand, it is important to ?remember? a record while
you?re still interested in its data. So in our example, if we had not stored a reference to the object in
the records collection (which is declared at global scope) then when the
Form_Load function exited then there would be no references left to the record
we had just requested. In other words the record would be bound and then
immediately un-bound, and we would never get any data for it. (Note that although we used Dataworks Enterprise? RTRecords collection to store
the record any method of holding a reference to the record would have been
sufficient. ) If you want to explicitly unbind a record at anytime but keep the
record in existence then you can simply set its Bound
property to false. So far, we have successfully requested a record from a source.
What if something goes wrong? For instance, what if the instrument name
requested wasn?t available from that source? Or, what if the source itself
wasn?t available, or was subsequently shutdown, or had some other
problem? Situations such as these are handled by Dataworks Enterprise? status information
which is maintains about each requested record. This status information is
always available from the record?s Status
property, and clients are notified of any changes to a record?s
status via another event named ChangeStatus. Here?s a trivial example of a ChangeStatus event handler for our
program: Private Sub
rtc_ChangeStatus(ByVal Record As RT.IRTRecord,
_ Here the supplied parameters specify the Record whose status is
changing, the StatusType which specifies the new state of that record, a
StatusCode which is really a just a code specifying the reason for the state
change, and finally a human readable text string further explaining why the
fault has occurred. ChangeStatus is only called when there?s a fault. If a record
gets some data in an image then it should be inferred that any previous faults
on the record have been resolved (else we wouldn?t be getting any data
anyway). Although there are 5 values in the RTStatusType enumeration the
StatusType parameter in the ChangeStatus event can only ever be one of two
values: either RTStale or RTFailure. ?
If the Source issues an RTStale status, it means
?there is a problem with the data, I may be able to automatically recover the
situation and provide correct data later on?. ?
If the Source issues an RTFailure status, it means
?there was a problem with your request, I will not be able to supply any further
data for this record?. RTFailure is a permanent fault, whereas RTStale is often referred to as
a temporary fault (although this is sometimes a little optimistic since the
fault can last indefinitely!). A couple of example situations causing each kind of fault: An RTStale might be returned if you request something from a Source
that does not exist, since someone might start the source later in which case
the request would then be applied against the source and data hopefully
returned. An RTFailure might be returned if you request something from a Source
that has a fixed list of possible instruments but you have requested an
instrument that is not part of that list. It?s never going to be able to
supply the data so it puts the record into a permanent RTFailure
state. Of the other possible status types besides RTStale and RTFailure, the
RTStatusType additionally has the following values (although these never occur
in the ChangeStatus Event): ?
RTPending is the initial state a record
has before it receives any data or other status notification. ?
RTConnected is the state a record has
when it has received data but no subsequent fault. It is the state a
record will have following an ImageFields event, and can be interpreted as the
?normal? state of a record. ?
RTReRequest is never seen by a client of
the cache, it is only used by the cache servers. (These are discussed
later.) When using Blocking semantics, before examining the records field
collection we should really check that the record has a state of
RTConnected. If it is not, we should respond to the fault rather than try
accessing the fields, which will be invalid anyway. We have considered ImageFields, ModifyFields, and ChangeStatus.
What of the other events that the RTClient can fire? (Keep in mind that ImageFields provides the initial comprehensive list
of fields, and ModifyFields provides updates to the values of fields in that
list.) You may have noticed the following other events are
declared: AddFields:
This supplies new fields that did not appear in the field list supplied with the
Image. DeleteFields: This
supplies a list of fields that may previously have been supplied via ImageFields
or AddFields, but that no longer exist for this record and should no longer be
considered to be part of the records fields collection. MaintainFields: This is a rarely
used event whereby a Source can notify a client that the values of previously
issued fields need to be corrected with new values. It is commonly treated
exactly like a ModifyFields event by client programs. (The distinction
between MaintainFields and ModifyFields is only really important when dealing
with financial data that represents traded data. In this case a Modify
represents a change to previous data caused by a trade, whereas a MaintainFields
is issued to correct previous data but the update should not be taken to
represent a trade.) SourceChange:
an RTClient object maintains a list of available sources. This event
notifies the client of changes to this list. Command:
sources can support ?Commands? that are sent by a client. This events
supplies the response that a Source can return to the client. (Commands
are discussed later.) BeginTransaction
and? EndTransaction:
Dataworks Enterprise does not currently use these two events. At some future time
these may be supported, but for now they can be ignored. As mentioned above, the RTClient object maintains a list of sources
available on the local machine. The current list of sources can be
obtained from the RTClient object via its Sources
property. However, sources
can be dynamically mounted or dismounted. The SourceChange event will tell
you if a particular source subsequently goes up or down. Try adding the following event handler to our client program, then
watch its output while you press the Stop/Start button on the
TestSvr. Private Sub
rtc_SourceChange(ByVal Source As RT.IRTSource,
_ TIP: Be careful not to rely on the SourceChange
event until you have accessed the RTClient?s Sources property. The
SourceChange events are only fired after the Sources list has been established,
which only happens after you access the Sources collection. Therefore, if
you never access the Sources collection, you will never get a SourceChange
event. This is a side effect of the permissioning system in Dataworks Enterprise,
which will be discussed later. We now have a simple program that uses an RTClient object to connect to
the cache. It doesn?t do anything interesting with the data it receives,
it just dumps it to an output window; manipulating the data or displaying it in
more imaginative ways is left to your own imagination? Bear in mind that we have only requested one record from the RTClient
for the sake of space and simplicity but we could easily request other
records. In our Form_Load function we have a few lines of code that create and
store a request for ?ABC?. We could duplicate these to request a second
record, lets say ?XYZ?, like so. Set rec
= rtc.CreateRecord Set rec
= rtc.CreateRecord Our records collection would now contain two RTRecord objects
representing ABC and XYZ respectively. When events such as ImageFields are
received, we know which record is being updated because a reference to the
appropriate record is passed as an event parameter. Tags Another useful feature of almost all the commonly used Dataworks Enterprise objects is
that they store a ?Tag?. This user-defined value is associated and
maintained for each object into which you can place anything that will fit into
a Variant. What you use this for is up to you but it is often useful to
associate an index of other object with, for example, each record object for use
later in your program. Samples You can find a slightly more complete example of a client program
written in Visual Basic in the Samples\VB\Client directory of Dataworks Enterprise
SDK. This example has similar functionality to the program we have just
built except that it also displays the record?s status and data on the form in a
more user-friendly manner. Thus far, we have shown how to retrieve data from a Dataworks Enterprise source using
its RTClient interface. For simplicity, we have only looked at one source
mounted by one Dataworks Enterprise server, the TestSvr. However, Dataworks Enterprise is supplier
with a wide variety of data feed handlers what expose their data as Dataworks Enterprise
sources. Although Dataworks Enterprise excels as a data retrieval mechanism both in terms
of ease-of-use and performance, this in itself is not enough to distinguish it,
as there are other tools in the market place that allow you retrieve data from
various systems. Where Dataworks Enterprise really show its true colours is the ease with which new
data sources can be created and mounted onto Dataworks Enterprise system. This
section will demonstrate the creation of a new data source. Dataworks Enterprise ?Servers? are programs that use the RTServer object to create
?Sources?, where each source is a logical collection of data. Each
server can mount multiple sources. Dataworks Enterprise servers are commonly referred to
as ?Handlers? since they usually ?handle? an external data feed, serving up the
data to Dataworks Enterprise system. Creating a Dataworks Enterprise server source is about as easy as creating a Dataworks Enterprise
client. Experience shows that in practice the task of writing a real-world
Dataworks Enterprise server to interface to a typical external data feeds almost entirely
consists of the effort of handling the details of that feed. The amount of
code to necessary to write to the RTServer interface is usually minimal by
comparison. The process of writing a Dataworks Enterprise server is effectively the inverse of
that involved in writing a Dataworks Enterprise client. In brief, these are the steps required to mount a source:
1)
Create an RTServer object. 2)
Ask it to create a Source of a given name and type.
3)
Wait for Dataworks Enterprise to send an event describing a request
made of that source for a given item. (A server-side item corresponds to a
client-side record.) 4)
Obtain the appropriate data by some means and supply it to
Dataworks Enterprise by calling methods on the Item. 5)
Continue to update Dataworks Enterprise with any changes to the Items
data until Dataworks Enterprise sends your server an event notifying it that no more
clients are interested in that item?s data. Like the RTClient, the RTServer has two basic modes of operation.
You can wait for RTServer events as outlined above or you can pre-emptively
?push? data into the cache regardless of whether it is currently required by any
clients. Most servers will supply data in response to events so we shall this
technique first, and leave pushing items into the cache until later.
Since in this example we want to focus on Dataworks Enterprise interfaces, and not
become bogged down in the details of obtaining data from some external
repository, we?ll just conjure up some data in our server program.
In this example, we?ll create a source called ?SCOOBY? which will
supply data for one or two named items (or records from the RTClient?s
perspective) each corresponding to a hypothetical individual. The data we
supply for each individual will consist that persons age, sex etc.
As with Dataworks Enterprise client, lets start by running up Visual Basic and
creating a new Standard Exe project. Once again, we will want to reference
the RTCache type library. Now we can add some code to our new form?s Form_Load
event. We shall first
create ourselves an instance of an RTServer object. Then we will ask it to
create a source called ?SCOOBY? for us: Dim WithEvents rts As
RTServer Dim srcScooby As
RTSource Private Sub
Form_Load() Set rts = New
RTServer Set srcScooby =
rts.CreateSource("SCOOBY", "Record", RTStandardSource) End Sub We have stored a reference to these objects at global scope because as
with RTRecord objects created by the RTClient, RTSource objects are functional
only while they exist and we are responsible for keeping them in existence by
holding a reference to them. Now we can add a ?Request? event handler that will be called when a
client requests an item from us. Private Sub
rts_Request(ByVal Source As RT.IRTSource, _
ByVal Item As RT.IRTItem, _
ByVal ReqType As RT.RTRequestType) If
Item.Instrument = "SHAGGY" Then Dim
fields As New RTFields
fields.Add "AGE", 20
Item.ImageFields fields End
If End Sub The parameters for the Request event specify the Source, the Item that
represents the client?s request, and a RequestType, which we shall discuss
later. We only receive events for Sources that have been mounted by our server
so in this case the Source parameter will be that same as the srcScooby object
that we created earlier. Next, this code examines the Item?s Instrument property to discover
exactly what the client has asked for. If they are asking for something
that we recognise then we create an RTFields collection object, and populate it
with some data using the Add method. Then we need to supply our fields collection to the cache, telling it
to update the corresponding Item. Since this is the first time we are
supplying data for this item we use the ImageFields method of the
Item. Note that an RTItem object has methods that
correspond directly to the events that an RTClient object fires as discussed in
the previous section. In this example, we have supplied the data to satisfy a request in the
Request event function itself, but it doesn?t have to be done in this way.
Depending on the implementation of a server the data may not be immediately
available and it may not be desirable to wait until it is available before
returning from the Request function. If our server must perform a
substantial amount of work to be able to produce the data required for the
Image, then it is perfectly acceptable for our server to return from the Request
function and then supply the data for the Image some time later. This can
significantly improve the responsiveness and efficiency of various types of
server. Bear in mind that it is important for the server to return a response
to client clients request in a reasonable time frame (even if that response is a
failure notification using ChangeStatus as described below). This is
because some clients may be blocking waiting for a response for their
request. If the server fails to provide a response in a timely manner, or
even at all, then the client may be blocked for a long time, which is rarely
desirable. What should we do if the client requests something we don?t recognise
or for which we cannot supply data? Then we should set the status of the
item to indicate this fact. Private Sub
rts_Request(ByVal Source As RT.IRTSource, _
ByVal Item As
RT.IRTItem, _
ByVal ReqType As RT.RTRequestType) Dim
fields As New RTFields If
Item.Instrument = "SHAGGY" Then
fields.Add "AGE", 20, "Twenty"
fields.Add "SEX", "M"
Item.ImageFields fields
ElseIf Item.Instrument = "DAPHNE" Then
fields.Add "AGE", 21
fields.Add "SEX", "F"
Item.ImageFields fields
Else
Item.ChangeStatus RTFailure, RTStatusNoSuchItem,
_ End
If End Sub This code now also supports a second person, but if the client is
requesting neither ?SHAGGY? nor ?DAPHNE? then we call ChangeStatus to alert the
client to the problem by calling ChangeStatus. When we call ChangeStatus we specify RTFailure to indicate that this is
a permanent failure from we cannot not recover. We also give some
indication as to what the problem is by specifying a status code of
RTStatusNoSuchItem along with a short textual description. (See the
RTCache help file for other possible values for the RTStatusCode parameter and
recommended usage.) At this stage, we have a minimalist but usable program that we can run
and test. Try running this server program, and then run the standard Dataworks Enterprise
Client.exe program as described earlier in this document. You should see that the Sources list now contains our ?SCOOBY?
source. Next to this, you can type the name of one of our people, try
?SHAGGY?, and see the data returned. TIP: Remember that Dataworks Enterprise is case-sensitive
with respect to Source names, Record/Item names, and Field names. If, instead of ?SHAGGY, you request something like ?PETERPAN? the
client program will display our status message indicating that this is an
unknown item in the window?s title bar. Next, we?ll update the Item?s data using the ModifyFields method.
In this example, we?ll add a timer to our form and use it to update an
item every few seconds. We?ll do this for just one of our items,
?SHAGGY?. To be able to update the item we need to be able to reference
this specific item later on, so we will store a reference to it. In this
instance we will do it the trivial way ? by storing the reference in a global
variable. To make sure we don?t hold onto this item longer than is appropriate we
will also handle another RTServer event ? Cancel. Here?s the full code for our new server: In our new program, if ?SHAGGY? has been requested, and not
subsequently cancelled, then the variable ?itmShaggy? will reference the item
representing this request. In the timer event function, which fires every
second, we create a new field collection and add a single field it which
contains the ?AGE? of shaggy which increments each time the function
executes. We call ModifyFields with this field collection which sends the
update to the client. In our program we have stored a reference to the item we want to update
by storing an explicit reference to it at global scope, but there are a numerous
alternative techniques. We could have simply looked up the Item by name
from the source?s ?Items? collections for instance. As has been previously noted the heart of Dataworks Enterprise is it?s
cache. The cache is responsible for maintaining a database of the items on
watch and passing the appropriate messages between the clients and servers of
this data. If a client requests a given item while another client already has the
same item on watch Dataworks Enterprise delivers up the current data to the second client
from its cache, without making any requests to the server. The Request for
an item occurs when the first client subscribes to it and the ?Cancel? message
for the item is only sent once all clients have dropped their
subscriptions to it. In other words there is a many-to-one relationship
between records and items. The cache ensures that all the interested
clients get any updates for it. It can be seen from this that the lifetime of a request is really
driven by the clients. The clients keep the Record around for as long as
they want and the cache keeps the Item that corresponds to these records around
for as long as is required by the clients. Thus the lifetime of a client-side RTRecord object
is the responsibility of the client program; whereas the lifetime of a
server-side RTItem is the responsibility of the Cache, rather than the server
program. In our server program, we do not need to hold a reference to the Item
to keep it alive; the cache will do that for us anyway. The only reason we
hold a reference to it is because it makes it easier for us to update the fields
of the item. Does this mean that the server has no control over the lifetime of an
item? Most servers will supply data in response to events. This ensures
that the server only goes to the effort of generating data is a client is
interested in it. Further there are many scenarios where the server has no
reasonable mechanism for producing the data until a client has specified what it
requires. However there are scenarios where these issues do not
apply. In this case a server may choose to pre-emptively push one or items
into the cache regardless of whether a client has requested the item.
Pushing data into the cache is usually done where the number of items
involved is small since we don?t want to unnecessarily overburden the
cache. To push an item into the cache we simply force the creation of a new
item for a given Source by adding the item by name into the source?s ?Items?
collection. For example to create an item called ?FRED? to our source and then
provide some data for it we can simply do the following: Dim itm As RTItem Items are normally cancelled once all clients have dropped their
subscriptions to the Instrument, or in the case of pushed items, by removing the
items from the Source?s Items collection. However, it is often useful to
inform clients that an item that they have on watch will no longer receive any
updates. For failure situations this can be accomplished by calling
ChangeStatus with the RTFailure flag, but what about situations where a failure
has not occurred but we still want inform clients that an item will simply no
longer update? In this case we can Terminate the item by setting its ?Indicator? to be
?RTTerminated?. This will cause the cache to cancel all client
subscriptions to the item. E.g. itm.Indicator =
RTTerminated We now have a functional Dataworks Enterprise server that provides updating data to
its clients. Although this document does not cover the operational aspects of
Dataworks Enterprise, we now have a server that with only a little configuration, but without
any further development, can expose its data while leveraging the various
distribution and resiliency features of Dataworks Enterprise Data Platform.
Contents
Introduction
Some
preliminaries?
Obtaining data using a
Dataworks Enterprise Client
TestSvr will now create a simple Dataworks Enterprise source of the name
specified and should look like this:
E.g.: "C:\Program
Files\Primark\Platform\Bin\Client.exe"
Building our own
client
Private Sub
Form_Load()
Set rtc = New RTClient
rtc.Blocking = True
End Sub
Private Sub Form_Load()
Set rtc = New
RTClient
clnt.Blocking = True
Dim rec As RTRecord
Set rec = rtc.CreateRecord
rec.Source = "TEST"
rec.Instrument = "ABC"
rec.Bind
End Sub
Private Sub Form_Load()
Set rtc = New
RTClient
rtc.Blocking = True
Dim rec As RTRecord
Set rec =
rtc.CreateRecord
rec.Source = "TEST"
rec.Instrument = "ABC"
rec.Bind
Dim f As RT.RTField
For Each f In rec.Fields ? enumerate the fields
Debug.Print f.Name & " : " &
f.Value
Next
End SubSYMBOL : ABC
BID : 37178.1152662037
LAST : 37178.2151967593
VOLUME : 2
EXCHTIM : 14/10/2001 02:45:59
NET_CHG : 0.3
PRCTCK : U
Using RTClient
Events
Dim records As RTRecords
Private Sub
Form_Load()
Set rtc = New
RTClient
rtc.Blocking = False
set records = new RTRecords
Dim
rec As RTRecord
Set rec =
rtc.CreateRecord
rec.Source = "TEST"
rec.Instrument = "ABC"
rec.Bind
records.Add rec
End
Sub
Private Sub rtc_ImageFields(ByVal Record As RT.IRTRecord, _
ByVal Fields As RT.IRTFields)
Debug.Print "Image for " & Record.Instrument
Dim f
As RT.RTField
For Each f In
Record.Fields
Debug.Print
f.Name & " : " & f.Value
Next
End Sub
Private Sub rtc_ModifyFields(ByVal Record As RT.IRTRecord, _
ByVal Fields As RT.IRTFields)
Debug.Print "Modify for " & Record.Instrument
Dim f
As RT.RTField
For Each f In
Fields
Debug.Print f.Name
& " : " & f.Value
Next
End Sub
LAST : 37178.549525463
VOLUME : 37
BID :
37178.449525463
PRCTCK : U
SYMBOL : ABC
NET_CHG : 0.3
EXCHTIM :
14/10/2001 10:47:19
----- Modify for ABC
VOLUME : 38
BID :
37178.4495486111
SYMBOL : ABC
EXCHTIM : 14/10/2001 10:47:21
-----
Modify for ABC
VOLUME : 39
BID : 37178.4495833333
SYMBOL :
ABC
EXCHTIM : 14/10/2001 10:47:24
Timing and
Blocking
Record lifetime and
automatic request cancellation
Status and Fault
handling
ByVal StatusType As RT.RTStatusType,
_
ByVal StatusCode As RT.RTStatusCode,
_
ByVal Message As String)
Debug.Print Record.Instrument & ?has a
problem because : ? & Message
End SubOther RTClient
Features
ByVal State As Boolean)
Debug.Print
?-------------------------------?
If State = False
Then
Debug.Print "Source " &
Source.Name & " has been dismounted."
Else
Debug.Print "Source " &
Source.Name & " has been remounted."
End If
End
SubA last look at our
program?
rec.Source =
"TEST"
rec.Instrument = "ABC"
rec.Bind
records.Add rec
rec.Source =
"TEST"
rec.Instrument = "XYZ"
rec.Bind
records.Add rec
Providing data using a
Dataworks Enterprise Server
Building our own
server
Imaging an
Item
fields.Add
"SEX", "M"Handling bad
requests
"Person unknown"Updating an Item?s
data
Item
lifetime
No. It can both create an item and deliver its data to the cache
independent of any client request ? this is called ?Pushing an Item?.
Also, it can ?Terminate? an item which forces its early cancellation, even if
clients still have it on watch. Pushed
Items
Set
itm = srcScooby.Items.Add("FRED")
Dim fields As New RTFields
fields.Add
"AGE", 24
itm.ImageFields fieldsTerminating
Items
A last look at our
program?