6

I have a set of ORM models that are shared between the main business application and a couple minor side applications (such as an administrative web interface).

I don't want to put the object's business logic for the main application inside the ORM model classes because the web interface doesn't use any of it (and it would be too bloated).

That leaves me with the problem of having two classes for every real "object" (the business layer class and the ORM model class), and wondering how I should link the two. Either composition or inheritance would work, but both feel wrong. For example, I have a User class and a DBUser ORM model class. User "is not" a DBUser and a User "does not have" a DBUser.

Is there a standard solution or best practice to address this predicament? Or is this a case where there is no great answer and I just have to pick the one that makes me the least uneasy?

Here is a code example, just in case the above wasn't clear:

class DBUser(SQAlchemyBase):

    __tablename__ = 'users'

   user_id = Column(Integer, primary_key=True)
   username = Column(String, nullable=False)
   # ...


class User(object):

    def __init__(self, user_id):
        self.dbuser = db.query(DBUser).filter(DBUser.user_id == user_id).first()

    @property
    def username(self):
        return self.dbuser.username

    @username.setter
    def username(self, username):
        self.dbuser.username = username

    def connect_to_server(self, server):
        ...

    def save(self):
        db.add(self.dbuser)
        db.commit()
        db.detach(self.dbuser)

    def disconnect_from_server(self):
        ...

    def handle_incoming_action(self, action):
        ...

2 Answers 2

3

I don't know your ORM, but what you have looks like Data Transfer Objects, or DTOs: basically just data recordsets mapped into objects, with little to no further logic in them (here: DBUser). It is possible to access their data values from the business model. In best case, through an interface, so you can possibly use different data access layers.

Dependent on the ORM, one can also use the business objects directly for data mapping (as with (N)Hibernate in .NET/Java). It would be bad to implement separate code to copy data between DTOs and business object fields/properties, e.g. as part of load/save/update routines (see below)!

I see a serious design mistake in your business model, which occurs frequently by ORM novices (there's probably still a chance to correct it now):

Having Load (or initializer by ID) and Save functions on individual objects, even a DB transaction Commit. In most ORM, entities should be designed Persistence Ignorant, meaning that objects should have no code that is DB persistence related, especially not for individual objects, no attempts to control DB access and no properties for transient (not persisted), dirty (changed) or stale (changed in DB meanwhile).

Instead, ORM usually have a Unit of Work (I saw in a short Google search that it's also called "session" in SQAlchemy). The application just works with the entities within an active session, the session tracks changes and persists them all in the end (Flush/Commit, different settings possible).

Session function names like Load, Save or Update are often misleading, because they don't mean actual DB Insert, Update, Delete, Select statements for entities. These will be only scheduled and occur when the ORM decides it's the right time. In (N)Hibernate, changes will be persisted even without any Save/Update call on the entities, and unchanged entities will not be updated at all, even after Save/Update.

With attempts to force the ORM to individual CRUD statements to the DB, the user violates the ORM and destroys many helpful features!

It will also result in a huge number of individual DB roundtrips for single statements (called SELECT N+1 when traversing object graphs), instead of optimized queries and batches by the ORM.

Maintaining object/data identity:

Also, if using wrapping business objects, accessing DTOs, it should be ensured that object instances are unique per data row, just like the DTO. ORM usually just allow one DTO instance per database source item, even if queried or used in multiple places.

3
  • While you are right, the main reason I'm using the ORM is actually for the side applications.
    – user35358
    Commented Jan 16, 2017 at 2:18
  • I meant, in the side applications that's how I use it. The main application is latency sensitive and vast majority of changes do not need to be persisted until certain events are hit, so it was preferable to manually add/commit/remove Users to the session when they needed to be saved. Much much faster.
    – user35358
    Commented Jan 16, 2017 at 2:29
  • While your implementation may work in specific cases, it will lead especially newbie programmers on the wrong path, thinking that ORM is like Load/Save/Update on individual objects, or sending and mapping SQL statements to result sets of objects. I say this because I have seen wrong ORM usages of this kind, which are in fact more complex and brittle than direct SQL programming.
    – Erik Hart
    Commented Jan 20, 2017 at 8:38
1

Looking at your issue through the glasses of Fowler's Patterns of Enterprise Application Architecture suggests the following:

  • the data source logic is implemented in DBUser, which is a Row Data Gateway: it is a simple data holder responsible for keeping in memory a single row of a table but without any domain logic.
  • the domain logic is implemented with User taking a Domain Model approach. The domain object must be associated with its data holder, for example keeping a reference to it.

Keeping such a reference may technically be implemented like a composition. Semantically this link is not necessarily a "has a" relation; on an UML model you'd use an association labelled "has a data holder" or "use for storage". Some might argue that it should be represented by an aggregation (where DBUser is the persistent data part of the object). But UML composition would not be used, because a DBUser is not necessarily owned by User (for example, it could be owned by an Identity Map that keeps a view on all db rows in memory).

Using an inheritance would be the wrong choice here, in my opinion: it would lock your domain classes into one specific design (where the domain object would be a proxy of db object). This is not a sound separation of concerns. For example, If later you'd decide to use another datasource implementation (for example an ORM that uses metadata based dynamic mapping) you would then have to redesign completely your domain objects.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.