pytest-orm-boundaries: catching ORM queries that violate DDD aggregate rules

Hi everyone,

I’m Evgeniia, and I work on a legacy Django monolith.

We’re gradually moving towards DDD, import linters help us control dependencies, but old code can still reach surprising places through the ORM:

Purchase.objects.get(client__name="John")

Here, Purchase and Client belong to different aggregates, but the query joins them.

I built pytest-orm-boundaries plugin to detect such crossings. You map Django models to aggregates, and the plugin inspects the ORM activity executed during pytest - including select_related, prefetch_related, subqueries, and raw SQL.

The trade-off is that the relevant code must be exercised by a database-reaching test.

The project is on alpha stage, and I’d really appreciate feedback: would this be useful in your projects? What would it need to support?

GitHub: GitHub - evchibisova/pytest-orm-boundaries: Enforce the DDD aggregate boundaries you define in your ORM · GitHub
PyPI: pytest-orm-boundaries · PyPI

I’m curious, with the example Purchase.objects.get(client__name="John"), how should that be implemented?

@CodenameTim Hello!
Do you mean how this example could be implemented if Purchase and Client belong to different aggregates?

Something like:
```
client_id = client_repository.get_id_by_name(“John”)
purchase = purchase_repository.get_by_client_id(client_id)
```
Here, an application service coordinates the two aggregate repositories without querying across the aggregate boundary directly.

Sorry if I misunderstood your question.

I think you understood me properly. I don’t do much with DDD, so I was curious what the “correct” solution was for the given example. Thank you for that!

Sure!
If purchase has a client_id FK - we can first find client by name through client aggregate/repo, and then use the resulting client_id to look up the purchase.

Don’t allow aggregates call each other, only “parent” service can call both of them for making their connections explicit and easier to control, that’s the idea