Delphi e Parse: Início dos trabalhos

Olá

Este seria mais um post da série Dia-a-dia do Desenvolvedor de Software, porém, como penso voltar a discutir sobre o assunto no futuro, irei tratá-lo como avulso, pelo menos por enquanto.

Do que se trata

Utilizar Delphi para interagir com Banco de Dados MongoDB utilizando Parse, que no caso será via www.back4app.com.

Motivação

O sistema que disponibilizo aos meus clientes tem um controle de liberação. Este controle acessa um banco de dados online, verifica se o cliente tem pendências e se está habilitado com base numa data de liberação.

Antes, eu vinha utilizando uma hospedagem normal, com banco de dados MySQL. Porém, recentemente surgiu a necessidade de ampliar a interação com o banco de dados, que seria: implementar um sistema de envio de mensagens (financeiro, informações gerais, atualizações, correções, etc.).

Utilizar a atual estrutura era inviável, mesmo porque, eu precisaria de mais acesso aos recursos do SO (apache/linux), e numa hospedagem normal não teria essa possibilidade (pelo menos não na atual hospedagem que utilizo aqui).

Primeiramente, pensei utilizar o AWS – Amazon Web Services. Cheguei a efetuar alguns testes e confesso que fiquei animado com os primeiros resultados. Mas eu precisava de algo mais simples visto que, mesmo ampliando o sistema com o controle de mensagens, não se justificaria contratar toda a estrutura oferecida pela Amazon.

Passei a testar os “bancos de dados em tempo real” como o Firebase e o Parse. E estes demonstraram ser uma ótima alternativa e, com simplicidade, supria tranquilamente minhas necessidades. Após vários testes, escolhi Back4app.

Eu pequei

Alguns desenvolvedores tratam linguagens de programação como tratam de religião (me refiro aos extremistas de plantão). E com estes, é comum desentendimentos em redes sociais, fóruns e até mesmo no ambiente de trabalho. E para eles eu digo: eu pequei.

Nunca me apeguei a qualquer tipo de tecnologia, seja ela qual for. Nunca me prendi a discussões do tipo “minha linguagem é melhor do que a sua!”, “Mac é melhor do que PC”, “Android rules, IOS is bad”, “Windows fede, Linux reina”, etc. Considero este tipo de discussão um atraso tremendo. Mesmo porque, o que vale pra mim no final das contas é que meu problema seja resolvido.

