TIdHTTPRestClient é um cliente RESTful implementado sobre o componente INDY  TIdHTTP de uso geral.

TIdHTTPRestClient

 

O procedimento “execute” do componente envia uma solicitação ao servidor HTTP e aguarda resposta pela propriedade “content”.

Propriedades

  • BaseURL – servidor e porta de acesso;
  • Method – identifica o método a ser executa no servidor;
  • Resource – URL de acesso;
  • ResourcePreffix – Serviço de acesso no servidor;

 

Informações adicionais de HEADER deve ser enviado através da propriedade do componente “IdHTTP” que expõe o componente TIdHTTP instanciado internamente.

Exemplo de envio de “token” no HEADER:

   IdHTTPRestClient1.IdHTTP.Request.CustomHeaders.AddValue('token','abcdexz');

Se necessário obter informações adicionais é possível obter a partir de documentação do TIdHTTP e pode ser aplicado a propriedade IdHTTPRestClient1.IdHTTP…

Introdução ao OData

Nos textos anteriores foi possível destacar como fazer INSERT, DELETE e UPDATE – cada um individualmente em comandos separados.

Aqui vamos mostrar o METHOD   PATCH. Na especificação padrão do RESTful o PATCH efetua um UPDATE no banco de dados. Qual então a diferença entre PUT e PATCH ? Regra geral no OData, o comando PUT faz update de todas as colunas da tabela, enquanto o PATCH faz igualmente um update, no entanto realiza atualização somente nas colunas indicadas no BODY.method-patch

No MVCBrServer, o PUT e o PATCH possuem as mesmas funcionalidades, ou seja, atualizam somente as colunas indicadas no BODY como já visto no texto que discorremos sobre o METHOD PUT.

Uma variação implementada no MVCBrServer é a possibilidade do comando PATCH executar múltiplas funções e aceitar enviar INSERT, DELETE e UPDATE dentro de um mesmo ARRAY com as linhas de comando.

Para executar múltiplas funcionalidade, cada linha deve ser indicada com uma propriedade “rowstate” com um dos valores: “modified”, “inserted”, “deleted”.

Exemplo:

url: http://localhost:8080/OData/OData.svc/vendas()
body:
[
   { "rowstate":"deleted","data":"2017-03-17","documento":3},
   { "rowstate":"inserted","data":"2017-03-17","documento":3,"cliente":1,"total":10},
   { "rowstate":"modified","data":"2017-03-17","documento":3,"cliente":2,"total":10}
]

 

Este BODY envia 3 comandos no mesmo ARRAY que serão executados na seguinte ordem:

  1. na linha 1 o “rowstate”:”deleted” indica que a linha deve ser processada como um comando DELETE;
  2. na linha 2 o “rowstate”:”inserted” indica que a linha é um INSERT;
  3. na linha 3 o “rowstate”:”modified” deve processar um UPDATE para a linha;

Resposta:
{"@odata.context":"\/OData\/OData.svc\/vendas()","StartsAt":"2017-03-17T20:45:46.541Z",
 "@odata.count":"3","EndsAt":"2017-03-17T20:45:46.559Z"}

Em relação ao controle de transação da operação o controle ocorre no mesmo formato que já tratamos aqui ACID , ou seja – tudo ou nada.

 

Introdução ao OData

O protocolo OData não trata sobre o controle de transação do banco de dados. Esta é na verdade um decisão do server que implementação será feita. Como o OData nasce em um ambiente noSQL com forte presença é fácil imaginar que o controle de transação é uma preocupação mais presente no legado e menos presente nas novas tecnologia.
Aqui um paradigma bom a ser vencido ao longo dos anos – como sair de um ambiente transacional e abandonar o ACID no noSQL…

Aqui no MVCBrServer o banco de transação é o Firebird e a transação faz parte da estrutura de controle. Pensando nisto, quando o cliente solicita um conjunto de linhas em um ARRAY o server faz um STARTTRANSACTION e somente após a conclusão de todas as linhas irá enviar um COMMIT para o servidor.
Caso ocorra algum erro durante o processo um ROLLBACK é enviado para o servidor cancelando todo o lote da transação.

