Creating a Data Abstract Server Utilizing Hydra Plugins

Forward

RemObjects Software makes three wonderful products that play extremely well together: RemObjects SDK, Data Abstract, and Hydra. Using all three of these products together allows a developer to create cross platform web services that utilize plugins to separate functionality and logic and utilize state-of-the-art multi-database support on the back-end.

However, it is not a simple task to pull all three of these technologies together into a single coherent project. Many of the wizards found within the individual products (such as the new Data Abstract server wizard) do not work when you want to separate your service into plugins with Hydra. There are also many visual components server side for RemObjects SDK and Data Abstract, and it can be confusing to a developer which components should be in each plugin and which components should be shared between plugin modules (and how to accomplish that sharing).

This article aims to lay down the basic framework for building a RemObjects SDK web service that utilizes Data Abstract for data access and also breaks services up into individual plugin modules. This is a fairly advanced article. You should already be familiar with how to create a RemObjects SDK server, a Data Abstract server, and be familiar with how Hydra functions.

Goals

  • Create a RemObjects SDK powered web service that serves Northwind data
  • The server should utilize Data Abstract for data delivery and updating
  • The server should be broken down into logical plugin modules
  • The plugins should share common objects, such as session, connection, and driver managers

Creating the RemObjects SDK Service Project

First things first – go ahead and fire up Delphi. In this article we’ll be using Delphi 2007, but the principles should apply to other versions of Delphi. Click File > New > Other. Select RemObjects SDK, then VCL Standalone, and click OK. In the New RemObjects SDK Server dialog, set the Project Folder to C:\Hydra Northwind\Server\ and set the Project Name to NorthwindServer. Also, uncheck the option to “Also create a matching client application”, as this would create a RemObjects SDK client rather than a Data Abstract client.

New RemObjects SDK Server

Already you begin to see the first problem for a developer who may be new to combining these three technologies – we couldn’t start with the New Data Abstract Server wizard as our server won’t really be a Data Abstract server – instead the plugins will serve Data Abstract data. This also means we don’t automatically get a Data Abstract client generated for us either.

Click the RemObjects SDK menu in the Delphi menu bar, and click Edit Service Library. Rename the library to NorthwindLibrary. This server will only load modules and provide an interface to services defined in our plugins, so delete the NewService service.

Service Builder

Close Service Builder.

As you know if you are familiar with Hydra or building plugins in Delphi, the plugins and hosts that share VCL objects must be built with runtime packages – specifically the runtime packages for any of the objects shared between plugins and the host. Click the Hydra menu in the Delphi menu bar and click Hydra Package Settings. The server itself will only be sharing VCL, RTL, Hydra, and RemObjects SDK objects, so select Build with Hydra and RemObjects SDK Core Packages and click OK.

Double-click fServerForm.pas and display the design surface. Add a THYModuleManager, renaming the component to HYModuleManager. Throughout the article, we will be renaming all of the components we add, removing the number at the end. Instead of repeating this throughout, just rename each component added, giving it a nicer name. Add a THYRODLReader (renaming it), and assign the ModuleManager property to our HYModuleManager. Also point the ROMessage’s RODLReader property to our HYRODLReader.

This is the basic setup of visual components for a Hydra-aware RemObjects server. The RemObjects message component is hooked up to the THYRODLReader, as is the Hydra module manager. This allows the Hydra plugins that are RemObjects SDK Remote Service Plugins to have their RODL’s merged into the main service’s RODL, and allows the server to provide access to services defined within those plugins.

Now let’s write some code. First, add a FormCreate event handler for the server form, and add the following code:

//this is all that is needed for RemObjects Remote Service Plugins 
HYModuleManager.LoadModules('Plugins\*.dll');

This code causes the module manager to load all of the modules in the Plugins folder. RemObjects SDK Remote Service Plugins are automatically instantiated by the framework, so this is all you have to do to provide access to services defined in the plugins.

Now, add a FormDestroy event handler and add the following code:

//unload modules 
HYModuleManager.UnloadModules;

While this is all that is needed to facilitate RemObjects SDK Remote Service Plugins, if we plan on hosting other types of plugins (and we do), we need a bit more code. Declare a TInterfaceList called FPluginList as a private field on your server form, and add the following code:

procedure TServerForm.FormCreate(Sender: TObject);
begin
  FPluginList := TInterfaceList.Create;
  ...
end;

procedure TServerForm.FormDestroy(Sender: TObject);
begin
  //release plugin instances
  FreeAndNil(FPluginList);
  ...
end;

