In the last installment I noted that I'd run into a circular-dependency problem and fixed it the wrong way, by removing a type annotation instead of addressing the actual circularity. The problem, again, is:
AddTask
. It has a field for a TaskCoordinator
.TaskCoordinator
, KickoffCoordinator
, generates commands, including AddTask
, which it checks for explicitly.What is the circular dependency here? There are at least two good answers here:
commands.py
imports from coordinator.py
and coordinator.py
imports from commands.py
.AddTask
is depends on task coordinators: that's one of its fields. But coordinators depend on commands, because they need to know the commands that exist to implement the domain-specific logic required for processing them.These dependencies are related but different. You can remove one without removing the other (as I did before the last installment). Do not settle for removing dependency problems at the code level but leaving problems at the logical level. Dependency problems of all sorts are signs of mistakes in structure, and those will always cause more and worse problems if you don't address them.
The problem I got myself into is of a fairly simple structure: A requires or uses B, and B requires or uses A. Here are some generic causes of that problem:
AddTask
doesn't need to know what a TaskCoordinator
is.)The second of these is the easiest to eliminate. Our command processor needs to know what AddTask
is. It's just not a woodchipper.
The first is also, I think, wrong. I thought pretty hard before adding the coordinator object.
That leaves the third--and, I think, revisiting my reasoning for adding a coordinator object shows why the third solution is right. What information should AddTask
contain? Roughly, information about the task. Because coordinators and tasks should be different, it's too much information to add a whole TaskCoordinator
as a field. Coordinators exist in part because all sorts of complicated domain-specific logic can go into scheduling tasks. A lot of it won't be relevant to the process of adding a task.
So, for example, I'd eventually like to implement coordinators that schedule new tasks based on when I have more time available and based on when I'll be doing other tasks. Certainly not all of this information, or the mechanisms to handle it, should be involved in the process of creating a new task, even if that task will be governed by that coordination logic.
If coordinators weren't intended to include other information, the first solution would probably be better: simply get rid of them and let tasks reschedule themselves.
It often happens, as it did here, that code concerns are really metaphysical concerns. I made this problem by losing track of which things needed to exist, and why.
So, I've implemented the last alternative. Here's the current commit in the veery/
repository.