introdução a OData | INSERT | UPDATE

A execução de um UPDATE no banco de dados envolve duas categorias de informações. A primeira delas é uma lista de colunas a atualizar no servidor, na segunda parte de importância é a indicação de quais linhas serão envolvidas na atualização (a WHERE).

Com base na especificação OData para RESTful, o METHOD PUT indica que o cliente deseja fazer uma atualização da tabela. As colunas a serem atualizadas devem ser enviadas no BODY da mensagem ao servidor RESTful. O servidor MVCBrServer esta preparado para receber uma linha simples de colunas/valores, bem como um ARRAY com um conjunto de linhas.

Possibilidades  de formato do BODY para atualizar a(s) linha(s):

  1. Uma linha simples:
      { "id" : "1" ,  "nome":"DESCRICAO TESTE" }
  2. Um ARRAY com uma lista de linhas:
      
          [  { "id": "1",  "nome": "DESCRICAO 1"},
             { "id": "2",  "nome": "DESCRICAO 2"},....
          ] 
    

Se o BODY indica os valores das colunas a serem atualizadas no servidor, próximo passo é tratar a seleção das linhas que iram receber atualização já que em geral queremos atualizar uma linha ou um conjunto delas e raramente desejamos atualizar todas as linhas.
Preferencialmente a definição da WHERE deve ser indicada como parâmetro para o RESOURCE (a tabela ou coleção):

     http://localhost:8080/OData/OData.svc/produtos(id='1')  -> aplica atualiza sobre a linha com ID = "1"
     
     caso o RESOURCE requeira mais de uma coluna - pode-se separa-las por vírgulas

     http://localhost:8080/OData/OData.svc/produtos(id='1',outra='x')  -> aplica atualiza sobre a linha com ID = "1"  AND outra="x"

Caso a coluna não seja indicada, o MVCBrServer irá utilizar a chave indicada no metadata (modelo de dados).

Quando o BODY indica uma lista de linhas em um ARRAY – não é possível indicar a chave nos parâmetros já que cada linha possui uma chave diferente (WHERE). Neste caso o servidor irá utilizar o seu “keyID” do metadata e aplicar o valor de cada linha na WHERE para escolher as linhas que irão receber atualização ou seja, as linhas enviadas no ARRAY devem conter as colunas correspondente a chave do RESOURCE.

Ao caso cabe uma questão – A especificação OData indica que no comando PUT, todas as colunas devem ser enviadas para o servidor. A implementação no MVCBrServer não possui esta exigência – é possível enviar somente as colunas da chave e as colunas a serem atualizadas, ignorando as colunas que não foram enviadas na lista. Nos casos que for necessário enviar um NULL para uma coluna, este desejo deve constar na linha enviada… Ex: {…., “colunaxxx”: NULL, … }

Introdução ao OData | INSERT | DELETE |

O texto anterior mostrei um exemplo de como enviar um INSERT para o servidor usando o METHOD POST padrão RESTful utilizado no MVCBrServer.

Agora vamos olhar como enviar comando DELETE (METHOD DELETE) para o servidor e excluir um linha na tabela.

Há duas possibilidade para excluir registros de uma tabela na estrutura do servidor MVCBrServer. A primeira é enviar uma única linha diretamente pela URI ao servidor indicando o RESOURCE e como parâmetro o identificador padrão da linha – em geral ligado a chave primária:

  • http://localhost:8080/OData/OData.svc/grupos(’08’) – com METHOD DELETE, exclui a(s) linha(s) com chave ’08’ – neste formato o servidor irá utilizar como chave para encontrar a linha a excluir o “keyID” da tabela indicado no metadata – Um cuidado a observar é que neste formato pode ocorrer do metadata se referir a mais de uma linha e neste caso o servidor irá excluir todas as linhas que contenham a mesma informação – exemplo: se for o documento da venda e o RESOURCE for os itens irá excluir todos os itens da venda que contenham o mesmo documento;
  • http://localhost:8080/OData/OData.svc/grupos(grupo=’08’) – neste modelo o servidor irá utilizar a coluna indicada para selecionar as linhas a serem escolhidas o que permite melhor controle pelo “Coder”. Caso seja necessário mais de uma coluna, basta separar por vírgula ex:  …/ItemDaVenda(dcto=’00001′,ordem=1);

