5 ms·
> You can make subdirectories without making apps, and thus avoid dependency hell. It's hard to get 100% right and thus our projects always have a few lazy for
by rowanseymour 4y ago
> You can make subdirectories without making apps, and thus avoid dependency hell.
It's hard to get 100% right and thus our projects always have a few lazy foreign key relationships and inline imports to avoid cyclical imports... but I think our code is easier to manage because we try to model the dependency relationships by having things in separate apps.
> Also be wary of the formerly South, now built-in migrations stuff
Our experience from South to 4.x has been that the models/migrations system has matured significantly and is probably now the main selling point for Django for us.
- cjohnson318 4y agoI'm currently in circular import hell. My business logic has Jobs and Loads, and they both need to update each other under certain circumstances. Should these two things/monstrosities be lumped into the same app?
- hbrn 4y agoThe short answer is yes. The long answer is if two entities are updating each other you might benefit from shifting all update responsibilities to one of them. Or even to a third entity that knows about both and keeps those two isolated from each other.
- cjohnson318 4y agoWoof. Thank you so much. I like the idea of a third party, like a mediator. Would that mediator be another app? Or should it be some module sitting in the project directory? (I'm not even sure Django would import something like that.)
- hbrn 4y ago> Would that mediator be another app? Yes! It is an app that might not even have any model classes. But it will contain business logic. And it will probably speak domain language, which is great. If you're lucky, those two other apps will become pluggable. You will probably never replace them, but separation of concerns is always nice. The downside of course is that you will have 3 apps instead of 1. That's the balance you have to maintain.
- cjohnson318 4y agoSo, the way I understand it, job/services.py and load/services.py depend on mediator/mediator.py which depends on job/models.py and load/models.py, instead of job/services.py ultimately using load/services.py, and vice versa. Thanks so much!
- hbrn 4y agoThis is good enough to break circular dependencies between individual modules, but keep in mind that circular dependencies between the apps remain (e.g. job depends on mediator, mediator depends on job). I usually prefer to resolve those too. If job is small enough, all the orchestration of jobs should happen through mediator (same for load). If it's not plausible, then job can emit signals which mediator subscribes to. A good place to start is to give a more descriptive name to the mediator. Sure, it mediates between the two, but it probably does that to implement some business process. Can you name it after that process?
- cjohnson318 4y ago> ...all the orchestration of jobs should happen through mediator (same for load) So, in a smaller app, when a request comes into job/views.py or load/views.py then we immediately start working with JobLoadMediator to handle business logic between the two? I was just going to focus on specific tasks between job and load. I'll probably look into signals; I haven't used those in several years and as I recall, it felt hacky. > give a more descriptive name to the mediator When a Job is deleted, it needs to delete associated Loads. And the states of the Loads will affect the state of the Job. That's the main cycle I'm looking at right now.
- hbrn 4y ago> when a request comes into job/views.py or load/views.py then we immediately start working with JobLoadMediator to handle business logic between the two? In my world, mediator.views and job.views would likely have different audiences. mediator.views is for business domain (e.g. your API). Though name would not be mediator, it would be something domain-specific. job.views could be for more low-level internal tooling (e.g. analytics/monitoring). Or it could be empty, if job is just dumb data object that has a lifecycle but doesn't require public API. > When a Job is deleted, it needs to delete associated Loads. And the states of the Loads will affect the state of the Job If you want to keep them apart, signals is the right (though not the easiest) answer. job owns (depends on) loads, and subscribes to loads signals. load doesn't know anything about job (ignore the fact that job is referenced in load.models as foreign key, that's just limitations of SQL DDL). The easiest is a subtle dependency: load can access job through foreign key, job can access loads through related_name. Circular dependency is still there, but it is resolved at runtime. This will start to cause pain when application grows, but is fine at small scale. Though again, I'd probably merge those apps: if you can't reason about load without job, and can't reason about job without load, there's very little benefit in keeping them separate. It might make you feel more organized, but the if the code is lying about your mental model, this is false organization.
- rowanseymour 4y agoWell you're writing Python so you're never truly stuck when you run into dependency issues, but if you feel like you're in hell then maybe they do belong in the same app. All complex real world apps have complex dependencies between entities - all I would argue is that "put them all in the same package" generally isn't the most scalable solution.