Feito esta explicação, retomo. No controle de liberação (habilitando ou não a utilização do sistema), construí uma pequena API utilizado Slim FrameWork (PHP). Para liberar um cliente, eu utilizo sistema feito no Delphi no desktop e no Android, primeiramente utilizei Xamarin(Visual Studio C#) e posteriormente, Android Studio.

É agora que a porca torce o rabo! Sei que muitos desenvolvedores Delphi me diriam: “Mas como pode?!? o Delphi já lhe permitiria desenvolver um único sistema, compilando para as três plataformas: desktop, Android e IOS!”.

Sim, sim, eu sei. Porém, a versão que comprei do Delphi (XE2 professional) não me permitiu utilizá-lo para este fim. E como sendo um micro, micro, miiiiiiicro empresário, atualizar 2 vezes por ano e ainda para uma versão mais completa é complicado. Então, a escolha do Android Studio foi algo natural para mim. E aí você pergunta: e o IOS?! Simples, não desenvolvo para esta plataforma. Não no momento (e se necessário, que venha SWift!!!).

Chega de blá, blá, blá

Escolhido o serviço, Back4app, torna-se necessário colocar mãos na massa, ou seria no código?!

Procurei algo pronto no Github, porém achei apenas para Firebase. Como não sou preguiçoso (pelo menos não neste caso! Experimente me pedir para lavar uma louça, não é mesmo patroa?!), parti para a documentação da API disponibilizada no Dashboard do serviço: Parse – Rest API Guide.

Tudo muito simples, mas “chatim” às vezes. No Android Studio, já temos uma vasta biblioteca que torna tudo muito simples de implementar (fiz um chat simples com apenas alguns minutos de trabalho), porém para Delphi, teremos que desenvolver a partir do zero.

Ao acessar o Guia conseguimos já de início identificar possíveis objetos:

parserestapi

Após alguns minutos lendo a API, cheguei à seguinte conclusão/interface:

unit DelphiParse.Interfaces;

interface

uses System.JSON, System.SysUtils, System.Generics.Collections;

type
  IResponseParse = interface
    ['{D356B879-8FAC-47BC-8946-7418497C1047}']
    function ResponseAsString(const Encoding: TEncoding = nil): string;
  end;

  IDelphiParse = interface
    ['{87E940B9-D2F8-4197-84D8-A84A426049BA}']
    function Post(const UrlParams: array of string;
      ObjectJson: TJSONValue = nil;
      QueryParams: string = ''): IResponseParse;

    function Get(const UrlParams: array of string;
      ObjectJson: TJSONValue = nil;
      QueryParams: string = ''): IResponseParse;

    function Put(const UrlParams: array of string;
      ObjectJson: TJSONValue = nil;
      QueryParams: string = ''): IResponseParse;

    function Delete(const UrlParams: array of string;
      ObjectJson: TJSONValue = nil;
      QueryParams: string = ''): IResponseParse;
  end;

  IParseQuery = interface
    ['{A00E5771-50D0-44C2-86BC-2F0ED2418CF9}']

    function Count: Integer;

    //where
    procedure WhereEqualTo(Key, Value: string);
    procedure WhereStartsWith(Key, Value: string);
    procedure WhereContains(Key, Value: string);

    //others
    procedure SetLimit(Value: Integer);
    procedure SetSkip(Value: Integer);

    //formatted
    function GetParamsFormatted: string;
  end;

  IParseObject = interface
    ['{A6616D36-B794-46DC-BBC9-51CF0AC18E5F}']
    procedure WhereEqualTo(Key, Value: string);
    procedure WhereStartsWith(Key, Value: string);
    procedure WhereContains(Key, Value: string);
    procedure Limit(Value: Integer);
    procedure Skip(Value: Integer);

    procedure Add(Key, Value: Variant);

    function SaveInBackGround: string;
    function GetInBackGround: string;
    function GetAllInBackGround: string;
    function DeleteInBackGround(ObjectId: string): string;
  end;
  • IDelphiParse: classes que implementem esta interface será a responsável por enviar e receber nossos JSONObjects.
  • IQueryParse: será onde montaremos os parâmetros no formato JSON. Exemplo: {“nome”:”joão”&limit:100&Skip=1}.
  • IParseObject: terá a incumbência de processar os comandos da API (inserir, atualizar, buscar e deletar).

A Classe TDelphiParse implementa IDelphiParse:

 TDelphiParse = class(TInterfacedObject, IDelphiParse)
  private
    function Send(const UrlParams: array of string;
      const Command: TParseVerbs; ObjectJson: TJSONValue = nil;
      QueryParams: string = ''): IResponseParse;

    function FormatUrlParams(Params: array of string): string;
  public
    function Post(const UrlParams: array of string;
      ObjectJson: TJSONValue = nil;
      QueryParams: string = ''): IResponseParse;

    function Get(const UrlParams: array of string;
      ObjectJson: TJSONValue = nil;
      QueryParams: string = ''): IResponseParse;

    function Put(const UrlParams: array of string;
      ObjectJson: TJSONValue = nil;
      QueryParams: string = ''): IResponseParse;

    function Delete(const UrlParams: array of string;
      ObjectJson: TJSONValue = nil;
      QueryParams: string = ''): IResponseParse;
  end;

Para exemplificar, vejamos o método Post:

function TDelphiParse.Post(const UrlParams: array of string;
  ObjectJson: TJSONValue; QueryParams: string): IResponseParse;
begin
  Result := Send(UrlParams, TParseVerbs.pvPost, ObjectJson, QueryParams);
end;

E o método Send:

function TDelphiParse.Send(const UrlParams: array of string;
  const Command: TParseVerbs; ObjectJson: TJSONValue;
  QueryParams: string): IResponseParse;
var
  HttpCliente: THTTPClient;
  HttpResponse: IHTTPResponse;
  CompletURL: string;
  ObjectStream: TStringStream;
begin
  HttpCliente := THTTPClient.Create;
  try
    HttpCliente.ContentType := 'application/json';
    HttpCliente.CustomHeaders['X-Parse-Application-Id'] := APP_ID;
    HttpCliente.CustomHeaders['X-Parse-REST-API-Key'] := REST_KEY;
    ObjectStream := nil;
    if ObjectJson <> nil then
      ObjectStream := TStringStream.Create(ObjectJson.ToJSON);
    try
      CompletURL := BASE_URL + FormatUrlParams(UrlParams) +
        IfThen(QueryParams='','', '?' + QueryParams);
      case Command of
        pvPost:
          HttpResponse := HttpCliente.Post(CompletURL, ObjectStream);
        pvGet:
          HttpResponse := HttpCliente.Get(CompletURL);
        pvPut:
          HttpResponse := HttpCliente.Put(CompletURL, ObjectStream);
        pvDelete:
          HttpResponse := HttpCliente.Delete(CompletURL);
      end;
      Result := TResponseParse.Create(HttpResponse);
    finally
      if Assigned(ObjectStream) then
        ObjectStream.Free;
    end;
  finally
    HttpCliente.Free;
  end;
end;

O Post recebe a URL completa do resource e o objeto JSON, e repassa ao método Send. Este sim, terá a responsabilidade de enviar os dados para a API. Passamos no header a APP ID e o REST API Key.

Com relação a IParseObject, vejamos a classe que implementa esta interface:

  TParseObjects = class(TInterfacedObject, IParseObject)
  private
    FClassName: string;
    Obj: TJSonObject;
    Parse: IDelphiParse;
    Query: IParseQuery;
  public
    constructor Create(ClassName: string);
    destructor Destroy; override;

    procedure WhereEqualTo(Key, Value: string);
    procedure WhereStartsWith(Key, Value: string);
    procedure WhereContains(Key, Value: string);
    procedure Limit(Value: Integer);
    procedure Skip(Value: Integer);

    procedure Add(Key, Value: Variant);

    function SaveInBackGround: string;
    function GetInBackGround: string;
    function GetAllInBackGround: string;
    function DeleteInBackGround(ObjectId: string): string;
  end;

Como este projeto, assim como foi informado no Github (link no final), é um Alfa do Alfa ;), então destaco o método SaveInBackGround, que foi criado apenas para efetuarmos os testes abaixo. Inspirado no que temos no Android, este serviria tanto para inserir quanto para salvar registros na base de dados. Porém, como vemos abaixo, ele contém apenas a primeira opção, ou seja, a de inserir um novo registro. A função de salvar ainda será implementada.