Finally, add uHYIntf and uHYPluginFactories to the implementation uses clause and add an event handler for the module manager’s OnAfterLoadModule:

  
{ this is needed so that we also instantiate non-RemObjects Remote Service Plugins, like Non Visual Plugins
  such as TCommonPlugin found in hpCommonPlugin.pas}
procedure TServerForm.HYModuleManagerAfterLoadModule(Sender: THYModuleManager; aModule: THYModule);
var
  Descriptor: THYPluginDescriptor;
  Instance: IInterface;
  I: Integer;
begin
  for I := 0 to aModule.ModuleController.FactoryCount - 1 do
  begin
    Descriptor := aModule.ModuleController.Factories[I].Descriptor;
    if Descriptor.CheckPluginInterface(IHYPlugin) then
    begin
      HYModuleManager.CreateInstance(Descriptor.Name, Instance);
      //add our plugin interface to our interface list to keep it in scope
      FPluginList.Add(Instance)
    end;
  end;
end;

And that’s it for code in the server. The above event handler goes through the plugins contained in each module (DLL) that is loaded, querying to see if the plugin supports IHYPlugin (one of the base plugin types). If so, the plugin is instantiated and added to our interface list to keep it in scope. All in all, fairly simple. As a last step, click Project and then Build.

Creating the Common Plugin Module

Now we’re going to create a plugin module that hosts common RemObjects SDK and Data Abstract objects, such as session, connection, and driver managers. We will be able to access these objects from our other Hydra plugins using both the IHYHost interface and a couple of simple custom interfaces.

Right click on our ProjectGroup1 and click Add New Project. Select RemObjects Hydra, then Plugin Module, and click OK. For a project folder, specify C:\Hydra Northwind\Server\Plugins\Common\, and for the name of the project specify Common.

 New Hydra Module

Click Next, leaving the defaults for the module controller (CommonController), and click Next again. Finally, click Finish. This has created your Hydra module (the actual project which will compile to a DLL library), and the New Hydra Plugin Wizard will be shown.

Click Next, select Non-Visual Hydra Plugin, and click Next. For the plugin name specify CommonPlugin and click Next. Finally, click Finish. Because this module will be sharing RemObjects and Data Abstract objects as well as Hydra objects, first select Build with Hydra and RemObjects SDK Core Packages to automatically populate the text box. Now, click Build with Custom Package List and add the Data Abstract core package for your version of Delphi. In my case, it’s DataAbstract_Core_D11.

 Hydra Packages

Click OK.

Click Save All, saving the plugin as hpCommonPlugin.pas and saving the project group as NorthwindProjectGroup. Finally, click Project and then Options. Click Directories/Conditionals, and specify “..\” for the Output directory. Click OK.

Now we’ll add the common components this plugin will be hosting. Add a TDADriverManager and TDAADODriver (renaming both). Add a TDAConnectionManager, pointing the DriverManager property at DADriverManager. Finally, add a TROInMemorySessionManager.

Now we’ll define some simple interfaces for exposing both the connection manager and session manager. Click File > New > Unit. Click Save All, and save the unit as uCommonInterfaces.pas. Add the following interface declarations to uCommonInterfaces.pas:

interface

uses uDAClasses, uROSessions;

type
  IConnectionProvider = interface(IInterface)
  ['{0F4C0953-0478-44DA-BED0-8FCD75ABAD55}']
    function GetConnectionManager: TDAConnectionManager;
    property ConnectionManager: TDAConnectionManager read GetConnectionManager;
  end;
  
  ISessionProvider = interface(IInterface)
  ['{0C5E6182-1252-44B1-B5A1-F209637B1583}']
    function GetSessionManager: TROCustomSessionManager;
    property SessionManager: TROCustomSessionManager read GetSessionManager;
  end;

Double click hpCommonPlugin.pas and add uCommonInterfaces to the interface uses clause. Alter the declaration of TCommonPlugin as shown below, so that it implements IConnectionProvider and ISessionProvider:

  TCommonPlugin = class(THYNonVisualPlugin, ISessionProvider, IConnectionProvider)
  ...
  private
    function GetSessionManager: TROCustomSessionManager;
    function GetConnectionManager: TDAConnectionManager;
  end;

And implement the methods as shown here:

//implementing IConnectionProvider
function TCommonPlugin.GetConnectionManager: TDAConnectionManager;
begin
  Result := DAConnectionManager;
end;

//implementing ISessionProvider
function TCommonPlugin.GetSessionManager: TROCustomSessionManager;
begin
  Result := ROInMemorySessionManager;
