1

While reading "Patterns, Principles, and Practices of Domain-Driven Design" by Nick Tune; Scott Millett (certainly not the first book on DDD I've read) I started to understand usage of Martin Fowler's EAA patterns with DDD and with that there came a question.

A single SQLAlchemy model is like Active Record (if I understand it correctly), however, a set of SQLAlchemy models is very good at abstracting persistence mechanism away, so I always thought following Domain Model, degrading in some places to Anemic Domain Model.

In many cases there still are Transaction Scripts, touching several Entities. I am trying to keep these in actions / services (Service Layer?) because it feels wrong to have actions touching multiple models in the model classes themselves (circular dependencies can occur) unless they are subordinate (lay lower in the aggregate structure).

The result is not quite a pure pattern (as business logic is between "actions" and "active records") - more like some kind of hybrid of those mentioned above - but it is working in practice, captures domain knowledge (both verbs and nouns) well, fits naturally with SQLAlchemy persistence.

What pattern SQLAlchemy models (in ORM mode) naturally represent and how operations, which touch several models, can be fit into DDD way? Is there anything I can improve on the described above?

To clarify more, this question and it's answers deals with another, similar in spirit, dilemma: whether to use ORM classes directly for the domain model or add one more layer (and the favorite answer is not to overarchitect).

In this question, I am trying to find a good way to place multi-entity behaviors. My take is that "action" / "service" modules with function per action is the right place (in Python it is usual to use module level functions when there is no shared state).

Roman Susi
  • 1,763
  • 2
  • 12
  • 20
  • I think you misunderstand the purpose of DDD. *DDD is not a coding methodology;* it is a design technique. You also seem to be misunderstanding software patterns. Patterns are not building blocks; they are well-known solutions to common problems. – Robert Harvey Jul 12 '18 at 15:51
  • On the contrary. The book I mentioned explained it well. What I am wondering about SQLAlchemy (and I guess other ORMs) seems to offer a good way, but with it's own pattern it seems. So the question is, is there something wrong as there are many recognizable patterns in the solution (inside one Bounded Context). (And SQLAlchemy at it's own level uses all those Object-Relational patterns - most of them) – Roman Susi Jul 12 '18 at 16:29
  • I guess I'm wondering why you think SQLAlchemy or Fowler's enterprise patterns have anything to do with DDD. All three of these things were authored by different people. SQLAlchemy and Fowler's enterprise patterns deal with software design from a *systems* perspective; DDD deals with software design from *your specific business domain's perspective.* – Robert Harvey Jul 12 '18 at 17:41
  • Even though DDD's own tactical patterns or Fowler's patterns are not main thing DDD is about, it is imperative for "analysis model" not to deviate too much from "code model", do you agree? It means, that code dealing with core domain should not use too many solution-space tricks. – Roman Susi Jul 12 '18 at 17:56
  • 1
    I think the point is that the fact you use SQLAlchemy is irrelevant to DDD. Your domain will be abstracted away from all these concerns. Maybe you have a repository which is implemented to use SQLAlchemy as an ORM, but your domain objects will not be tainted by this. – Vincent Savard Jul 12 '18 at 18:05
  • @VincentSavard What do you mean by "but your domain objects will not be tainted by this" ? At least, I do not see any reason to separate code representation of Entity from ORM's model class. Yes, there are some leaks, and Generic Repo as a basis. – Roman Susi Jul 12 '18 at 18:19
  • 1
    I'm not sure I understand what you say. The domain layer is about what your business does. You want to isolate your domain layer from irrelevant technical details such as the fact that you use an ORM. – Vincent Savard Jul 12 '18 at 18:36

2 Answers2

7

This is a common issue, and the cause is usually related to a shallow understanding of DDD. The purpose of DDD is to model the functional requirements of your system such that the result is a useful abstraction of the core rules of your business domain. Often developers fail to understand why this is important and create a domain model almost directly mapped from their persistence model. Because data is usually a poor starting point for modeling the functional requirements of a system, this usually will result in a highly-coupled system where rules are devoid of context (and are therefore useless).

When creating a domain model, the business requirements should be the absolute focus. This often means that the most useful domain models are created before a physical model is made to store/relate/normalize the data involved. You should seek to keep your persistence layer (physical model) as loosely coupled to your domain model as possible. That's the entire point of a layered architecture right?

While I agree with the sensibilities put forth in the link you provided, it is simply a poor design choice to give your necessarily anemic persistence model (ORM) the responsibility of being your domain model. An ORM is a tool not a solution, and not something your domain should be aware of in any way, shape, or form (it needn't know nor care if/how it's persisted).

You, yourself, have already started running into the problems of mixing the two. By using your persistence model as your domain model, it is preventing you from properly modeling business processes that are dependent on data from two entities (clearly the data in this case should be combined into a single domain entity). This is precisely the reason mixing the two doesn't work. Your application is going to devolve into a system where your "models" are simply bags of data being passed around as arguments to "rules/processes". That separation of data and behavior is fundamentally procedural in nature, not OOP, and certainly not DDD.

