In NexusDB communication between Client and Server is facilitated using the Transport components. These Transports implement a bidirectional, multi-thread aware, message exchange mechanism.
For more information and in-depth articles about Nexus Remoting please read Thorsten's Blog.
On top of this NexusDB implements a low-level RPC framework. This framework is composed of a remote proxy (sometimes referred to as a client stub) in form of TnxRemoteServerEngine and a server stub in form of TnxServerCommandHandler. It is possible to write additional proxies (“Remote Plugins”) and stubs (“Plugin Command Handler”) to build upon that framework.
This low-level RPC implementation has the advantage of having a very low overhead (in runtime performance) which is especially critical for often called procedures which have to transfer a lot of data like RecordGet, RecordInsert and RecordModify. But it also has a number of disadvantages. Adding additional procedures requires a significant amount of coding as the marshalling code in proxy and stub has to be written manually and creating a new plugin is even more involved. The manual static assignment of message numbers opens the possibility for overlapping message numbers between different plugins. Callbacks (Server calling a procedure on the Client as opposed to the normal case of the Client making calls against the Server) while possible, are difficult to implement.
To improve upon this NexusDB 3 will include a high-level, interface based RPC framework which is build on top of the existing RPC implementation. It will be used internally by NexusDB as well as being available to developers creating NexusDB based applications.
I’ll go into more details regarding the capabilities and implementation in future posts. For now, lets have a look at a simple example.
First step is to have server and client agree on a particular interface. As an example we’ll take a simple calculator:
interface
uses
nxllTypes,
nxivTypes;
const
CLSID_Calc : TnxGuid = '{AA97D66D-F493-4267-95B7-48D166E8B43D}';
type
InxCalc = interface(InxInvokable)
['{5642D468-0B63-4ECF-B932-D33C41932528}']
procedure Clear;
procedure Add(const aValue: Extended);
procedure Subtract(const aValue: Extended);
procedure Multiply(const aValue: Extended);
procedure Divide(const aValue: Extended);
function GetResult: Extended;
property Result: Extended
read GetResult;
end;
implementation
initialization
nxInvokeRegistry.RegisterInterface(TypeInfo(InxCalc));
end.
On the server side we need an actual implementation of this interface.
interface
uses
nxrdClass,
CalcIntf;
type
TnxCalc = class(TnxClass, InxCalc)
protected {private}
clcResult: Extended;
protected
{--- InxCalc ---}
procedure Clear;
procedure Add(const aValue: Extended);
procedure Subtract(const aValue: Extended);
procedure Multiply(const aValue: Extended);
procedure Divide(const aValue: Extended);
function GetResult: Extended;
end;
implementation
uses
nxrbTypes;
{ TnxCalc }
procedure TnxCalc.Add(const aValue: Extended);
begin
clcResult := clcResult + aValue;
end;
procedure TnxCalc.Clear;
begin
clcResult := 0;
end;
procedure TnxCalc.Divide(const aValue: Extended);
begin
clcResult := clcResult / aValue;
end;
function TnxCalc.GetResult: Extended;
begin
Result := clcResult;
end;
procedure TnxCalc.Multiply(const aValue: Extended);
begin
clcResult := clcResult * aValue;
end;
procedure TnxCalc.Subtract(const aValue: Extended);
begin
clcResult := clcResult - aValue;
end;
var
Control : InxClassFactoryControl;
initialization
TnxClassFactory.RegisterClass(CLSID_Calc, TnxCalc, Control);
end.
Once this is in place, getting a interface reference to a newly created instance of this object from the client is very simple:
Calc: InxCalc;
begin
if RemotingClient.CreateInstance(nil, CLSID_Calc, InxCalc, Calc) <> S_OK then
exit;
Calc.Add(1234.56);
Calc.Multiply(7);
Calc.Divide(8);
Calc.Subtract(9);
WriteLn(Calc.Result);
end;
There is no need to manually write any code for the proxy or stub, no need to define the used interface(s) in any special interface description language or using a type library editor or schema builder or similar software and no need to generate any code before compiling your application.
The above code is all that's required to to define the interface, implement the server and call it from a client.