In part 10 I discussed separating the domain and persistence layers. We proceeded to create an object suitable for the domain layer. Now we're going to create a class suitable for the persistence layer.
One canonical term for such a class is a "repository." This can be confusing, because the unit of code that's kept under version control with git is also called a "repository." I like the term anyway.
(Cultural-psychological aside: There's a school of software thought called "Domain-Driven Design (DDD)," and it either coined, revolutionized, or popularized the "repository" term for this sort of thing. Putting some of my theoretical commitments on the table: (i) I like DDD a lot; (ii) I have no idea whether the repositories I make are true, full-blooded DDD repositories, though I doubt it; (iii) Architecture Patterns with Python is a wonderful book but I've never been able to finish another DDD book, for reasons I'll try to explain some other time.)
There are many new language devices in today's code. Learn those that seem most important. I'll put some notes at the end. First, some more important notes:
Most essentially, the purpose of the repository is to shield off the rest of the code from persistence details. We are, again, using a simple text file as our database for now. Using a repository is one way of making this totally fine. It will, some day, come time to change this decision--no matter how carefully we make it or how large a check a procurements department writes to a cloud database provider. Then, we can either (i) change the repository without changing the interface it exposes to the rest of the code base or (ii) make a different repository conforming to the same interface, using one or the other where appropriate. Trust me, this all helps a lot. And the time to do it is now.
We're writing some tests already, because that's the best way to verify that the repository does what it's supposed to do. Note that I am alienating both the people who think there's no need to write tests until later and also the people who think that a full test suite should exist already. The tests are not a full and proper test suite. They're missing a lot of edge cases (and even non-edge cases). But they're enough to lean on for now, while we're still likely to change this code all over the place.
We're testing behavior, not implementation (as the slogan goes--you can Google it). And a great way to get into the habit of testing behavior and not implementation is to test behaviors the implementation of which you know you'll be changing!
The (frozen=True)
argument to the @dataclass
decoerator is so that we can compare two Tasks for equality--or, more specifically, so that two Tasks will come out as equal if they have equivalent descriptions and due dates. If you like, study up on why this is the case. It's a nice exercise and involves concepts that are necessary for mastering the language, but it is not essential to know right now.
The @staticmethod
decorator on TaskRepository.task_from_string()
is related to the fact that the first argument to that argument is not self
. (So if my_task
is a Task
, we can't call my_task.task_from_string()
and get my_task
implicitly used as the first argument to the method. In this case, we don't want that behavior.) Again, it involves important ideas and is worth knowing, but is not essential to know now.
Here's the current commit of the veery/ repository
.
Next post: Python task manager from scratch, part 13: Fix a major mistake