user3347715
  • 3,084
  • 11
  • 16
  • This is quite radical POV. Many disagree with this pursuit of purity as impractical, eg https://enterprisecraftsmanship.com/2016/04/05/having-the-domain-model-separate-from-the-persistence-model/ , https://softwareengineering.stackexchange.com/questions/313188/if-repository-pattern-is-overkill-for-modern-orms-ef-nhibernate-what-is-a-be , https://stackoverflow.com/questions/14024912/ddd-persistence-model-and-domain-model . Entity remains entity - there is almost no difference between ORM model class (per Entity in SQLAlchemy) and "pure" counterpart. Infra is almost invisible, by design. – Roman Susi Jul 12 '18 at 19:53
  • Imagine, I have a separate domain model. Where do I put multi-entity functionality then? I do not see any natural candidates in the domain itself. – Roman Susi Jul 12 '18 at 19:55
  • 3
    This is not a radical POV. It is precisely how DDD is meant to be done. Period. It's only impractical to create a multi-layered, highly-cohesive, low-coupled system if your application is trivial. As for your problem, it kind of depends what you mean by "multi-entity functionality". The simple answer is this problem would not exist. Why would I purposely split functionality between multiple entities when modeling my domain? I would essentially have to create the problem for it to exist. If you are referring to "process" instead of "data dependencies", then I would use a `ProcessManager`. – user3347715 Jul 12 '18 at 20:19
  • Maybe, my multi-entity operation is a Service - "When an operation does not conceptually belong to any object." (from wikipedia on DDD). An alternative is to have a "god" entity, which certainly does not belong to the domain, at least in my case. – Roman Susi Jul 12 '18 at 20:30
  • The important bit of that quote is "any object". Are you sure you have modeled your domain correctly? That's (part of) the point I'm making above. So many questions posted here have a developer looking for some missing technical detail to solve their problem when the issue is simple: the model they have created is insufficient and is not a useful abstraction of their business rules. Be careful when implementing a domain service. Overuse of services leads to the exact same anemic model I'm warning you against above. Leaning on too many services is usually a symptom of a bigger problem. – user3347715 Jul 12 '18 at 20:43
  • User deletes himself from the system. Whose behavior is that? User does a lot of things in the system - should I put all that behavior into it's class? If not, should I create some artificial classes? This sounds a bit strange and counterintuitive and violates SOLID principles, I guess. Of course, I agree on abusing services, but as for modeling - it's a work in progress. Something is still anemic, something is not yet added to the domain model due to lack of cases. – Roman Susi Jul 12 '18 at 20:53
  • Services are best used for operations that require the coordination of domain objects, not when you simply need state from two places. For example, when withdrawing money from an ATM: The money must be in the ATM (not out of cash), and can only be dispensed if the funds are available and debited from your account. Therefore, the `withdrawFunds` method can't exist on either the `Account` or the `ATM` (because they don't know one another). Instead an `ATMService` can be introduced to coordinate the *multiple* transactions (dispenseMoney, debitAccount) that need to occur as part of one use-case. – user3347715 Jul 12 '18 at 20:56
  • Even if 80% of cases can be handled by a single entity (and it's related value objects), the rest of the cases requires coordination of multiple domain objects. (I hope you do not suggest application should issue commands separately just to be able to deal with one entity at a time?). So, there is a need for Services. – Roman Susi Jul 12 '18 at 21:03
  • `User` is unlikely to be a good candidate for a domain object because it probably encompasses too much knowledge and doesn't denote any behavior. What do your `Users` do? Do they buy stuff? `Buyer`. Review things. `Reviewer`. Comment? `Commentor`. You get it. Data should be partitioned *vertically* according to behavior. Maybe you have an `AccountManager` to hold a certain subset of behavior. Obviously the modeling requirements are unique to your system. I'm just trying to get the ball rolling. Services will exist. Just remember that your model is "discovered", not decided upon. – user3347715 Jul 12 '18 at 21:07
  • Good point. The system was not designed by DDD from the start, I am just trying to bring it nearer to the extent it is practical. As for User roles, it seems that at current rate it moves too fast to the genericity, so roles will soon be user-defined... Many other entities are also moving to be generic - it's coming from business cases, "discovered". – Roman Susi Jul 12 '18 at 21:16
  • Also from the DDD book I mentioned: "An anemic model is similar to the domain model pattern; however, the model is devoid of any behavior. It is purely a model of the state of an object all behavior resides in service classes that modify." - used in functional programming approach. So definitely, there is space for different domain logic patterns (when isolated from surrounding layers). – Roman Susi Jul 12 '18 at 21:24
  • Good on you for tackling it head on! If you haven't already read the blue book by Eric Evans (specifically the chapters on evolving legacy systems), I would highly recommend it for your situation. Cheers and good luck! – user3347715 Jul 12 '18 at 21:24
  • Thanks. I can accept this answer even though I can't fully follow the advice right away. Nonetheless, I will rethink my approach and compromises. – Roman Susi Jul 13 '18 at 10:39
0

An article with lots of arguments why DM should be separate from PM (persistence model):

https://www.mehdi-khalili.com/orm-anti-patterns-part-4-persistence-domain-model by Mehdi Khalili.

Cited highlights:

  • PM is a property bag while DM is about business logic and behavior
  • Business logic is far more testable in DM than PM
  • PM looks like database while DM should look like the business domain
  • PM (typically) has dependencies on ORM while DM is POCO
  • PM has DB related constraints while DM is about business rules and constraints
  • PM can enter invalid state while DM should not allow invalid state even temporarily

(POCO - plain old ... object)

Roman Susi
  • 1,763
  • 2
  • 12
  • 20