Podemos utilizar diretamente uma instância de TMicroServiceClient
, passar um XML como parâmetro e obter a resposta. Mas isso não seria o ideal. Devemos ter Classes de Negócio, com suas próprias Regras e entrada/saída de informações.
Vamos codificar uma Classe de Negócio, que construa seu próprio XML e utilize, internamente, uma instância do Client.
Introdução
No artigo anterior eu escrevi sobre o Módulo MicroService e Localização de Serviços.
Nesse artigo você irá aprender a codificar uma Classe de Negócio que irá consumir um Microservice, utilizando o Módulo já apresentado ateriormente.
Também aprenderá como transformar XML em dados tabulares para apresentá-los numa Grid, por exemplo.
Classe TXMLFactory
Uma instância de TXMLFactory
é utilizada no Client, especificamente no método TMicroServiceClient.Send
, que não foi abordada no artigo anterior.
É uma Classe simples mas importante, utilizada tanto para montar tanto o XML de envio como o de retorno.
O Delphi 7 não trabalha com encode UTF-8
por padrão mas os Microservices em Java trabalham no formato UTF-8
. Então o Client deve fazer todas as solicitações nesse formato.
O resultado, no entanto, é convertido para ISO-8859-1
— que é o formato que estou utilizando no Delphi 7 — antes dos dados serem disponibilizados para o resto da aplicação.
A Classe TXMLFactory
facilita o envio e retorno.
type
TXMLFactory = class(TInterfacedObject, IXMLFactory)
private
FEncoding: string;
FStream: IDataStream;
public
constructor Create(const Encoding: string; Stream: IDataStream); reintroduce;
class function New(const Encoding: string; Stream: IDataStream): IXMLFactory; overload;
class function New(const Encoding: string): IXMLFactory; overload;
class function New: IXMLFactory; overload;
function Document: IXMLDocument;
end;
{ TXMLFactory }
constructor TXMLFactory.Create(const Encoding: string; Stream: IDataStream);
begin
inherited Create;
FEncoding := Encoding;
FStream := Stream;
end;
class function TXMLFactory.New(const Encoding: string;
Stream: IDataStream): IXMLFactory;
begin
Result := Create(Encoding, Stream);
end;
class function TXMLFactory.New(const Encoding: string): IXMLFactory;
begin
Result := New(Encoding, TDataStream.New);
end;
class function TXMLFactory.New: IXMLFactory;
begin
Result := New('UTF-8');
end;
function TXMLFactory.Document: IXMLDocument;
var
Buf: TMemoryStream;
begin
Result := TXMLDocument.Create(nil);
Buf := TMemoryStream.Create;
try
Result.Options := [];
Result.Active := True;
Result.Version := '1.0';
if FStream.Size > 0 then
begin
FStream.Save(Buf);
Result.LoadFromStream(Buf);
end;
Result.Encoding := FEncoding;
finally
Buf.Free;
end;
end;
Consumindo um Serviço
Nesse projeto eu defini que todo consumo de qualquer Microservice será feito através de Classes de Negócio. Então poderei ter o mesmo número de Classes correspondende ao número de Microservices ou até mais, pois uma Classe de Negócio poderia fazer uso de mais de um Microservice.
Iremos implementar uma Classe de Negócio simples e hipotética, visto que não poderia disponibilizar o código real do sistema quando se trata do negócio da empresa.
A Classe proposta é a TFaturasService
.
A instância dessa Classe irá receber um parâmetro do tipo IDataUId
— uma classe que encapsula um GUID — para retornar os dados de uma Fatura.
Segue sua implementação abaixo:
type
TFaturasService = class(TInterfacedObject, IMicroServiceAction)
private
FUId: IDataUId;
public
constructor Create(UId: IDataUId); reintroduce;
class function New(UId: IDataUId): IMicroServiceAction;
function Act: IMicroServiceResponse;
end;
{ TFaturasService }
constructor TFaturasService.Create(UId: IDataUId);
begin
inherited Create;
FUId := UId;
end;
class function TFaturasService.New(
UId: IDataUId): IMicroServiceAction;
begin
Result := Create(UId);
end;
function TFaturasService.Act: IMicroServiceResponse;
function XML: IXMLDocument;
begin
Result := TXMLFactory.New.Document;
with Result.AddChild('Params') do
begin
AddChild('uid').Text := FUId.AsString;
AddChild('active').Text := 'True';
end;
end;
begin
Result :=
TMicroServiceClient.New(
TMicroServiceParams.New(
'financ:faturas-service'
)
.Find
)
.Send(XML)
end;
O código passo-a-passo
Vamos entender o código juntos:
TFaturasService
é uma Classe de Negócio que implementaIMicroServiceAction
(veja aqui);- O Objeto
TXMLFactory
é utilizado para gerar o XML de envio; - O Objeto
TMicroServiceParams
localiza o serviço através do seuname
(único) e traz as informações em forma de parâmetros; - O Objeto
TMicroServiceClient
envia o XML de envio e retorna uma instância deIMicroServiceResponse
. - O objeto retornado contém um
XML: IXMLDocument
que é a resposta do Microservice codificado em Java;
E é isso.
Uma implementação completa de um serviço.
Se você esperava algo mais complexo, sinto decepcioná-lo, pois a implementação é tão simples quanto isso. :)
Utilizando a Classe de Negócio
Uma vez que a Classe de Negócio foi implementada, poderá ser utilizada em muitas partes do sistema sem haver duplicação de código; não precisará montar o mesmo XML em vários lugares; não precisará passar os mesmos parâmetros para o Client, etc.
Então, como utilizar a nova Classe no nosso código?
Aqui está um exemplo:
procedure TForm1.FillData;
begin
TXMLClientDataSetAdapter.New(
FModule.FaturaClient,
TFaturasService.New(FUId).Act.XML
)
.Adapt;
No Delphi 7 utilizamos TClientDataSet
para manter os dados tabulares (linhas e colunas) em memória, exibir numa Grid ou em qualquer outro widget. Mas os dados dos Microservices vem no formato de XML. Esses dados precisam ser convertidos para um formato tabular.
Para isso temos uma outra Classe responsável por fazer essa conversão da forma mais genérica possível.
Essa Classe é a TXMLClientDataSetAdapter
.
Ela recebe 2 parâmetros:
- Uma instância de um
TClientDataSet
, que no exemplo está em umTDataModule
; - Uma instância de
IXMLDocument
, que é obtido através da chamadaAct.XML
;
Então executamos o método Adapt
e pronto, temos um XML convertido em dados tabulares.
Será que o código da Classe TXMLClientDataSetAdapter
é complexo?
Não. É tão simples quanto a implementação da Classe de Negócio.
Veja a implementação do método Adapt
:
function TXMLClientDataSetAdapter.Adapt: IDataAdapter;
var
I: Integer;
Field: TField;
begin
Result := Self;
if not Assigned(FSource) then
Exit;
FDest.DisableControls;
try
while Assigned(FSource) do
begin
FDest.Append;
for I := 0 to FSource.ChildNodes.Count -1 do
begin
with FSource.ChildNodes[I] do
begin
Field := FDest.FindField(NodeName);
if Assigned(Field) and (Text <> '') then
Field.Value := Text;
end;
end;
FSource := FSource.NextSibling;
end;
finally
FDest.First;
FDest.EnableControls;
end;
end;
Ele adapta o XML para o formato tabular e no fim temos uma instância de TClientDataSet
com os dados provenientes do XML.
Será um substituto ao DataSnap?
Eu ainda não terminei essa série, porém já recebi alguns e-mails de alguns leitores perguntando se é possível utilizar essa solução como um substituto ao Delphi DataSnap.
Minha resposta é que o DataSnap tem muito, muito código implementado com inúmeras facilidades para os desenvolvedores. Então não é um substituto — e mesmo se esse fosse o objetivo, estaríamos muito longe de concluí-lo.
É melhor ou pior?
Depende do tipo do projeto.
Nesse projeto a versão do Delphi é a 7. A empresa não quer mais investir no Delphi e vai substituí-lo, aos poucos, por Java ou mesmo C#. Então não haveria motivos em utilizar DataSnap, visto que a linguagem irá mudar no futuro.
Desvantagens by design
É claro que há desvantagens na solução proposta. Sempre há desvantagens.
Uma grande desvantagem é que novas informações não são, necessariamente, visíveis automaticamente quando há alterações nos Microservices.
Por exemplo.
Caso o Microservice acima enviasse uma nova informação chamada “status”, ela não iria aparecer automaticamente numa Grid do Delphi. Esse campo teria que ser incluso, manualmente, no ClientDataSet que fosse receber os dados.
O que eu quero dizer é que a aplicação Delphi não se adapta automaticamente as mudanças dos Microservices. É possível fazer, porém exigiria muito mais tempo de desenvolvimento.
A solução é simples porque estamos utilizando a premissa Convention over configuration.
- Se há dados, então eles estarão no formato XML;
- Os fields de um ClientDataSet devem ter o mesmo nome dos fields que são esperados nos Microservices e vice-versa;
- Utilizamos os mesmos formatos para Data, Hora e Números decimais.
- Etc.
Apesar de não vermos isso como desvantagens, não deixa de ler uma limitação.
No próximo artigo…
Você acabou de ver uma implementação simples, “made in home”, para trabalhar com interoperabilidade entre sistemas distintos utilizando Microservices que nada mais são do que requisições HTTP enviando e recebendo XML.
Finalizaremos essa série no próximo artigo.
Até logo.