Hacker News new | ask | show | jobs
by z3ugma 1654 days ago
As someone who's used both - Rails packaging and environment maintenance has been so much less of a headache. Gems and Gemfiles and Gemfile.lock vs having to choose a Python package maintainer (Pipenv vs Poetry?) and deal with that has been worth it to me.

In terms of how they "feel" to develop with? About the same. Similar ideals - skinny models, service layers, REST-first but you can make it RPC-style if you want...

3 comments

I think that was a fair criticism a year ago, and it's hard to believe it took this long to get a sane dependency management system, but now the answer is use poetry every time.
I can't recommend using Poetry in production - I've had massive headaches with it due to https://github.com/python-poetry/poetry/issues/697

Basically if you have two dependencies that depend on the same package, but depend on different versions or non-overlapping ranges of versions, Poetry's only solution is "tell the maintainers of your dependencies to update their pyproject.toml" - building your package will just fail, with no workaround other than to fork the dependencies and update pyproject.toml yourself. Yes, in principle that sounds like the right way to resolve it, but in practice there are lots of Python packages with overly narrow dependency version ranges (or that are pinned to a single version), the maintainers understandably aren't always that responsive, and forking all your dependencies isn't a great solution.

But doesn't even pip take that strict approach now?
Yes, and that's probably the right default behavior. But with pip I think you can still work around it, e.g. by installing the sub-dependency manually and then installing with --no-deps.
A couple of comments here have talked about the benefits of “skinny models”, but that runs counter to the advice I’ve seen. How come skinny models? Is the idea to put most of the business logic… where?
I'm unconvinced by the "skinny models" and "service layer" arguments. Django's ORM is an active record style ORM, its at its best if you use it that way. If you want to have a service layer type architecture, use SqlAlchemy with implements the data mapper scheme.

I follow this general rule of thumb, in order:

If it's a complicated business logic touching multiple models or external apis, put in in a `utils` module (keeping this sensibly organised by model/role/activity depending on what you are building, i.e. utils.{model_name}.{methods} or utils.{e.g. order_processing/email/accounts}.{methods}). You can add a shortcut to it as a method on the model if this makes sence. (some may argue this is a service layer)

If I'm only doing something once in one place (in one view), then keep the logic there.

If it's a custom create method, stick it on the models manager class.

If it's something you want to do to a set of objects (from a query set), put it on the manager class or a QuerySet subclass (to make it chain-able).

If you haven't put it elsewhere put it on the model class, as a method or a property if it seems cleaner (don't do get_... and set_... use a property)

The argument is that Active Record is itself a bad pattern that you should not use. And it may be. But it might be a long way down the runway before you run into the bad effects.
Yes, exactly, I agree there are valid arguments against active record, partially at scale. However, I believe, certainly when starting out on a new project/startup, sticking with the standard recommendations and patterns provided in the docs for Django and DRF you make life much easer for on boarding new people to the project later on - they will have seen it all before.

If you get big enough where the standard patterns don't work anymore, YAY! you got big, celebrate! Then start looking at how to refactor for easer maintainability for your specific use case and scale.

The problem I tend to see is that the refactoring never happens. Instead the system becomes the legacy mess nobody wants to work on and eventually it gets replaced. If you get lucky, it'll be replaced by the people who wrote the first version, but it's more likely to be replaced by a team who weren't there when the first one was built, who will build it in this-year's-shiny, without learning the fundamental lessons of why the first version ended up that way so doom the next version to a similar fate.
Some recommends Service layers, but not the total extreme extent that the service layer is flexible/transferable between frameworks/databases.

https://alexkrupp.typepad.com/sensemaking/2021/06/django-for...

https://news.ycombinator.com/item?id=27605052

IMO service objects are an OOP-cargo-cult abomination, especially the kind named after a verb with a "call" method (CreateFooFromBar#call), so the first thing I reach for to offload my business logic involving multiple models or just lots of logic are PORO domain objects with meaningful non-generic method names (a CampaignReservation with "#create!", "#persisted?" or "#available?" methods, a XlsxClientImport with "#perform" or "#check_format" methods).
As someone who knows some Python, and "gets" Django / Flask basics, what would my timeline look like for switch to Ruby / Rails?

Thanks.

One day of learning the slightly more interesting Ruby syntax, one day of learning how each thing is called in the other framework, and then a few weeks until your code in Ruby stops looking like Python (i.e. you start using the fancy Ruby stuff).
Appreciate it!