delphi面向服务开发解决方案
1)服务接口设计
基于openapi3进行接口设计。面向服务设计的接口,支持跨平台和跨语言,支持任何终端设备。
以《商品资料》资源为例。
unit server.resources.goods; /// <author>cxg 2022-6-8</author> interface uses System.SysUtils, WiRL.Core.Registry, WiRL.Core.Attributes, WiRL.http.Accept.MediaType; type [Path('goods')] TGoods = class [post, path('/select/{dbid}'), Produces(TMediaType.APPLICATION_JSON)] function select(const [PathParam('dbid')] dbid: string): string; [post, path('/insert/{dbid}'), Produces(TMediaType.APPLICATION_JSON)] function insert(const [PathParam('dbid')] dbid: string; const [BodyParam] body: TBytes): string; [post, path('/update/{dbid}/{goodsid}'), Produces(TMediaType.APPLICATION_JSON)] function update(const [PathParam('dbid')] dbid: string; const [PathParam('goodsid')] goodsid: string; const [BodyParam] body: TBytes): string; [post, path('/delete/{dbid}/{goodsid}'), Produces(TMediaType.APPLICATION_JSON)] function delete(const [PathParam('dbid')] dbid: string; const [PathParam('goodsid')] goodsid: string): string; end; implementation { TGoods } function TGoods.delete(const dbid, goodsid: string): string; begin end; function TGoods.insert(const dbid: string; const body: TBytes): string; begin end; function TGoods.select(const dbid: string): string; begin end; function TGoods.update(const dbid, goodsid: string; const body: TBytes): string; begin end; initialization TWiRLResourceRegistry.Instance.RegisterResource<TGoods>; end.
从上面的《商品资料》接口可以看出,接口设计完全不依赖框架,即使是不懂技术的产品经理也能设计接口。
在云服务器上部署《服务接口中间件》
客户端浏览器打开:http://42.193.160.160:8080/rest/app/openapi/
即可以在浏览器里面在线查看设计好的服务接口。
2)中间件实现《商品资料》业务逻辑
接口设计好以后,就开始实现《商品资料》的业务逻辑。
首先实现《商品资料》的data-model
//protobuf模板文件 syntax="proto3"; package goods; //商品资料 message Goods { string goodsid = 1; //商品编号 string goodsname = 2; //商品名称 double price = 3; //价格 } //商品资料数组 message GoodsArr { repeated Goods Goodss = 1; } //查询条件 message GoodsQuery { string goodsid = 1; //商品编号 string goodsname = 2; //商品名称 }
用google protobuf实现data-model,目的是为了支持跨语言。目前所有的主流语言都提供工具,根据 .proto模型文件,自动生成本语言的代码。
运行codegen.bat,根据商品资料model文件goods.proto,自动生成对应的PASCAL code。
自动生成的PASCAL商品资料 model
{ Unit pbGoodsMessages.pas } { Generated from goods.proto } { Package Goods } unit pbGoodsMessages; interface uses Grijjy.ProtocolBuffers, SysUtils; { TGoodsRecord } type TGoodsRecord = record [Serialize(1)] Goodsid : String; [Serialize(2)] Goodsname : String; [Serialize(3)] Price : Double; end; { TDynArrayGoodsRecord } type TDynArrayGoodsRecord = array of TGoodsRecord; { TGoodsArrRecord } type TGoodsArrRecord = record [Serialize(1)] Goodss : TDynArrayGoodsRecord; end; { TGoodsQueryRecord } type TGoodsQueryRecord = record [Serialize(1)] Goodsid : String; [Serialize(2)] Goodsname : String; end; implementation end.
中间件编写业务逻辑,以商品资料查询为例,下面是json序列
function goodsQry(url: string; body: rawbytestring): string; var db: tdb; pool: tdbpool; arr: tarray<string>; serial: TJsonSerializer; sp: TGoodsrecord; sps: TDynArrayGoodsRecord; i: Integer; err: TResRecord; begin serial := TJsonSerializer.Create; try try arr := url.Split(['/']); pool := GetDBPool(arr[4]); db := pool.Lock; db.qry.Close; db.qry.SQL.Clear; db.qry.SQL.Text := 'select * from tgoods'; db.qry.Open; SetLength(sps, db.qry.RecordCount); db.qry.First; i := 0; while not db.qry.Eof do begin sp.goodsid := db.qry.FieldByName('goodsid').AsString; sp.goodsname := db.qry.FieldByName('goodsname').AsString; sps[i] := sp; inc(i); db.qry.Next; end; Result := serial.Serialize<TDynArrayGoodsRecord>(sps); except on E: Exception do begin err.ok := False; err.err := e.Message; Result := serial.Serialize<TResRecord>(err); end; end; finally pool.Unlock(db); serial.Free; end; end;
下面是protobuf序列
function goodsQry(url: string; body: tbytes): tbytes; var db: tdb; pool: tdbpool; serial: TgoProtocolBuffer; arr: tarray<string>; dw: TUnitsRecord; sp: TGoodsRecord; sps: pbGoodsMessages.TGoodsArrRecord; i: integer; err: TresRecord; begin serial := TgoProtocolBuffer.Create; try try arr := url.Split(['/']); pool := GetDBPool(arr[4]); db := pool.Lock; db.qry.Close; db.qry.SQL.Clear; db.qry.SQL.Text := 'select * from tgoods'; db.qry.Open; SetLength(sps.Goodss, db.qry.RecordCount); db.qry.First; i := 0; while not db.qry.Eof do begin sps.Goodss[i].Goodsid := db.qry.FieldByName('goodsid').AsString; sps.Goodss[i].Goodsname := db.qry.FieldByName('goodsname').AsString; inc(i); db.qry.Next; end; Result := serial.Serialize<pbGoodsMessages.TGoodsArrRecord>(sps); except on E: Exception do begin err.ok := False; err.err := e.Message; Result := serial.Serialize<TresRecord>(err); end; end; finally pool.Unlock(db); serial.Free; end; end;
客户端查询
json查询
procedure TForm1.Button1Click(Sender: TObject); //查询 json begin var t: TTablesRecord := TRest.qryJson<TTablesRecord>('/rest/tables/qry/1'); //计量单位 FDMemTable1.EmptyDataSet; FDMemTable1.DisableControls; for var dw: TUnitsRecord in t.Unitss do FDMemTable1.AppendRecord([dw.Unitid, dw.Unitname]); FDMemTable1.First; FDMemTable1.EnableControls; //商品资料 ClientDataSet1.EmptyDataSet; ClientDataSet1.DisableControls; for var sp: TGoodsRecord in t.Goodss do ClientDataSet1.AppendRecord([sp.Goodsid, sp.Goodsname]); ClientDataSet1.First; ClientDataSet1.EnableControls; end;
google protobuf查询
procedure TForm1.Button2Click(Sender: TObject); //查询 protobuf begin var t: TTablesRecord := TRest.qryPB<TTablesRecord>('/protobuf/tables/qry/1'); //计量单位 FDMemTable1.EmptyDataSet; FDMemTable1.DisableControls; for var dw: TUnitsRecord in t.Unitss do FDMemTable1.AppendRecord([dw.Unitid, dw.Unitname]); FDMemTable1.First; FDMemTable1.EnableControls; //商品资料 ClientDataSet1.EmptyDataSet; ClientDataSet1.DisableControls; for var sp: TGoodsRecord in t.Goodss do ClientDataSet1.AppendRecord([sp.Goodsid, sp.Goodsname]); ClientDataSet1.First; ClientDataSet1.EnableControls; end;