Outro formato é o envio no “BODY” com um ARRAY contendo uma lista de linhas a serem excluídas:

  http://localhost:8080/OData/OData.svc/ItemDaVenda

        no BODY enviar o JSONArray:
           [ { "dcto":"00001", "ordem":1},
             { "dcto":"00002", "ordem":1}
              ....
           ]

Introdução ao OData | Fazendo INSERT

Tomando emprestado do padrão RESTful, para enviar um INSERT para o banco de dados com o protocolo OData é preciso formatar uma mensagem com METHOD POST e enviar no BODY da mensagem o JSON correspondente aos dados das colunas a serem inseridas no banco de dados.

 

POST_Cliente

 

No exemplo, o METHOD POST enviou uma linha contendo o registro a persistir no banco de dados. Como a repetição de envios de comandos ao servidor pode se tornar de alto custo, dado ao uso de conectividade de baixa velocidade ou instável – o recomendado é enviar um pacote de dados juntos para que sejam executados no servidor. Para isto, o servidor MVCBrServer prevê a possibilidade de receber um   ARRAY JSON com uma lista de registros a serem inseridos.

Sendo assim, é possível enviar um pacote de dados no seguinte formado:

URL-POST: http://localhost:8080/OData/OData.svc/clientes

[
{"codigo":9999997,
"nome":"Jose da Silva",
"fone":"055-111-12312",
"cidade":"São Paulo"
},

{"codigo":9999998,
"nome":"Jose da Silva8",
"fone":"055-111-12312",
"cidade":"São Paulo"
},

{"codigo":9999999,
"nome":"Jose da Silva9",
"fone":"055-111-12312",
"cidade":"São Paulo"
}
]

Codigo GIT MVCBr

Primeira Parte

FB

 

O que é OData?

É uma especificação para consumir recursos de banco de dados em servidores que implementam RESTful. A solicitação de operação no banco de dados é enviada para o servidor em uma URL e retorna o resultado da consulta do banco de dados.

Através dos métodos de comunicação com o servidor RESTful é estabelecido o tipo de operação a ser executado:

  • GET – Solicita que retorne um conjunto de dados do banco de dados;
  • POST – Envia um INSERT para o banco de dados;
  • PUT – Envia um UDPATE  para o banco de dados – no OData é necessário enviar todas as colunas;
  • PATCH – Envia um UPDATE  parcial – não precisa mandar todas as colunas;
  • DELETE – apaga um registro no banco de dados;

Exemplo de GET:

  1. http://servidor:8080/OData/OData.svc/produtos(1) – Executa uma busca nos registro de produtos e retornar o produto 1;
  2. http://servidor:8080/OData/OData.svc/produtos(1)/clientes – retorna os clientes que compraram o produto;
  3. http://servidor:8080/OData/OData.svc/produtos(1)/clientes(estado=’SP’) – retorna os clientes que compraram o produto 1 e que estão no estado SP;
  4. http://servidor:8080/OData/OData.svc/produtos?$top=10 – retorna os 10 primeiro itens da tabela/coleção produtos;
  5. http://servidor:8080/OData/OData.svc/produtos?$top=10&$skip=20 – salta os 20 primeiros registros e retornar se próximos 10 produtos;
  6. http://servidor:8080/OData/OData.svc/produtos?$select=codigo,nome,unidade – retorna as colunas codigo,nome,undiade;
  7. http://servidor:8080/OData/OData.svc/produtos?$filter=unidade eq ‘KG’ -> retorna os produto que a unidade seja igual a KG;

Notações de filtro:

  • eq – equal        =
  • lt – less than   <
  • le – less equal   <=
  • ne – not equal   <>
  • gt  – great than  >
  • ge  – great equal >=

 