function TParseObjects.SaveInBackGround: string;
begin
  if (Obj.Count = 0) then
    raise Exception.Create('Objeto JSON não preenchido!');
  Result := Parse.Post(['classes', FClassName], Obj).ResponseAsString();
end;

Outro ponto que merece atenção, SaveInBackGround sugere o uso de threads, o que ainda não foi implementado.

Não irei passar por todas as classes e nem explicar cada método que compõe este projeto, visto que o código restante é bastante intuitivo e poderá ser visto na íntegra ao final deste post.

Primeiros Testes

Finalizado o processo de codificação, vamos ao teste. Iremos simular um sistema de envio de mensagens contendo: username, mensagem.

Para isto, criei um form:

form1

Vejamos o código do botão de enviar mensagem:

var
  Parse: IParseObject;
  Resultado: string;
begin
  Parse := TParseObjects.Create('Mensagens');
  Parse.Add('username', editUser.Text);
  Parse.Add('mensagem', editMessage.Text);
  Resultado := Parse.SaveInBackGround();
  memResult.Lines.Clear;
  memResult.Lines.Add(Resultado);
end;

É tudo que precisamos para enviar uma mensagem.

Nele, instanciamos um objeto do tipo TParseObject. Adicionamos o campo username e mensagem. Acionamos o método SaveInBackGround e o resultado adicionamos ao memo. Clicando no botão de enviar, obtemos:

enviomensagem

A resposta do envia mensagem informa a ID e data da criação.

Botão GetAll:

var
  Parse: IParseObject;
  Resultado: string;
begin
  Parse := TParseObjects.Create('Mensagens');
  Resultado := Parse.GetAllInBackGround();
  memResult.Lines.Clear;
  memResult.Lines.Add(Resultado);
end;

Clicando no botão, o Memo receberá todas as mensagens já enviadas no formato JSON:

{"results":[{"objectId":"FH9Am8MJOe","username":"joão","mensagem":"acabo de enviar mensagem para o Back4app","createdAt":"2016-09-10T18:23:12.148Z","updatedAt":"2016-09-10T18:23:12.148Z"}]}

Para finalizar, um código interessante é o do botão JsonToObj, que converte a resposta do servidor em objeto:

var
  Parse: IParseObject;
  Response: string;
  JsonStr: TJSONObject;
  JsonArray: TJSONArray;
  MensagemJson: TJSONValue;
  MensagemObj: TMensagem;
begin
  Parse := TParseObjects.Create('Mensagens');
  Response := Parse.GetAllInBackGround();
  JsonStr := TJSONObject.ParseJSONValue(Response) as TJSONObject;
  try
    memResult.Lines.Clear;
    JsonArray := JsonStr.GetValue('results') as TJSONArray;
    for MensagemJson in JsonArray do
    begin
      MensagemObj := TJson.JsonToObject<TMensagem>(MensagemJson.ToString);
      ObjetoToMemo(MensagemObj);
      MensagemObj.Free;
    end;
  finally
    JsonStr.Free;
  end;
end;

Classe Mensagem:

  TMensagem = class
  private
    FObjectId: string;
    FCreatedAt: TDateTime;
    FUpdatedAt: TDateTime;
    FUsername: string;
    FMensagem: string;
  public
    property ObjectId: string read FObjectId write FObjectId;
    property CreatedAt: TDateTime read FCreatedAt write FCreatedAt;
    property UpdatedAt: TDateTime read FUpdatedAt write FUpdatedAt;
    property Username: string read FUsername write FUsername;
    property Mensagem: string read FMensagem write FMensagem;
  end;

Quando clicamos no botão, o resultado será:

================================
ObjectId: FH9Am8MJOe
Username: joão
Mensagem: acabo de enviar mensagem para o Back4app
CreatedAt: 10/09/2016 18:23:12
UpdatedAt: 10/09/2016 18:23:12
================================

Github

Link para o projeto: DelphiParse

Quando forem compilar, não se esqueçam de colocar a DLL do FastMM4 na pasta do executável.

Lembro mais uma vez, é apenas o começo. Muita coisa será alterada e/ou implementada ainda. Em posts futuros, irei mostrar a implementação de uma nova classe: TParseUser, para a questão do login. E também irei tratar das threads.

Isso é tudo por enquanto. Abraços.

Sobre o autor: Luiz Carlos (60 Posts)

Desenvolvedor de software Balsas/MA


Compartilhe:

4 Replies to “Delphi e Parse: Início dos trabalhos”

    1. Oteniel

      Este é o GUID (Globally Unique Identifier), ou seja, Identificador Global Único. É um identificador da interface ou objeto de servidor COM. Para gerar o número, basta usar o atalho Ctrl+Shift+G.

      Serve para você saber se determinado objeto dá suporte a uma determinada interface, esta tem um identificador único, o GUID. É mais ou menos isso.

  1. Parabéns Luiz Carlos
    Gostaria de parabenizar está muito bom
    Concordo contigo, sobre a tecnologia, o problema é que as próprias empresas dão esse divisor de águas entre Delphi / Java etc.
    É possível com um pouco de esforço ter facilidades no delphi, basta arregaçar as mangas e ir a luta.

    Parabéns e que venha os próximos.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *