Aurelius objects everywhere - Distributed applications using JSON

Bookmarks: 

Thursday, December 13, 2012

TMS Aurelius version 1.7, released a couple of weeks ago, introduced a new feature that was often requested by users: JSON support. You may ask yourself, why is that so important? Can't I "use JSON" already with Delphi itself using JSONMarshal class, or even by a 3rd party library like Super Object?

Well, yes and no. Actually there are already many people using Aurelius in distributed applications, using it with DataSnap, REST servers. etc.. My previous blog post is an example of that. If you have in your application simple entity classes like this:
  
  [Entity, Automapping]
  TCustomer = class
  private
    FId: Integer;
    FName: string;
    FCity: string;
  public
    property Id: Integer read FId write FId;
    property Name: string read FName write FName;
    property City: string read FCity write FCity;
  end;
Then it would work smoothly. One of the things I like most in Aurelius is your entity classes can be very simple. You can use simple types, simple objects, don't need to inherit from a specific class or override methods. So you can build your entity classes the way it will fit for your application, in this case, make them simple to transfer them between your client and server.

But you might also want to benefit from many Aurelius features: nullable types, lazy-loading, blob support, associations, lists. That's when the problem arises. Most JSON libraries don't handle them correctly. Some of them allow you to add some custom converters and attributes that maybe would make it happen, but it would pollute your class (why add extra meta information when your mapping is already done) and probably will require many hours of extra effort to make it work.

So, that 's the reason for this new feature - it takes advantage of the mapping information for the serialization and deserialization. Aurelius already knows which fields/properties are important (mapped), the custom types, the lists, associations, etc.. So for example, to build a DataSnap REST server that has methods returning objects, you can simply implement your server methods like this;
function TBugTrackerMethods.LoadIssue(Id: integer): TJsonValue;
begin
  Result := FSerializer.ToJson(FManager.Find<TIssue>(Id));
end;

procedure TBugTrackerMethods.SaveIssue(Issue: TJsonValue);
begin
  FManager.SaveOrUpdate(FDeserializer.FromJson<TIssue>(Issue));
  FManager.Flush;
end;

function TBugTrackerMethods.ListIssuesByProject(ProjectId: integer): TJsonValue;
var
  IssueList: TList<TIssue>;
begin
  IssueList := FManager.Find<TIssue>
    .SubCriteria('Project')
    .Where(TLinq.IdEq(ProjectId))
    .List<TIssue>;
  try
    Result := FSerializer.ToJson(IssueList);
  finally
    IssueList.Free;
  end;
end;
It doesn't matter how complex a TIssue class is built. Actually all the structure was built not only to make it easy to send/receive Aurelius objects, but also to mimic the behavior of TObjectManager class, if needed. So if you have an existing client-server Aurelius application, changing it to retrieve objects from a remove application server instead of database should be very simple and straightforward. Associations are fully supported, even with lazy-loading. Even from non-Delphi clients, like JavaScript, the client you will write will look very similar to a Delphi application using Aurelius. The following code is an example in JavaScript (using JQuery) that communicates with the DataSnap server mentioned above. It loads an issue from the server (using an ID provided by the user) and allows you to close or cancel the issue by clicking buttons:
var issue = null;
$(document).ready(function() {

  $('#issuediv').hide();

  $('#searchButton').click(function(event) {
    issue = serverMethods().LoadIssue(issueIdField.value).result;
    $('#issuediv').show();
    $('#idField').val(issue.Id);
    $('#subjectField').val(issue.Subject);
    $('#priorityField').val(issue.Priority);
    $('#statusField').val(issue.Status);
    $('#assignedToField').val(issue.AssignedTo.Name);
    $('#projectField').val(issue.Project.Name);
    $('#descriptionField').val(issue.Description);
  });

  $('#closeButton').click(function(event) {
    issue.Status = 'Closed';
    serverMethods().SaveIssue(issue);
    $('#issuediv').hide();
  });

  $('#cancelButton').click(function(event) {
    issue.Status = 'Canceled';
    serverMethods().SaveIssue(issue);
    $('#issuediv').hide();
  });
});
A final note: the serializer/deserializer can use any JSON parser library you want. Currently both Delphi (DataSnap) and SuperObject are supported, but architecture is build in a way that any other library can be used.

Wagner R. Landgraf


Bookmarks: 

This blog post has received 6 comments.


1. Tuesday, January 15, 2013 at 12:45:48 PM

Aurelius really looks a great piece of software. I wonder if are that any plans on Aurelius support for Free Pascal / Lazarus (on Linux) ?

Regards,

Anderson Farias


2. Tuesday, January 15, 2013 at 3:54:41 PM

We would like to support it, but as far as I know it doesn''t provide the same RTTI library that is available in Delphi, so it''s not compatible. If it becomes more compatible, we can consider, yes.

Wagner Landgraf


3. Wednesday, March 06, 2013 at 9:33:30 AM

Hi,

Where I can find the source code of this example?

Rafael


4. Friday, March 08, 2013 at 7:53:05 AM

Hi,

Congratulations on the good work with Aurelius.

Is there any possibility of providing the example presented in this post to download?

Rafael


5. Friday, March 08, 2013 at 8:01:12 AM

Thank you! Please send an e-mail to our support e-mail so we can talk more about this demo.

Wagner Landgraf


6. Monday, January 27, 2014 at 1:49:52 PM

Boa tarde Wagner,
Estamos estudando a utilização do TMS Aurelius, e estamos com dúvida com relação a utilização dele com DataSnap REST.

Quando recebemos do servidor uma lista de objetos JSON, tal como abaixo:
{"result":[[
{"$type":"PapelModel.TPapelModel","$id":1,"FID":1,"Nome":"GERENTE","Descricao":"GEN"},
{"$type":"PapelModel.TPapelModel","$id":2,"FID":2,"Nome":"SUPERVISOR","Descricao":"SUP"},
{"$type":"PapelModel.TPapelModel","$id":3,"FID":3,"Nome":"ADMINISTRADOR","Descricao":"ADM"}
]]}

Estou usando a função abaixo para deserializar o objeto JSON de retorno
class function TJsonManager.JSONToObject<T>(AValue: TJSONValue): T;
var
Deserializer: TDataSnapJsonDeserializer;
begin
if AValue is TJSONNull then
Exit(nil);

Deserializer:= TDataSnapJsonDeserializer.Create;
try
Result:= Deserializer.FromJson<T>(AValue);
finally
Deserializer.Free;
end;
end;

Porem quando eu faço a deserialização do objeto para um TList<TPapelModel> é gerada uma exceção:
class function TPapelController.Find: TList<TPapelModel>;
var
ResponseStream: TStringStream;
begin
try
ResponseStream := TStringStream.Create;
try
Get(ResponseStream);
Result := TJsonManager.JSONToObject<TList<TPapelModel>>(TJsonManager.JSONStringStreamToJSONValue(ResponseStream));
finally
ResponseStream.Free;
end;
except
on E: Exception do
Application.MessageBox(PChar(''Ocorreu um erro durante a consulta. Informe a mensagem ao Administrador do sistema.'' + #13 + #13 + E.Message), ''Erro do sistema'', MB_OK + MB_ICONERROR);
end;
end;

EIncompatibleJsonValueType with message ‘Incompatible jsonvalue Cannot serialize to proper type.

Você poderia me orientar sobre a falha que estamos cometendo no processo?


Antonio Baceiredo




Add a new comment:
Author:
Email:
  You will receive a confirmation mail with a link to validate your comment, so please use a valid email address.
Comment:
 
Change Image
Fill in the characters from the image above:
 

All fields are required.
 




Previous  |  Next  |  Index