22

Today I got into a heated debate with another developer at my organization about where and how to add methods to database mapped classes. We use sqlalchemy, and a major part of the existing code base in our database models is little more than a bag of mapped properties with a class name, a nearly mechanical translation from database tables to python objects.

In the argument, my position was that that the primary value of using an ORM was that you can attach low level behaviors and algorithms to the mapped classes. Models are classes first, and secondarily persistent (they could be persistent using xml in a filesystem, you don't need to care). His view was that any behavior at all is "business logic", and necessarily belongs anywhere but in the persistent model, which are to be used for database persistence only.

I certainly do think that there is a distinction between what is business logic, and should be separated, since it has some isolation from the lower level of how that gets implemented, and domain logic, which I believe is the abstraction provided by the model classes argued about in the previous paragraph, but I'm having a hard time putting my finger on what that is. I have a better sense of what might be the API (which, in our case, is HTTP "ReSTful"), in that users invoke the API with what they want to do, distinct from what they are allowed to do, and how it gets done.


tl;dr: What kinds of things can or should go in a method in a mapped class when using an ORM, and what should be left out, to live in another layer of abstraction?

1
  • It seems to me that it was two issues discussed at the same time, the issue of persistance Models are classes first, and secondarily persistent (they could be persistent using xml in a filesystem, you don't need to care). and the reason for the code. Usually the things clears, at least for me, when I ask myself why the code is written, what requirement forces this code. Is it the customers requirement on how the program will work or is it from in which manner we choose to implement it. For me the first is business logic and second what you call domain logic. How this helps.
    – user1041
    Mar 29, 2012 at 6:11

4 Answers 4

12

I'm mostly with you; your colleague seems to be arguing either for the anemic domain model antipattern or for duplicating the model in a "persistence model" with no obvious benefit (I'm working on a Java project where this was done, and it's a massive maintainability headache, as it means three times the work whenever anything changes in the model).

What kinds of things can or should go in a method in a mapped class when using an ORM, and what should be left out, to live in another layer of abstraction?

Rule of thumb: the class should contain logic that describes basic facts about the data which are true under all circumstances. Logic that is specific to a use case should be somewhere else. An example is validation, there's an interesting article from Martin Fowler where he makes the point that it should be considered context-dependant.

2
  • +1, for the second part of your answer. As for the first part, I am sure why you say that anemic domain model requires that much 'extra' work in case of change. My understanding is the change will occur any way but in several different classes (which is kind of bad) but it is almost the same amount of change; I guess?
    – NoChance
    Mar 29, 2012 at 10:23
  • 1
    @Emmad Kareem: The comment was about having spearate persistence and domain models. Adding a property to the model then requires adding it to two model classes (rather than one) as well as to whatever maps between them (that could in theory be automatic, but usually the asshat who thought it was a good idea to have a separate "persistence model" decides to justify that separation by making them different, e.g. have datatypes that more closely match the DB type model) so that's 2+X times the amount of change, with X varying between 0 and hours of lost productivity due to obscure mapping issues. Mar 29, 2012 at 10:36
4

This is a judgement call that really depends your anticipated size and scale of what you are developing. The most rigid approach is to limit the ORM types to a data access component and use POCOs in a common library as types referenced and used by all layers. This would allow for future physical separation as well as logical separation. You could also decide that an additional layer should exist between the UI and the business logic layer. This is usually called a Facade or Business Interface layer. This additional layer is where your "use-case code" lives. The individual loosely coupled code is called by the Facade/BI layer (e.g. Facade has a ProcessOrder() function which calls into the business logic 1:M times to perform all of the steps necessary to actually process the order).

However, all of that being said: many times this amount of architecting is simply unnecessary overkill. For example, code specifically for a simple Web site where you have no intention of packaging its components for re-use. It is perfectly valid to create an MVC Web site and use EF objects for this type of solution. If the site needs to scale out later, you can look into clustering or a process often lost fray called "refactoring."

2
  • I appreciate the clarity of stating rigid approach (or conceptual ideal) as the baseline, and then simplify from there. For me, the awkwardness is having 2 sets of class abstractions that are very similar (ORM and POCO). For my particular application, I'm going to punt on OO and just use the functional approach for my business logic. Thus, I won't have the 2 parallel sets of classes at the data access level and business logic level. Apr 5, 2021 at 16:30
  • related: the problem of having two classes for every real "object" (the business layer class and the ORM model class) softwareengineering.stackexchange.com/questions/340105/… Apr 5, 2021 at 16:40
3

Just remind your colleague that you don't need to overarchitect the models as if this was a Java project. I mean, comparing two persisted objects is behavior, but it's none specified by the persistence layer. So the 6 beer question is: why have completely unrelated classes describing something about the same thing? Sure, persistance is a big aspect enough of a model to be treated separately, but not enough to warrant for it to be treated distinct of everything else. If you drive your car, wash it or bust it, your manipulating your car the whole time.

So why not just compose all these different aspects into a single model class? You need a bunch of class methods dealing with persisted objects--put them in one class; you have a bunch of instance methods dealing with validation--put them in another one. Finally, mix the two in and voila! You got yourself a smart, self-aware and fully contained model representation right there.

1

In addition to other answers, pay attention to hidden cavehats when using rich domain models with an ORM.

I had issues injecting polymorphic services in the persisted model classes when trying to achieve something like the following pseudocode:

Person john = new Person('John Doe')
Organisation company = organisation_repository.find('some id')
Employee our_collegue_john = company.hire(john)

In this case, an Organisation may require a HRService as a constructor dependency (for instance). You usually can't easily control the instanciation of your model classes when using an ORM.

I was using Doctrine ORM and the service container from Symfony. I had to monkeypatch the ORM in a not-so-elegant fashion and had no choice but to separate persistence and business models. I haven't tried yet with sqlachemy, thought. Python might prove more flexible than PHP for this stuff.

Your Answer

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

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