end;

We’ve now created a new non-visual plugin that implements a couple of simple custom interfaces. As a final step, click Project and then Build. If you look at the code we added for OnAfterLoadModule when creating our server, that code will instantiate the non-visual plugin we defined and keep an instance of it in the plugin list. We’ll be able to query against the instantiated plugins of our host below, finding plugins that support our customer interfaces and using those to obtain an instance of our common objects.

Creating the Customers Plugin Module

Now we’ll create our first module that exposes some data from Northwind using Data Abstract. Right click our NorthwindProjectGroup and click Add New Project. Select RemObjects Hydra, and then Plugin Module, and click OK. For the project folder, specify C:\Hydra Northwind\Server\Plugins\Customers and for the project name, specify Customers. Click Next. Leave the default CustomersController for the controller name and click Next, followed by Finish. Click Next, and select New RemObjects SDK Remote Service Plugin, as this plugin will expose data through a RemObjects SDK service. Click Next, followed by Finish.

Since this module, like the Common.dll module, shares Hydra, RemObjects, and Data Abstract objects with other modules, we first select Build with Hydra and RemObjects SDK Core Packages, then click Build with Custom Package List, adding the Data Abstract package name to the end. Click OK, and click Yes to launch Service Builder.

Now we will go through the manual process of creating a Data Abstract server within our Hydra plugin. These steps are normally taken care of by the New Data Abstract Server wizard if your project does not utilize Hydra.

Change the name of NewLibrary to NorthwindLibrary. Click Edit, Use Existing RODL, and then Data Abstract. Click the button on the toolbar with the caption Service to create a new service definition. Change the name to CustomersService, and for the Ancestor specify DataAbstractService from the drop down. Close Service Builder. Click Project and then Options. Click Directories/Conditionals, and specify “..\” for the Output directory. Click OK. Finally, click Project and Build to have RemObjects generate our service code and build the project.

Double click CustomersService_Impl.pas. Add a TDAConnectionManager, changing its name to DesigntimeConnectionManager. Add a TDASchema, changing the name to CustomersSchema and pointing the ConnectionManager property at the DesigntimeConnectionManager. Add a TDABin2DataStreamer (renaming it). Now adjust the properties on your data module, setting the ServiceSchema to CustomersSchema and the ServiceDataStreamer to DABin2DataStreamer. Finally, double-click the CustomersSchema component to launch Schema Modeler.

In the Connections pane, add an ADO connection configured to connect to Northwind. In the Data Explorer pane, expand Tables and drag the Customers table to the Data Tables pane, clicking Create New Data Table on the resulting popup menu.

Schema Modeler

Close Schema Modeler.

Right click Customers.dll and click Add. Browse to C:\Hydra Northwind\Server\Plugins\Common and double click uCommonInterfaces.pas. Double click CustomersService_Impl.pas and create an OnCreate event handler, adding uCommonInterfaces and hcCustomersController to the implementation uses clause and adding the following code:

procedure TCustomersService.DataAbstractServiceCreate(Sender: TObject);
begin
  AssignPropertiesFromHost;
end;

{ the DA connection manager and RO session manager should be shared throughout the Hydra plugins, so we access them from a common plugin module, Common.dll }
procedure TCustomersService.AssignPropertiesFromHost;
var
  I: Integer;
  ConnectionProvider: IConnectionProvider;
  SessionProvider: ISessionProvider;
begin
  CustomersSchema.ConnectionManager := nil;
  SessionProvider := nil;
  for I := 0 to CustomersController.Host.InstanceCount - 1 do
  begin
    if Supports(CustomersController.Host.Instances[I], IConnectionProvider, ConnectionProvider) then
      CustomersSchema.ConnectionManager := ConnectionProvider.ConnectionManager;
    if Supports(CustomersController.Host.Instances[I], ISessionProvider, SessionProvider) then
      SessionManager := SessionProvider.SessionManager;
    if Assigned(CustomersSchema.ConnectionManager) and Assigned(SessionProvider) then
      Break;
  end;
end;

This code simply iterates through the plugin instances in our host, checking to see if they support our custom interfaces. If they do, we use those interfaces to access our shared RemObjects SDK and Data Abstract objects.

As a final step, we’re going to take the connection string we just defined in Schema Modeler and copy that back to the connection manager that will be used at runtime. Double click the DesigntimeConnectionManager component and select what should be the only connection in the list. Copy the connection string out of the Object Inspector. Open hpCommonPlugin.pas from the Common.dll project. Double-click the DAConnectionManager on the design surface, and add a single connection. Paste in the connection string from the clipboard and set Default to True. Finally, build Common.dll and Customers.dll