Ver especificação em:   OData.org

Tenho visto muitos videos que falam sobre como aplicar MVC com Delphi e sempre fiquei com a impressão que não é nada fácil. O que não é fácil se torna um desafio – é assim que nasceu MVCBr – Um desafio para elevar a organização do código a outro patamar.

A filosofia nativa vendida pelo fabricando do delphi, basta ver os diversos videos disponíveis na comunidade, é que a ferramenta suporta MVVM, ou seja Model View e View Model – talvez seja este o maior entrave em implementar um MVC.

1) O desafio

Sendo a IDE altamente produtiva é preciso que um projeto MVC não retire aquilo que há de melhor, mesmo assim, encontramos muitos que condenam a condição de “arrastar e soltar” utilizada deliberadamente ao longo dos anos, formando uma legião de arrastadores de componentes para a interface e quando precisa de um pouco mais de profundidade da engenharia de software – foi esquecida.

2) Onde encaixar o Controller (“o bobo da corte”)

O papel o Controller é fazer o meio de campo entre o VIEW e MODEL, o que já chamei antes de “bobo da corte”, ou seja, é o controle que faz a ponte entre os MODELs e as VIEWs. Quando uma VIEW precisa de alguma informação, ela pregunta para o CONTROLLER quem detém este dado – em existindo entrega a VIEW o MODEL que é responsável pelo informação.

3) A View – camanda de apresentação

É a VIEW que se encarrega de fazer a apresentação ao usuário – no caso de desktop o “FORM” – toda VIEW possui um controller associado a ela. Se o aplicativo rodar em um browser, a VIEW é a página de apresentação para o usuário…

Não é responsabilidade da VIEW saber como guardar informações;

4) O Model – onde tudo acontece

É no MODEL que as regras acontecem. Persistência, cálculos, DAO, ORM…

VIEWMODEL

No MVCBr ainda temos a figura do VIEWMODEL – o ViewModel é um artefato de MVVM e não de MVC, no entanto o seu uso pode ser bastante útil para não nos afastarmos muito da filosofia da ferramenta.

O que fazer com o VIEWMODEL ?

Com o ViewModel, pode-se adotar uma estrutura híbrida e delegar ao VIEWMODEL a validação do formulário. Sendo ele ao mesmo tempo um conector direto para o VIEW com funcionalidades de um MODEL, ou seja, ele pode receber validações do FORM ou mesmo guardar alguns estados ligados ao FORM (ou particulares ao VIEW).

 

O MVCBr

No MVCBr, a relação entre CONTROLLER  e  VIEW  é de 1 para  <=1 – Um Controller possui  no máximo  uma  VIEW, em alguns casos talvez nenhuma. Assim toda vez que se cria uma VIEW é preciso construir um controller para a VIEW.

Já o CONTROLLER pode possuir ligações para múltiplos MODELs do projeto. Sendo o MODEL um artefato que possui regras de negócio, um MODEL pode conter as regras de persistência de cliente enquanto outro MODEL possui regras de transação de cartão de crédito separando cada MODEL por finalidade.

Se um MODEL implementar uma NFe, ele dever saber lidar com o acesso ao cadastro de cliente, acessar cadastro de produto …. além de fazer persistência do conjunto de regras de NFe – Muitas vezes um MODEL poderá precisar de ajuda de outro MODEL;

O Assistente do MVCBr

Desafio lançado – código complexo. Precisamos de um assistente para nos ajudar a escrever código. Foi pensando nisto que o Assistente do MVCBr esta apto a criar o projeto com estrutura de MVC, VIEW, MODEL e CONTROLLER para a nossa aplicação;

Todo o projeto tem por base trabalhar com assinaturas publicadas em INTERFACEs visando eliminar ao máximo o acoplamento, implementadas em UNITs em separado dos seus “Object Factory”. Neste contexto, sempre que uma UNIT precisa de um recurso que se encontra em outra UNIT – deverá preferencialmente trocar informações pelas INTERFACEs.

Todo “Object Factory” public o seu THIS, uma espécie de SELF para o “Factory” onde permite facilmente fazer CAST de interfaces usando   “This. AsType<interface>” – claro que o “Factory” precisa conhecer e implementar a tal interface solicitada (ou retornará NIL).

 

Veja o Vídeo:  Criando um Projeto com FireDAC

Códigos:  MVCBr

 

 

#compartilhandoconhecimento #wba10anos
THIS não é um método ou propriedade de classes no DELPHI como ocorre com JAVA.
Emprestando a definição do JAVA-ORACLE temos: “this is a reference to the current object — the object whose method or constructor is being called”…

Acompanhando os artigos do Marcos Douglas B. Santos em seu Blog: Object Pascal Programming por vezes versa sobre implementar uma class function a classes que instâncie e retorne o próprio objeto de preferência por uma INTERFACE.

Bem, a questão nos apresenta quando temos uma interface e precisamos obter a referência ao objeto criado pelo seu construtor. Neste casos, em geral, fazer um CAST da INTERFACE para o OBJETO não é garantia de sucesso.

Depois de lutar muito com o problema minha conclusão é que a melhor solução seria a INTERFACE retornar o próprio objeto criado em seu construtor – o THIS – como definido no JAVA.

Exemplo:
[code lang=”pascal”]

type
TTransporteClass = class;

ITransporte = interface
{…}
function This:TTransporteClass;
end;

TTransporteClass = class(TInterfacedObject, ITransporte)
public
class function New:ITransporte;
function This:TTransporteClass;
end;


// class function para iniciar a instância
class function TTransporteClass.New:ITransporte;
begin
result := TTransporteClass.create;
end;

// function para obter o objeto instanciado
function TTransporteClass.This:TTransporteClass;
begin
result := self;
end;

[/code]

A boa prática logo vai se manifestar com argumento de promover maior acoplamento do código – perfeitamente… neste caso retornar uma classe de nível superior pode contribuir em elevar o acoplamento do código… para isto, vamos trocar o retorno da function THIS:

[code]

IThis = interface
{…}
function This:TObject;
end;

ITransporte = interface
{…}
end;

TTransporteClass = class(TInterfacedObject, ITransporte, IThis)
…..

[/code]

Congelando a janela com TTask.WaitForAll ???

#compartilhandoconhecimento #wba10anos
Depois que publiquei o vídeo Papo sobre POO (TTask e outros), recebi um comentário que me deixou intrigado.

Porquê o a janela principal trava quando executo   TTask.WaitForAll(  ….  );

Fui dar uma olhando como foi implementado o método – observei que é feito uma chamada para uma camada de TEvent que é implementado nas chamadas internas da rotina. Por traz da mecânica com TEvent é feito uso de  WaitForSingleObject – que é uma camada de acesso a biblioteca do windows.

A alteração não é trivial. O primeiro problema é como reescrever o método considerando que o array passado como parametro é um   .. AArray: array of ITask… qualquer deslize no seu uso vai provocar um incremento no contador RefCount da interface e pode levar a perda de controle no autofree do processo…
Para não causar um incremento do RefCount é preciso fazer uso da instrução  [unsafe] o que foi feito através de um “wrapper” para um record marcado para não incrementar o RefCount.

Contornado a questão de referência, o próximo obstáculo é encontrar um mecanismo que permita parar o processamento sem congelar a janela…

Depois de várias tentativas a solução encontrada foi o “infamous” application.processmessage. Esta não é uma boa opção, já que  mantém o processador em atividade,  quando o ideal seria encontrar um modelo que não fizesse uso do processador enquanto esta atualizando a janela principal.

Primeiramente foi criado um Class Helper para o TTask:

 

[code lang=”pascal”]

Type
TTaskHelper = class helper for TTask
private type
TUnsafeTaskEx = record
private
[Unsafe]
// preciso de um record UNSAFE para nao incrementar o RefCount da Interface
FTask: TTask;
public
property Value: TTask read FTask write FTask;
end;
public
class function WaitForAllEx(AArray: Array of ITask;
ATimeOut: int64 = INFINITE): boolean;
end;

