Storage is the lower part of the model. It will typically be driven by services.
Generic libraries like sqlite or JSON are not part of Storage, but simply external libraries used by our Storage code. With Storage, we mean implementations of our generic interfaces, that read and write data to/from disk or network.
In most cases, the best design pattern is to define a generic interface, e.g. an AddressBook, and then to subclass this generic interface for concrete implementations.
- E.g. the MDBAddressBook stores data in MDB files that Thunderbird 52 uses. The MDBAddressBook class has an async function load(filename), which reads the data from the file and creates AddressCard objects, and populates the AddressBook.addresses collection. Observers of the AddressBook.addresses collection would be notified of the new cards, and the UI would display them. There’s also a save() function, which writes the list back to the file.
- The AddressBookService would be instantiating the MDBAddressBook object and call load().
- The UI does not need to know that there is an MDBAddressBook, because the AB UI only lists the generic AddressBooks from the AddressBookService.
This way, the storage is decoupled from the generic interface and from the rest of the application.
The UI does not even need to implement async load handlers, because the generic collection observer API already takes care of that. All the UI does is list the address books and all the cards in each address book. The generic list widget will listen to additions and removals in the lists of address books and cards and update the UI accordingly.
This means only the AddressBookService and the importer/exporter code needs to know about a specific storage backend.