Jump to content
Darian Miller

Circular references with API design

Recommended Posts

In a codegen project I am working on, I am importing an API and creating the Delphi code to utilize it.  I prefer working with records so most of the codegen is creating record types for the request/response objects exposed by the API.   However, about 5% or less of this large API types involve circular references between types.

 

I tried a few different approaches and couldn't come up with a decent solution with records so I starting to use forward declaration with classes, at least for this 5%.  That seems to work, but now I think I need to make all the types classes instead of having a mixture.  I ventured into Variants and even implemented my own custom type via TInvokeableVariantType to get some properties defined but generic variant custom record types don't work.  It was a cool side trek but didn't solve this problem.

 

After stewing on this for a while, I thought I'd ask for any feedback.    Would you prefer a large API with 95% simple records and some occasional class-based request/responses due to a funky API design, or would you want to see it consistent throughout and only use classes?   Or would you implement an alternative approach?
 

TRecordA = record

  Field1:Integer;

  Field2:TRecordB;

  Field3:string;

  TopX:TRecordC;

end;


TRecordB = record

  Field4:Integer;

  Field5:TRecordA;

end;


TRecordC = record

  Items:TArray<TRecordA>;

end;

 

 

 

Share this post


Link to post

@Darian Miller

In times like these I just use pointers. Not sure whether that will affect the API interaction though.

 

PRecordB = ^TRecordB;
PRecordC = ^TRecordC;

TRecordA = record
  Field1 : Integer;
  Field2 : PRecordB;
  Field3 : string;
  TopX : PRecordC;
end;

TRecordB = record
  Field4 : Integer;
  Field5 : TRecordA;
end;

TRecordC = record
  Items : TArray<TRecordA>;
end;

 

Edited by PeaShooter_OMO
  • Thanks 1

Share this post


Link to post

I disagree the use of pointers directly for a waste use. In the past I had some side effects not "beauty" ... especially if they were targeting structures with "managed types".

 

In the state of Darian exposition, I will prefer the mix solution.

 

CodGen projects are always "beasts", I wish you good luck in your job @Darian Miller

  • Like 1
  • Thanks 1

Share this post


Link to post

The TRecordA/TRecordB relation leads to a structure of infinite size. I would question the design choice, but having no knowledge of the underlying API that is not more than just a gut feeling.

7 hours ago, Darian Miller said:

Would you prefer a large API with 95% simple records and some occasional class-based request/responses due to a funky API design, or would you want to see it consistent throughout and only use classes?

IMHO, you should use the approach that fits best. There is no general rule for all cases.

  • Like 1
  • Thanks 1

Share this post


Link to post

I agree with @Uwe Raabe

 

As you state that the APIs may return structures that cross reference one another - a record would not be suitable here, unless you had pointer support. The record is normally defined as something with a fixed size, and as Uwe pointed out, the interdependence means this constraint would not be met. So you would either need a pointer to a record (a forward can be defined for that), or use classes (that we know are heap bound). 

 

If your codegen can do some analysis and identify which entities have interdependencies, you could choose to use records in one case and classes in another, but IMO, just having a single consistent approach will save you in the long run. I'd just go for using classes.  

  • Thanks 1

Share this post


Link to post

When you've got a platform that has 64-bit integers that have values that actually range from, say, 0..27, and you think that having a dozen of them in a record is somehow more efficient than using an object with a single 64-bit pointer, I'm not clear what you think is being saved. Pointers might make use of half of a 64-bit data element, but there aren't a lot of applications that have that kind of dynamic range on the values they manipulate. Floating-point values ... ok, you get a little more precision for calculating the trajectory of things flying around the solar system. But for most routine programming tasks that the typical UI does ... it's way overkill. 

 

Consider that JSON is more or less a big record with labels but not types, meaning all of the data fits into 8-bit chunks, while accesses are usually done with very inefficient linear searches. So the first thing you often do is parse them into some kind of typed data structure as a more efficient container, unless you only refer to a few of the fields and then toss the the entire JSON string away.

 

Records are fine when all of the data is managed by a single interface; but when you're passing them around and lots of different methods are touching them, and a change is made in one instance that's not propagated down the line and nobody else knows that the value has changed, they can be horribly time-consuming to debug. Or you can make a change that you do NOT want propagated but it DOES get propagated, simply because their use is mainly pass-by-value semantics but there's a hidden copy-on-write going on. But when a Record's data is managed by a single interface, you're just simulationg a Class. So ... what's the point? You're just having to write more management code that the compiler provides for free if you use a Class instead.

It's like saying you won't sell your 1980-vintage car because you like being able to tweak the carb to assure optimal fuel efficiency, while ignoring the fact that electronic fuel injection can double or triple your overall mileage -- at the expense of having to trust that it's actually doing its job. 

I prefer to use Classes and trust the compiler to do its job.

 

 

Edited by David Schwartz

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×