Own implementation of the autogenerated xData/Aurelius CRUD endpoints

Hi,

For training and understanding purposes, I'm trying to define and implement the same endpoints as the ones autogenerated when using xData/Aurelius.

I think I've been able to define (and Route w/TRoutingPrecedence = Service) the interface for a simple table (DemoTable), in a way that it is now using my implementation instead of the autogenerated ones.

Entity:

  [Entity]
  [Table('DemoTable')]
  [Model('Default')]
  [Id('FID', TIdGenerator.IdentityOrSequence)]
  TDemoTable = class
  private
    [Column('ID', [TColumnProp.Required, TColumnProp.NoUpdate])]
    FID: Integer;

    [Column('Name', [], 50)]
    FName: Nullable<string>;

    [Association([TAssociationProp.Lazy], CascadeTypeAll - [TCascadeType.Remove])]
    [JoinColumn('ParentID', [], 'ID')]
    FParentID: Proxy<TDemoTable>;
    function GetParentID: TDemoTable;
    procedure SetParentID(const Value: TDemoTable);
  public
    property ID: Integer read FID write FID;
    property Name: Nullable<string> read FName write FName;
    property ParentID: TDemoTable read GetParentID write SetParentID;
  end;

Interface definition:

  [ServiceContract]
  [Route('')]
  IDemoTableService = interface(IInvokable)
    ['{350F11C4-DB96-4921-8757-22BD32B5BCD2}']
    [HttpGet,    Route('DemoTable')]       function ListDemoTables(Query: TXDataQuery): TList<TDemoTable>;
    [HttpPost,   Route('DemoTable')]       procedure CreateDemoTable;
    [HttpGet,    Route('DemoTable({ID})')] function RetrieveDemoTable(ID: integer): TDemoTable;
    [HttpPut,    Route('DemoTable({ID})')] procedure UpdateDemoTable;
    [HttpDelete, Route('DemoTable({ID})')] procedure DeleteDemoTable(ID: integer);
    [HttpPatch,  Route('DemoTable({ID})')] procedure MergeDemoTable(ID: integer);
  end;

Interface Implementation:
I've handled the implementation for HttpGet, HttpGet{ID} and HttpDelete{ID} in a way I'm happy with. But for HttpPost, HttpPut{ID} and HttpPatch{ID} I'm stuggling a bit.
For HttpPost I assume it would be something like:

procedure TDemoTableService.CreateDemoTable;
var
  lManager: TObjectManager;
  lDemoTable: TDemoTable;
begin
  lManager := TXDataOperationContext.Current.GetManager;
  lDemoTable := TDemoTable.Create;

  "lDemoTable.Values" := "Posted body"; <-- HOW TO DO THIS?

  lManager.AddOwnership(lDemoTable);
  lManager.Save(lDemoTable);
end;

Assuming the above interface is defined correctly, may I ask if you have some hints/help or a quick example on to implement the mentioned functions/procedures?

Thank you in advance.

That's a very interesting exercise and very good for learning purposes indeed. It illustrates the difference between automatic CRUD endpoints and service operations and requires understanding of several (simple) concepts of building service operations.

Regarding your questions:

HttpPost

You should receive the TDemoTable as a parameter, XData takes care of the rest. Another thing you should be aware is that automatic CRUD endpoints use Manager.Replicate, not Manager.Save because the former will handle duplicated instances in the object tree, avoiding the "object already in context" errors. Calling Save is absolutely not wrong, but you will have such problems depending on how the JSON is sent.

So the implementation would be something like this:

procedure TDemoTableService.CreateDemoTable(Value: TDemoTable);
var
  lManager: TObjectManager;
begin
  lManager := TXDataOperationContext.Current.GetManager;
  lManager.Replicate(Value);
end;

HttpPut

Same idea here, you receive the object you want to update, and call Merge to copy the properties to the object:

procedure TDemoTableService.UpdateDemoTable(ID: Integer; Value: TDemoTable);
var
  lManager: TObjectManager;
  MergedDemoTable: TDemoTable;
begin
  lManager := TXDataOperationContext.Current.GetManager;
  MergedDemoTable := lManager.Merge(Value);
  lManager.Flush(MergedDemoTable);
end;

HttpPatch

This is more complex and unfortunately not trivial do implement. In this case you should receive a parameter of type TJSONObject (or TJObject) and manually set each property present in the JSON to the object in the manager.

To help you a little in that task, you can create a DTO that has all fields of type Assignable<T>, so that when XData deserializes such object, you can check if IsAssigned property of those fields to check if they were present in JSON or not.

1 Like

Thank you for your detailed replay!
I'll play around with it to try and get a bit more under the hood.

One initial follow up though: Am I correct assuming all automatically generated CRUD's are implemented as functions, except for [HttpDelete]?

You could say that in the sense that, except for HttpDelete, all other endpoints return a JSON to the client.

But note that the actual implementation of automatic CRUD endpoints are not service operations, it's specific XData code, so it's not exactly the same thing.