Rude class definition

Attributes do appear in Delphi 2010, and it is worth saying that FPC has an alternative syntax. Older versions of Delphi (still very deployed) do not have attributes available in the language, so it was not possible to be compatible with Delphi 6 up to latest versions (as we wished for our units).

It is perfectly right to speak about 'mis-use of index' - but this was the easiest and only way we found out to have such information, just using RTTI. Since this parameter was ignored and not used for most classes, it was re-used (also for dynamic array properties, to have faster lookup).
There is another "mis-use" for the "stored false" property, which is used to identify unique mandatory columns.

Using attributes is one of the most common way of describing tables in most ORMs.
On the other hand, some coders have a concern about such class definitions.
They are mixing DB and logic: you are somewhat polluting the business-level class definition with DB-related stuff.

That is why other kind of ORMs provide a way of mapping classes to tables using external files (some ORMs provide both ways of definition).
And why those days, even code gurus identified the attributes overuse as a potential weakness of code maintainability.
Attributes do have a huge downsize, when you are dealing with a Client-Server ORM, like ours: on the Client side, those attributes are pointless (client does not need to know anything about the database), and you need to link to all the DB plumbing code to your application. For mORMot, it was some kind of strong argument.

For the very same reasons, the column definitions (uniqueness, indexes, required) are managed in mORMot at two levels:
- At ORM level for DB related stuff (like indexes, which is a DB feature, not a business feature);
- At Model level for Business related stuff (like uniqueness, validators and filters).

When you take a look at the supplied validators and filters - see this article - you'll find out that this is much powerful than the attributes available in "classic" ORMs: how could you validate an entry to be an email, or to match a pattern, or to ensure that it will be stored in uppercase within the DB?

Other question worth asking is about the security.
If you access the data remotely, a global access to the DB is certainly not enough. Our framework handle per-table CRUD level access for its ORM, above the DB layer (and has also complete security attributes for services). It works however the underneath DB grants are defined (even an DB with no user rights - like in-memory or SQLite3 is able to do it).

The mORMot point of view (which is not the only one), is to let the DB persist the data, as safe and efficient as possible, but rely on higher levels layers to implement the business logic. It will make it pretty database-agnostic (you can even not use a SQL database at all), and will make the framework code easier to debug and maintain, since we don't have to deal with all the DB engine particularities. In short, this is the REST point of view, and main cause of success: CRUD is enough.

About the fact that you need to inherit from TSQLRecord, and can't persist anything, our purpose was in fact very similar to the "Layer Supertype" pattern of Domain-Driven-Design, as explained by Martin Fowler:

It's not uncommon for all the objects in a layer to have methods you don't want to have duplicated throughout the system. You can move all of this behavior into a common Layer Supertype.

Several ORMs at once

To be clear, mORMot offers three kind of table definitions:
- Via TSQLRecord / TSQLRecordVirtual "native ORM" classes: data storage is using either fast in-memory lists via TSQLRestServerStaticInMemory, either SQLite3 tables (in memory, on file, or virtual). In this case, we do not use index for strings (column length is not used by any of those engines).
- Via TSQLRecordExternal "external ORM-managed" classes: DB tables are created by the ORM, via SQL - see this article. These classes will allow creation of tables in any supported external database engine (SQlite3, Oracle, MS SQL, Jet, whatever OleDB provider). In this case, we use index for text column length. This is the only needed parameter to be defined for such a basic implementation, in regard to TSQLRecord kind of classes.
- Via TSQLRecordMappedAutoID / TSQLRecordMappedForcedID "external mapped" classes: DB tables are not created by the ORM, but already existing in the DB, with sometimes a very complex layout. This feature is not yet implemented, but on the road-map. For this kind of classes we won't probably use attributes, nor even external files, but we will rely on definition from code, either with a fluent definition, either with dedicated classes (or interface).

The concern of not being able to persist any class (it needs to inherit from TSQLRecord) does perfectly make sense.

On the other hand, from the implementation point of view, it is very powerful, since you have a lot of methods included within this class definition. It does also make sense to have a common ancestor able to identify all three kind of mORMot's table definitions: the same abstract ancestor is used, and clients won't even need to know that they are implemented in-memory, using a SQLite3 engine, or even a MS SQL / Oracle database. Another benefit of using a parent class is to enforce code safety using Delphi's strong typing: you won't be able to pass a non-persistent type to methods which expect one.

From the Domain-Driven / SOA point of view, it is now an established rule to make a distinction between DTO (Data Transfer Objects) and Domain Values (Entity objects or Aggregates). In most implementations, persistence objects (aka ORM objects) should be either the aggregate roots themselves (you do not store Entity objects and even worse DTOs), either dedicated classes. Do not mix layers, unless you like your software to be a maintenance nightmare!

Some Event-Sourcing architectures even implement several DB back-end at once:
- It will store the status on one DB (e.g. high-performance in-memory) for most common requests to be immediate;
- And store the modification events in another ACID DB (e.g. SQLite3, MS SQL or Oracle);
- And even sometimes fill some dedicated consolidation DBs for further analysis.

AFAIK it could be possible to directly access ORM objects remotely (e.g. the consolidation DB), mostly in a read-only way, for dedicated reporting, e.g. from consolidated data - this is one potential CQRS implementation pattern with mORMot. Thanks to the framework security, remote access will be safe: your clients won't be able to change the consolidation DB content!

As can be easily guessed, such design models are far away from a basic ORM built only for class persistence.

The good ORM is the one you need

Therefore, we may sum up some potential use of ORM, depending of your intent:
- If your understanding of ORM is just to persist some existing objects, mORMot won't help you directly (but we identified that some users are using the built-in JSON serialization feature of the framework to create their own dedicated Client-Server ORM-like platform);
- If you want to persist some data objects (not tied to complex business logic), the framework will be a light and fast candidate, via SQLite3, Oracle, MS SQL or even with no SQL engine, using TSQLRestServerStaticInMemory class which is able to persist its content with small files - see this article;
- If you need (perhaps not now, but probably in the future) to create some kind of scalable domain-driven architecture, you'll have all needed features at hand with mORMot;
- If your expectation is to map an existing complex DB, mORMot will handle it soon (it is planned and prepared within the framework architecture).

Therefore, mORmot is not just an ORM, nor just a "classic" ORM.

Feedback is welcome on our forum, as usual.