Hacker News new | ask | show | jobs
by matsemann 1254 days ago
> If `course.has_finished` is a property of the course, why would you want to have a separate function outside of the class?

Because one should avoid passing Django models around. It leads to bad design. Have a selector or something that uses the ORM, but exposes some dataclass or pydantic model instead, and put the logic there.

2 comments

Keep in mind that passing around querysets has performance advantages you wouldn't get by passing around dataclasses or similar.

For example, if you do a query like Model.objects.filter(related_model__in=RelatedModel.objects.filter(...)) the ORM will only run a single query, silently converting the second one into a JOIN.

If you pass lists of "RelatedModel" however you would've had to first one one query to get that list (raising potential edge-cases with regards to atomicity and transactional isolation) and then pass the list of IDs to the outer query in an "WHERE related_model_id IN (...)", resulting in 2 queries in the end.

That gain is often lost by people doing unoptimized queries all over the place, though, instead of a single place where the queries are optimized. And passing the fat django objects around often lead to accidental n+1 queries, since you can't really trust that looking up a property on your object doesn't do a new query. Often nicer to avoid it all, by having a gated access to the DB.

While I propose most often sending in a list of related IDs (premature optimization and all that), the function could just accept any iterable, and you from the outside could send in the lazy relatedmodel query.

You want to add an ORM to the ORM? Why?
How is that adding an ORM to the ORM? I want all django orm access to happen at defined places, instead of the spaghetti mess it is when people do SomeOtherModulesModel.objects.filter(..) and expose themselves to the internal workings of that module. Access it through a selector instead.
If for some strange reason your application has data that it was not created with Django, sure.

But aside from that you are just adding another layer of abstraction that does not give any benefit when all your models are managed by Django already.

It gives a huge benefit, and not doing it is why most django code is incomprehensible and slow.

No longer should anyone in their module directly do something to a model and save it. They should always go through a service in the module owning that model, that makes sure everything is done correctly. So services.py and selectors.py works as a public API for the module, while the models are internal. Avoids having lots of other apps/modules depending on your app's internals.

> not doing it is why most django code is incomprehensible and slow.

> No longer should anyone in their module

> they should always go through a service

Weasel words and opinions-as-fact. Come back when you have a way to show that your approach gives any actual benefit.

I did give an example. What you call opinions-as-fact was me trying to explain the approach, not commanding anything. That you disagree (or can't comprehend it?) doesn't make it weasel words. Please don't behave like this towards me, read the guidelines. You can find a link at the bottom of this page. I'll leave this "discussion" here as it's unfruitful when you act so hostile.
An orm takes a selector (typically an sql query) and maps it onto an object.

What you're describing takes a selector, and maps it onto an object. Is it just that you want type hints or something?

No, what I'm describing is functions in a selector.py, like: def get_orders_for_date(date) -> Order:

where Order is a pydantic model, not a fat django model. Other modules shouldn't know about my internal database. All other modules should use functions from this selector.py, they aren't allowed to use the OrderModel themselves directly, only the pydantic class. Because otherwise you end up with spaghetti.

Right... so you're talking about mapping an object from your database to a pydantic object.

So you want an ORM, but you want it without a save method? Or presumably with a save method that can only be called under specific circumstances?