This is the fourth (and last) entry in a series of blog posts I'm writing to demonstrate ColdFusion in a RIA architecture. In the first entry, I introduced the simple CF version of an address book application. In the second, we looked at how to use the built-in CF 8 features to create a version of that application that uses AJAX to improve user experience. The third installment illustrated how a Flex application could be used to leverage the existing ColdFusion application logic while delivering a user experience that is far superior to what you can easily do with HTML and JavaScript. In this last installment, I am going to show how, with a few modifications to my lovely Flex UI and a little mapping on the server, I can use my existing CF application as the model provider for LiveCycle Data Services, and take my Flex application to the next level.
We'll begin by talking about what changed on the client. For the most part, the code is the same. One change is that I'm now getting my data from a DataService tag (rather than a RemoteObject tag) - this tells Flex to talk to my LCDS data service to get data. I disabled autocommit in the DataService - if I didn't, edits to grid rows would be automatically committed on the server for me with no additional work on my part. I turned it off in order to show how you can manually handle commits. Beneath the grid are buttons to commit or rollback all of the changes you make in the grid - each button simply calls a method on the dataservice (appropriately named "commit()" and "revertChanges()"). That's right - commit/rollback functionality without writing any actual code - nice!. What's nicer is that now I can open multiple clients and browse the application, and changes made by one client immediately reflect in the grid for all other clients, and if I try to edit a grid row that another client edits at the same time, it handles that conflict for me, too - no additional client code required. Wow... thank you, DataServices! There are two other small changes in the client code. First, to initially populate the grid using a DataService, you actually tell the dataservice to "fill()" the ArrayCollection you want to populate (again - this is simpler than handling a Remote Object result event). The other change worth noting is that I added the [Managed] MetaData tag to the DTO - this tells Flex that LCDS is managing these objects on the server for me (yes, you still also leave in the [RemoteClass] directive for mapping data types, too). OK, so the client is even more robust, and was made so with less effort. Surely, this must mean I did more work on the server? Let's see.
The 4 ColdFusion Components from the first version of the application are all still intact in the LCDS version - and none of the 4 were changed in any way yet again. Again, I have added a 5th CFC, 'facade.cfc'... only my facade cfc contains a lot more code than the facade for my 'simple' Flex version. This is because in order for LCDS to expose all of that cool functionality to Flex - the ability to retrieve all of the objects, add/edit objects, handle conflicts, paginate, etc. - you must in turn expose this same functionality to LCDS from your server side application (my facade.cfc in this case). The two questions you may be asking are "What methods do you have to expose to LCDS?" and "How does LCDS know what methods to call and what to pass them?".
First, LCDS. LiveCycle Data Services works by passing requests to a user defined "destination" - remoting in CF works this way, too. A default CF installation has a "ColdFusion" destination defined in it's remoting services XML file which points at the CF Server root (so you can call any CFCs on the server) - in the case of LCDS we add a destination to our data-management-config.xml file. The LCDS destination contains a lot more information than a typical remoting destination, though. If you're using CF as the data provider, you want to specify the "coldfusion-dao" adapter for your destination (make sure you've enabled it first) and define the communication channels - for real time it's "cf-dataservice-rtmp" and for polling it's "cf-polling-amf". You then specify the component that the destination will use - in my case it's the full package name to my facade CFC. You have to specify, in a 'metadata/identity' node of the destination, the name of the property in your CFCs that is the unique identifier - this is used by LCDS for edits, conflict handling, etc. You also have a few properties you can define for the fill method (the method that gets all records) and there are other properties of the destination you can define, but for the most part, that's all you have to do. When you write Java code to talk to LCDS, in the destination you can specify the names of all the various methods to call (as well as some properties of those methods) but in the case of CF, LCDS is hard-wired to use reserve method names on the CFC you specified. So there's really very little to do to configure LCDS for CF. All the "work", as I suggested earlier, is in the component you point your destination at... so let's take a look at what I did in facade.cfc.
As I said, all of the original CFCs were left in tact - we need only expose the right functionality and data to LCDS from one CFC (facade) using reserve method names - that facade (often refered to as an Assembler) talks to my other CFCs. One thing to note is that LCDS will pass a change object (or an array of change objects) to many of these methods. That change object has methods to tell you if this is an add, edit, or delete, and has methods for getting the old data and the new data (the old and new DTOs). The methods LCDS expects and a brief description of each are as follows:
- fill
- Returns all entries as an array of DTOs
- get
- Gets one DTO - receives a structure containing the unique id of the DTO to return
- sync
- Sync accepts an array of change objects - for each one, the sync method passes the change object to the appropriate action method (add/edit/delete method). Those 'action' methods return the new DTO (or old if there's a conflict or some business rule failure) which is placed in an array that the sync method ultimately returns.
- count
- Tells LCDS how many DTOs there are.
- create
- Receives a change object, creates the new DTO, adds that to the newVersion property of the change object, marks the change object as 'processed', and then returns the change object
- update
- Receives a change object and compares it's old DTO with the matching live DTO to make sure there isn't a conflict (resulting from trying to edit a DTO that another client already edited) - if there is, it flags the conflict in the change object. If there isn't, it commits, adds the new DTO to the change object, and returns it.
- delete
- Receives a change object and checks to see if that DTO was already deleted (in which case it flags the change object as 'failed'). If it wasnt, the method performs the delete, adds the deleted ID to the change object to flag it as deleted to all other clients, and marks the change object as processed.
All of this might seem like a lot of work, but if you download the code (
http://www.horwith.com/downloads/abook-lcds.zip) and take a look at the facade, you'll see that it's actually fairly simple to implement - especially given the functionality it allows you to drop-in to your Flex/LCDS applications. That zip file also includes the Flex LCDS code, and the config file for my LCDS server destination. If you're comfortable working with CF, using it as a provider to LCDS allows you to build very rich clients in Flex without having to struggle with client code. You could, of course, use this facade to introduce many of the same features to "regular" CF applications as well.
I hope these past 4 blog entries were useful for folks. Here are the links to the various versions of the code:
Regular CF Version CF AJAX Version Flex Version LCDS Version