Object-Relational Mapping (ORM) mechanisms are widely available and used in other technology stacks, usually involving code generation. But searching here for ORM doesn't yield too much. While governor limits add hard boundaries on heap, row counts, and query counts, there are still many cases (at least in the business domain that I work in) that would fit within those boundaries.
The platform's SObjects are already a good representation of the data fields, and they have to be exposed as so much of the UI tooling relies on them. It is the relationships between objects that are not well supported as the __r
mechanism has limited capabilities.
The features of the ORM mechanism would be something like this:
- Model an arbitrarily deep hierarchy
- Be creatable in memory and then saveable to the database
- Read lazily from the database as relationships are accessed in code
- Be generated from SObject describe data
- Have built-in
select *
as the default - A generated class per SObject that wraps and exposes the SObject and adds the relationship navigation (i.e. type safe)
- Optional base class and optional extending classes and factory mechanism so behavior can be added
- Convention over configuration, but some pragmatic stuff like being able to mark some fields (e.g. big text fields) as not queried and apply a query
limit
to some types - Basic bulkification
Does anything of this nature already exist in open source form? Or do people write their own?
PS
Based on the comments (thank-you for those), I've just double checked my understanding that the __r
fields can't be used in a general way as parent and child references would be in a normal object model. This test illustrates the various things you can't do (the tests would fail if you could), which makes it necessary to generate code to model the parent and child references:
@IsTest
private class RelationshipTest {
@IsTest
static void queried() {
Account aa = new Account(Name = 'A1');
insert aa;
Contact cc = new Contact(AccountId = aa.Id, LastName = 'C1');
insert cc;
Account[] accounts = [
select Name, (select Name from Contacts)
from Account
where Name = 'A1'
];
System.assertEquals(1, accounts.size());
for (Account a : accounts) {
System.assertEquals(1, a.contacts.size());
// Can't add to collection
a.contacts.add(new Contact(LastName = 'C2'));
System.assertEquals(1, a.contacts.size());
// Can't remove from collection
a.contacts.clear();
System.assertEquals(1, a.contacts.size());
}
}
@IsTest
static void notQueried() {
Account a = new Account(Name = 'A2');
// Can't add to collection
a.contacts.add(new Contact(LastName = 'C3'));
System.assertEquals(0, a.contacts.size());
// Can set parent reference
Contact c = new Contact(LastName = 'C4');
c.Account = a;
// But setting parent reference has no effect on child collection
System.assertEquals(0, a.contacts.size());
}
}