Python task manager from scratch, part 40: Persisting task coordinators

The job now is to fill out the persistence mechanisms relevant to KickoffCoordinators. You've seen a lot of this before, when we've persisted Tasks. A lot of the overall approach here is the same.

As you can see, I've made some changes to the initialization of KickoffCoordinator and to the object itself. Here's one problem I faced here and have faced before: When one domain object refers to another, how do you represent the dependency at the object level? Here: should we have Task be a field on KickoffCoordinator?

If we don't do this, and instead just store the task's UUID, our KickoffCoordinator domain object isn't as faithful to reality as we'd like it to be.

But if we do store the whole Task on the coordinator, how do we handle the persistence of that task?

  1. We can persist the whole Task with the coordinator, but we're already persisting it in its own repository. We could store the information in two places, but that raises a ton of problems (besides the wasted space, we have to worry about keeping the different representations in sync, which is the stuff of thick textbooks).
  2. Same as above, but we can only store the Task information with its coordinator and get rid of the other storage. But now we're coupling together both the actual persistence of those objects and the corresponding repositories. This is only appropriate if the objects are much more closely connected at the domain level thatn our coordinators and tasks are. (Think about how we'd store tasks that don't have corresponding coordinators; it could be done, but it would be a mess.)
  3. We can persist just the UUID of the task on the coordinator, and every time we retrieve a coordinator we also retrieve the corresponding task from its repository and construct the KickoffCoordinator object. This is unwieldy and also couples together the functioning of the two repositories. (This is an important point: coupling and dependencies can occur even if the dependency isn't a matter of an explicit code import. If the actual functioning of a coordinator's repository depends on a task repository, or--worse--some specific task repository, our system is much less modular and harder to reason about than we want it to be.)
  4. We can persist just the UUID of the task on the coordinator, only retrieving the whole Task when the situation demands it. Here, it's the responsibility of the user of the coordinator to summon up the whole task if that's necessary. It is not required to do so.
  5. We can create "thin" and "think" coordinator objects, the former of which holds only the UUID of the corresponding task and the latter of which holds the whole thing.

I chose the second-to-last option, storing just the UUID and accepting a slightly impoverished KickoffCoordinator object. I think these situations are legitimately tricky to get right, but the more I think about it the more I'm happy with this one.

Whether I got this one right or wrong, the persistence and related testing is in place.

Here's the current commit in the veery/ repository.

Home page