Creating the Employees Plugin Module

As an exercise for you, now create an Employees.dll module. Simply follow all of the steps outlined for creating a Customers.dll module, only we want to expose data from the Employees table in Northwind.

Creating the Client Application

Now, we’ll tie this all up by creating what is actually a basic Data Abstract client application.

Right click NorthwindServer.exe and click Build All From Here. Click the RemObjects SDK menu item in the Delphi menu bar and click Launch Server Executable. If the Windows Firewall alerts you, allow your new server access.

Now, right click NorthwindProjectGroup and click Add New Project. Select Delphi Projects, then VCL Forms application, and click OK. Click Save All, saving the main form as fMain.pas in the folder C:\Hydra Northwind\Client. Save the project as NorthwindClient in the same folder.

Now we’ll add the usual RemObjects SDK and Data Abstract components. Again, this is handled automatically for you if you create a Data Abstract server using the wizard. Add a TROBinMessage and a TROWinInetHTTPChannel, setting the TargetURL on the channel to http://127.0.0.1:8099/BIN (where our server should currently be listening). Add a TRORemoteService, changing the name to CustomersService, and hooking up both the Channel and Message properties to the existing ROBinMessage and ROWinInetHTTPChannel. Select CustomersService from the RemoteService drop down (this should populate automatically by querying your running server). Now add another TRORemoteService, setting the same settings but for the EmployeesService rather than CustomersService.

For the Data Abstract components, add a TDABin2DataStreamer. Add a TDARemoteDataAdapter, pointing the DataStreamer property at the DABin2DataStreamer and the RemoteService property at the CustomersService component, naming it CustomersDataAdapter. Add another TDARemoteAdapter, setting this one up for employees. Now, right click your CustomersDataAdapter component and click Create Data Tables, clicking Create on the Select Data Tables dialog. Do the same for the EmployeesDataAdapter.

You should now have all of the components needed to start working with your data as you normally would with Delphi data-aware controls. I’ll leave it up to you to create a UI using TDBGrids and TButtons. You can now use code such as:

tbl_Customers.Open;
tbl_Customers.Close;
tbl_Customers.ApplyUpdates;

The attached projects include a fully functional Delphi client.

Northwind Client

If you are having trouble, some common mistakes include:

  • Not setting the output directory on your modules – you should have three DLL’s in your Plugins\ folder
  • Not building with the right runtime packages – the server EXE should be built against RTL, VCL, Hydra, and RemObjects packages, while the three modules should also be built against the Data Abstract core package
  • Not having the runtime BPL’s available to your server EXE – the RemObjects installers should have already installed these BPL’s to your system path, but on other machines those BPL’s will need to be copied into the Server directory or be present on the system path
  • Not copying the connection string back to your Common.dll connection manager or not setting the Default property – this is the connection manager used at runtime and must have a proper connection defined

Wrapping it Up

Well I’ll admit, it’s not the smallest number of steps to get to where we are. But you should now have a fully working framework for creating Data Abstract Hydra modules to extend your RemObjects SDK web service. We didn’t implement any session management (that is covered in other articles), but our session manager is shared between services, so that shouldn’t be a problem. This gives you a lot of flexibility and power to mix and match Hydra plugin modules with a full multi-database framework on the back-end. In a future article I’ll discuss how to share structures and arrays declared in one RemObjects SDK Remote Service Module with other Remote Service Modules using custom packages, and how to call one RemObjects SDK Remote Service method from another Remote Service plugin by using Hydra.

6 thoughts on “Creating a Data Abstract Server Utilizing Hydra Plugins

  1. Pingback: A Few New Articles « Development Technobabble

  2. Ron

    Wow, great article. Far more ambitious than I’ve ever been in a blog post. 🙂

    As any FYI (perhaps you noticed), but some of the code is getting cut off in some of the segments.

    Reply
    1. nwoolls Post author

      I’m still looking for a good solution for posting code on a hosted WordPress site. Also, these articles may be hosted on the RemObjects wiki sooner or later (they’re very busy folks), after which I’ll probably just point these links there. Thanks for the feedback 🙂

      Reply
    1. nwoolls Post author

      Unfortunately it looks like (as with the rest of the solutions I’ve found) this requires hosting the blog myself (right now I let wordpress host it). However I did find some links that use the SyntaxHighlighter plugin you linked to convert code to HTML, which I may be able to use. Thanks!

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s