Building a distributed application | |||
Building The Monolith We'll start with a single PBL called 'pbaccess.pbl'. You can find this PBL in the code you downloaded under the /monolith directory. Here's what the PBL will contain when we are done building the monolithic application. - Lets go over the building process step by step. The Application Lets start with creating a new application called pbaccess. The open event of the application contains typical code that connects to the database and then opens the w_employee window. The database disconnect is coded in the close event. pbaccess::open // Profile Powersoft Demo DB V6 pbaccess:close Disconnect;
The Datawindow Objects Next, lets create the two datawindows. Let take some time here and think about how we are going to tie-up the list and freeform datawindows. We know the way the user interface is required to work. The user hits the 'Refresh' button to retrieve the data into the left hand side datawindow. Then, when he clicks on a row in this datawindow the same row is shown to him in the adjacent freeform datawindow, where he can edit it. The simplest solution would be to retrieve all the rows with all the columns in the list datawindow and then make the freeform datawindow share data with the list datawindow. Easy? Yes. But is it really efficient? Each employee record carries a lot of information and there will be hundreds of such records in the employee table. The user will want to edit just a few of them at any given time. Does it really justify bringing all the complete records to the client? On most networks bandwidth is on a premium. And our future plans to split the application will only aggravate the bandwidth situation, since typical distributed applications cause greater transfer of data across the network. So we'll create a list datawindow, dw_employee_list, such that it retrieves all the records with only the emp_id, emp_fname, emp_lname columns from the employee table. Then we'll create the freeform datawindow that will select all the columns for a given emp_id as the retrieval argument. When we build the window we'll tie up the retrieval of the freeform datawindow with the current selection in the list datawindow. When I advocate this strategy, people sometimes ask me - 'What if the user goes on a clicking spree? Won't that actually increase the number of requests for data?'. I must agree that it will. But in response, I ask them - do you really think that your users have nothing better to do than play with your application? Most users I've seen just want to get their work done and want to get it done without delays.When each row in the table carries a lot of information and there can be potentially a large number of rows, this approach definitely improves the user's perceived performance. I've seen it work well in PowerBuilder applications that access data across a low bandwidth WAN. This approach should also pay off when we go in for Web.PB/HTML clients. Building the NVO Next, we come the most important part of building the NVO 'nvo_employee'. As I said earlier this NVO will encapsulate the business logic as well as the database access logic. The code that handles presentation will need to exchange data and changes to that data with this NVO. Even though this sounds a little complex, with PowerBuilder 6.x this is a breeze cause we will be using the new datawindow state functions. I hope you are familiar with these functions. If not, check out the online help files, the online books. First we need a method to retrieve a list of employees. Here it is - nvo_employee::of_getlist() returns blob Datastore ds This code creates a datastore, assigns it the list datawindow object and then retrieves the datastore. The retrieved data is sent to the caller as a blob from the function GetFullState(). This blob contains all the retrieved rows, item status information as well the definition of the dw_employee_list dataobject. Next, we need a function that will retrieve just a single row from the employee table given the emp_id. This is the data that the user will modify. Hence we will have to come up with some persistence mechanism here. Our persistence object is going to be a datastore 'ids_employee', declared as an instance variable. Whenever the NVO gets a request for a row, we'll retrieve the row and hold it in this datastore. We'll create the datastore in the constructor and destroy it in the destructor. Here's the code from the two events - nvo_employee::constructor ids_employee = Create Datastore nvo_employee::destructor Destroy ids_employee And, here's code for the function that will retrieve a single row into the internal datastore and then pass it to the caller as a blob- nvo_employee::of_remoteretrieve_employee(integer id) returns blob Blob
blb_fullstate
The code for validating salary is in - nvo_employee::of_validateSalary(decimal adec_salary) returns boolean //Check if the salary is between 10,000 and 200,000
Finally, we need a function that will update the changes made by the user to the database. But the user makes the changes in a different datawindow. How will this NVO ever figure out what changes the user made? The datawindow state functions come to the rescue again. The presentation code will obtain the changes made by the user to the freeform datawindow as a blob using the GetChanges() function and pass will it to the NVO. The NVO will just apply those changes to the data in the internal datastore and then simply update it. Here's the function - nvo_employee::of_remoteupdate(blob ablb_changes) returns integer Integer rv
The Presentation Layer We've built the persistence logic and business logic. Now we just have to build the presentation layer that will tie-up everything together. We start off with creating the window 'w_employee'. On this window place the two datawindows and the four command buttons. Don't assign any datawindow objects to the datawindow. Yeah, you read that right. You don't have to do that, because the blob returned by GetFullState() also contains the datawindow object definition. Isn't that simply fabulous?! It allows us to completely encapsulate the database access logic into a single object. In cases where you need only the datawindow object definition without the data, I would still discourage associating the datawindow object at design time. Instead, write another function in the business object that just returns the value for Object.DataWindow.Syntax. The window declares an instance variable called 'invo_emp' for the business object 'nvo_employee' as. Lets go over the various events and functions I've coded in the window and it's controls. w_employe::Open myconn = Create nvo_employee Nothing special here; just instantiated the business object and post the event that will retrieve data into the list datawindow. w_employe::Close Destroy invo_emp w_employe::ue_refresh Blob blb_fullstate This code calls the business object function to provide a list of employees. The blob returned is applied to the list datawindow. Note that first 3 lines are essentially just a replacement of your typical Retrieve() function in a 2-tiered application. The code in the 'rowfocuschanged' event of dw_list retrieves the complete record for the currently selected employee into the freeform datawindow. dw_1::rowfocuschanged If currentrow < 1 Then Return There can be a slight problem here. What if the user changes a record but selects another record without saving? These are the times when the new rowfocuschanged event is really handy. Here's the code for the event and related function that offers the user to save changes- dw_1::rowfocuschanging //If there are unsaved changes check if the user would like to save them w_employee::of_checkChanges() returns boolean //If there are unsaved changes ask the user if he would like to - The code in the buttons is self explanatory - cb_refresh::clicked Parent.event ue_refresh() cb_add::clicked //Save the changes if the user has any unsaved changes cb_delete::clicked //Delete the current row and save immediately cb_save::clicked parent.event ue_save() The ue_save event of the window validates modified rows using the business objects of_validSalary() method. If you have other pre-save validations you can perform them here. The changes made to the datawindow are captured into a blob using GetChanges() and sent to the business object which takes care of updating the database. If dw_employee.AcceptText() = -1 Then Return Testing the monolith application It's a good idea to test well the application we've just built. It's easier to debug and fix a 2-tiered application than a 3-tiered one. An important thing to remember is to make sure that the business objects are well tested and stable. That way you don't have to worry about them when they are deployed on a remote server. The stability of the server depends on the stability of these objects and it is possible that a single rogue object can bring the server crashing down. Its time to split this monolith into server and client components. The next section explains that.
|
Introduction Strategy |
||
Home |