I'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?
Well 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.
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.
Woof. 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.)
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.
So, 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.
This 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?
> ...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.