[/code]

Versão 1. Implementando o método:

[code lang=”pascal”]
class function TTaskHelper.WaitForAllEx(AArray: array of ITask;
ATimeOut: int64 = INFINITE): boolean;
var
task: TUnsafeTaskEx;
i: integer;
taskInter: TArray<TUnsafeTaskEx>;
completou: boolean;
Canceled, Exceptions: boolean;
begin
Canceled := false;
Exceptions := false;
result := true;
try
for i := low(AArray) to High(AArray) do
begin
task.Value := TTask(AArray[i]);
if task.Value = nil then
raise EArgumentNilException.Create(‘Wait Nil Task’);

completou := task.Value.IsComplete;
if not completou then
begin
taskInter := taskInter + [task];
end
else
begin
if task.Value.HasExceptions then
Exceptions := true
else if task.Value.IsCanceled then
Canceled := true;
end;
end;

try
for task in taskInter do
begin
while not task.Value.IsComplete do
begin
try
TThread.Queue(nil,
procedure
begin
application.ProcessMessages;
end);
finally
end;
end;
if task.Value.IsComplete then
begin
if task.Value.HasExceptions then
Exceptions := true
else if task.Value.IsCanceled then
Canceled := true;
end;
end;
finally
end;
except
result := false;
end;

if (not Exceptions and not Canceled) then
Exit;
if Exceptions or Canceled then
raise EOperationCancelled.Create
(‘One Or More Tasks HasExceptions/Canceled’);

end;

[/code]

Versão 2. Revisando o código para um uso mais eficiente com MsgWaitForMultipleObjectsEx:
[code lang=”pascal”]
class function TTaskHelper.WaitForAllEx(AArray: array of ITask;
ATimeOut: int64 = INFINITE): boolean;
var
FEvent: TEvent;
task: TUnsafeTaskEx;
i: integer;
taskInter: TArray<TUnsafeTaskEx>;
completou: boolean;
Canceled, Exceptions: boolean;
ProcCompleted: TProc<ITask>;
LHandle: THandle;
LStop: TStopwatch;
begin
LStop := TStopwatch.StartNew;
ProcCompleted := procedure(ATask: ITask)
begin
FEvent.SetEvent;
end;

Canceled := false;
Exceptions := false;
result := true;
try
for i := low(AArray) to High(AArray) do
begin
task.Value := TTask(AArray[i]);
if task.Value = nil then
raise EArgumentNilException.Create(‘Wait Nil Task’);

completou := task.Value.IsComplete;
if not completou then
begin
taskInter := taskInter + [task];
end
else
begin
if task.Value.HasExceptions then
Exceptions := true
else if task.Value.IsCanceled then
Canceled := true;
end;
end;

try
FEvent := TEvent.Create();
for task in taskInter do
begin
try
FEvent.ResetEvent;
if LStop.ElapsedMilliseconds > ATimeOut then
break;
LHandle := FEvent.Handle;
task.Value.AddCompleteEvent(ProcCompleted);
while not task.Value.IsComplete do
begin
try
if LStop.ElapsedMilliseconds > ATimeOut then
break;
if MsgWaitForMultipleObjectsEx(1, LHandle,
ATimeOut – LStop.ElapsedMilliseconds, QS_ALLINPUT, 0)
= WAIT_OBJECT_0 + 1 then
application.ProcessMessages;
finally
end;
end;
if task.Value.IsComplete then
begin
if task.Value.HasExceptions then
Exceptions := true
else if task.Value.IsCanceled then
Canceled := true;
end;
finally
task.Value.removeCompleteEvent(ProcCompleted);

end;
end;
finally
FEvent.Free;
end;
except
result := false;
end;

if (not Exceptions and not Canceled) then
Exit;
if Exceptions or Canceled then
raise EOperationCancelled.Create
(‘One Or More Tasks HasExceptions/Canceled’);

end;

[/code]

Reescrevendo o Exemplo:  Dia11_Threading_TParallel

 

Este é um comportamento quando o SO é windows. Em outras plataformas o resultado poderá ser outro.