Delphi XE程序设计系列 1-主从架构, 多层到JSON和REST
从桌面开发,主从架构,一直到多层架构,虽然都是广泛被接受的观念和技术,但在信息技术的实作上却从不是开放, 相容的世界。Delphi从桌面开发到主从架构都是使用自己的数据传递格式以及通讯传递架构,到了多层架构虽然使用了Windows平台上的通讯协议,例 如COM/DCOM/COM+,但是在传递的数据格式方面仍然是使用自己的架构,COM/DCOM/COM+也是MS专属的通讯协议,和其它平台上使用的 通讯协议也不一样。当然,不光是Delphi/BCB,大部份的开发工具也是采用类似的方式,那就是都支持桌面开发,主从架构或是多层架构等通用观念的架 构,但使用来传递数据和沟通通讯协议都是封闭的架构。
直到JSON和REST的出现以及Delphi/BCB确定走向原生,跨平台的道路之后,Delphi/BCB 从2010版便开始走向以JSON封装数据,以REST做为通讯架构的方向。因此Delphi/BCB除了仍然支持原有的数据封装格式以及通讯协议之外,也允许开发人员选择使用JSON和REST,使用JSON和REST的好处是除了可以让JSON和REST拥抱最新的信息技术之外,也可以让 Delphi/BCB在不同的平台中使用相同的技术来开发主从架构,分布式多层以及Web应用,也可以更容易的和其它的程序语言,框架和技术整合在一起。
现在让我们重温旧梦一下,看看如何把一个简单的主从架构应用程序转换为使用JSON的架构。
主从架构
下图是一个简单的主从架构的主窗体,
它藉由下图的dbExpress组件从数据库的FishFacts数据表中取得数据,并且使用数据感知组件显示在应用程序的主窗体中。
虽然从BDE到dbExpress都使用专属的格式封装资料,但BDE和dbExpress也可以把数据封装成较开放的XML格式,因此要把上图中TClientDataSet中的数据转换为XML的格式,我们只需要存取它的XMLData特性值即可:
dssmFishFact.cdsFishFact.XMLData;
XMLData特性值会回传以下面格式封装的XML数据:
<?xml version=』1.0″ encoding=』UTF-8″ standalone=』yes』?> <DATAPACKET Version=』2.0″><METADATA><FIELDS><FIELD attrname=』Category』 fieldtype=』string』 WIDTH=』15″/><FIELD fieldname=』Species Name』 attrname=』Species_Name』 fieldtype=』string』 WIDTH=』40″/><FIELD fieldname=』Length (cm)』 attrname=』Length__cm_』 fieldtype=』r8″/><FIELD attrname=』Length_In』 fieldtype=』r8″/><FIELD attrname=』Common_Name』 fieldtype=』string』 WIDTH=』30″/><FIELD attrname=』Notes』 fieldtype=』string』 WIDTH=』50″/><FIELD attrname=』Graphic』 fieldtype=』bin.hex』 SUBTYPE=』Binary』 WIDTH=』1″/><FIELD fieldname=』Species No』 attrname=』Species_No』 fieldtype=』r8″/></FIELDS><PARAMS LCID=』0″/></METADATA><ROWDATA><ROW Category=』Triggerfish』 Species_Name=』Ballistoides conspicillum』 Length__cm_=』50″ Length_In=』19.68503937007874″ Common_Name=』Clown Triggerfish』 Notes=』Also known as the big spotted triggerfish. Inhabi』…
然而BDE/dbExpress虽然能够把数据封装成XML格式,但使用XML封装数据时仍然会因为不同的数据存取使用不同的XML元素来封装资 料,因此在交换数据时仍然会造成许多的困扰,而且使用XML格式封装数据的成本比起JSON来要昂贵许多(XML使用较多元素,较为复杂的规则封装数据所 致)。
因此Delphi/BCB要支持JSON/REST技术,其中一个工作就是必须能够把数据封装成JSON的格式,因此从Delphi/BCB 2010版开始便在VCL和RTL中加入了许多和JSON相关的类别以执行这项工作。到了XE版Delphi/BCB基本上不但能够把数据封装成JSON 的格式,甚至提供了REST的API允许Delphi,BCB和任何支持JSON和REST的客户端和使用Delphi/BCB开发的DataSnap伺 服器整合在一起,下图叙述了Delphi/BCB XE版支持的架构:
OK,现在先让我们看看Delphi/BCB XE如何能够把数据封装成JSON的格式,以及提供解析,处理JSON封包的相关类别。稍后我们再讨论Delphi/BCB XE如何支持REST API的使用和呼叫。
其实要把dbExpress中的数据封装成JSON非常的简单,就以直觉来说,我们只需要一个单向,只读的数据集,这个单向,只读的资料集可以直接从dbExpress的数据集组件建立,接着再一一的从这个单向,只读的数据集中读取数据,根据JSON规则封装即可。
因此在VCL中提供了TDBXDataSetReader这个单向,只读的资料集,它可以从dbExpress数据集组件建立,接着在TDBXJSONTools类别中提供了TableToJSON类别方法,它接受一个TDBXReader对象为第一个参数,第二个参数为要从第一个参数中封装的记录笔数,最后一个参数则代表在TableToJSON执行完毕之后是否需要释放第一个参数对象:
class function TableToJSON(const Value: TDBXReader; const RowCount: Integer; const IsLocalConnection: Boolean): TJSONObject; static;
因此要封装范例FishFacts数据表中的2笔资料为JSON格式,我们就可以使用下面的程序代码来完成这个工作。
在下面的程序代码中我们首先藉由数据模块中的TClientDataSet组件建立TDBXDataSetReader组件,接着呼叫TDBXJSONTools类别的TableToJSON类别方法把数据封装成JSON格式:
procedure TForm1.btnToJSONClick(Sender: TObject);
var
aDBXReader : TDBXReader;
aJSonObj : TJSONObject;
begin
aDBXReader := TDBXDataSetReader.Create(dssmFishFact.cdsFishFact, False);
try
aJSonObj := TDBXJSONTools.TableToJSON(aDBXReader, 2, False);
Memo2.Lines.Text := aJSonObj.ToString;
ParseData(aJSonObj);
finally
aJSonObj.Free;
aDBXReader.Free;
end;
end;
下图是执行
dssmFishFact.cdsFishFact.XMLData;
之后得到的XML格式的结果:
而下图则是藉由TableToJSON转换为JSON格式的结果:
如果我们观察JSON格式的结果可以看到DataSnap是以JSON对象封装数据,而每一个字段则是以JSON数组来封装:
{『table』:[["Category",1,0,0,15,16,0,0,false,false,0,false,false],["Species Name",1,1,0,40,41,0,0,false,false,0,false,false]….
由于使用JSON格式封装数据比较简单而且在解析上也比XML容易,我们可以使用VCL中JSON相关的类别很容易的解析出其中封装的数据。例如下面的ParseData从前面TableToJSON建立的JSON对象中解析其中封装的FishFacts数据表的数据:
procedure TForm1.ParseData(aJSONObj: TJSONObject);
var
iPair : Integer;
aPair : TJSONPair;
begin
for iPair := 0 to aJSONObj.Size – 1 do
begin
aPair := aJSONObj.Get(iPair);
if (aPair.JsonString.ToString <> ‘』Graphic』‘) then
Memo3.Lines.Add(Format(‘%s : %s’, [aPair.JsonString.ToString, aPair.JsonValue.ToString]));
end;
end;
下图即是ParseData执行后的结果:
由于DataSnap可以使用JSON封装数据,因此任何支持JSON的程序语言或是框架都可以处理DataSnap封装的数据,这也代表任何支持JSON的客户端都可以连结到Delphi/BCB XE建立的DataSnap JSON服务器并且呼叫它提供的服务。
OK,现在我们了解了如何使用DataSnap封装数据为JSON格式,因此我们现在可以很容易的把这个传统的主从架构应用程序转换为DataSnap JSON服务器,如此一来我们就提供了如何把传统主从架构架逐渐构转换为分布式JSON架构的可能性。
我们的第一步是把这个主从架构应用程序转换为DataSnap JSON服务器,要如此做我们需要让这个主从架构把数据以JSON的格式输出,以便客户端能够存取,使用。
转换主从架构应用程序为DataSnap JSON服务器
为了输出主从架构应用程序的数据,让我们首先在这个主从架构项目中建立一个Server Module,如下所示。Server Module能够自动把包含它的应用程序的数据或是服务输出给客户端使用。
由于我们现在需要把主从架构应用程序中的数据输出以便让客户端应用程序能够存取,因此我们需要把原本主从架构中数据模块中的dbExpress相关组件移动这个建立的Server Module中,接着在原本主从架构的数据模块中加入TDSServer,TDSTCPServerTransport和TDSServerClass组件,如下所示:
接着在TDSServerClass组件的GetClass事件处理函式中设定它的PersistentClass参数为Server Module中的TClientDataSet组件类别:
procedure TdmFishFact.dsscFishFactGetClass(DSServerClass: TDSServerClass;
var PersistentClass: TPersistentClass);
begin
PersistentClass := usmFishFact.TdssmFishFact;
end;
完成了这个简单的工作之后,现在如果我们编译并且再次执行这个主从架构应用程序,那么现在它不但仍然可以做为传统主从架构应用程序来使用,它现在也已经成为了一个DataSnap JSON服务器,现在我们就可以建立一个DataSnap客户端来连结它并且取得FishFacts的资料。
建立Delphi DataSnap客户端
建立一个VCL Form应用程序项目,在主窗体中加入如下的组件:
要连结前面的范例DataSnap服务器,我们只需要加入TSQLConnection组件,并且设定它的特性如下:
特性 |
特性值 |
Driver |
Datasnap |
加入一个TDSProviderConnection组件,设定它的特性值如下:
特性 |
特性值 |
SQLConnection |
SQLConnection1 |
ServerClassName |
TdssmFishFact |
Name |
DSPCFishFact |
加入一个TClientDataSet组件,设定它的特性值如下:
特性 |
特性值 |
RemoteServer |
DSPCFishFact |
ProviderName |
dspFishFact |
在上面的设定中关键的两个设定是TDSProviderConnection组件的ServerClassName特性值必须设定为DataSnap服务器中Server Module的类别名称,以及TClientDataSet组件的ProviderName必须设定为Server Module中的TDataSetProvider组件。而在这个范例DataSnap服务器中的Server Module的类别名称就是TdssmFishFact,而Server Module中的TDataSetProvider组件名称则是dspFishFact。
下图就是设定TDSProviderConnection组件画面:
而下面则是设定TClientDataSet组件的对象检视器:
设定好了之后只要再连结相关的数据感知组件就可以完成客户端应用程序了。
现在如果我们执行范例主从架构应用程序兼DataSnap服务器,再执行DataSnap客户端应用程序,那么我们可以看到类似下面的画面:
上图中范例主从架构应用程序兼DataSnap服务器执行时既是传统的主从架构,也是DataSnap服务器,因此右下方的DataSnap客户端应用程序执行之后才能够从这个主从架构应用程序兼DataSnap服务器取得FishFacts数据。
如何? 了解了dbExpress/DataSnap如何使用JSON封装数据的原理之后我们就可以容易的把它转换为DataSnap服务器。读者可以使用类似的方式在保留主从架构架构的同时又逐渐的把主从架构转换为DataSnap的分布式JSON架构。
我们下次再谈谈Delphi如何支持REST API,如此一来我们就可以让其它的JSON客户端连结并且使用Delphi的DataSnap JSON服务器提供的服务,再见了。