Goal
My goal for today is to add an internal interface (Rust type) for access a backup repository, whether it's on local dis or on a server.
Plan
- Define a type or trait for repository access.
- Implement it for local repository.
- Implement it for remote repository.
- Change the client to use new interface.
Notes
Refactor existing code
The current type for using a local repository has the following interface:
impl BackupRepository { pub fn is_init(dir: &Path) -> bool {...} pub fn init(dir: &Path) -> Result<Self, RepositoryError> {...} pub fn open(dir: &Path) -> Result<Self, RepositoryError> {...} pub fn all_chunks(&self) -> Result<Vec<Metadata>, RepositoryError> {...} pub fn add_chunk(&self, chunk: &Chunk) -> Result<(), RepositoryError> {...} pub fn get_data_chunk(&self, id: &Id) -> Result<Chunk, RepositoryError> {...} pub fn get_client_chunk(&self, id: &Id) -> Result<Chunk, RepositoryError> {...} pub fn open_client( &self, engine: &Engine, wanted: &str, ) -> Result<(Id, ClientChunk), RepositoryError> {...} pub fn get_credential_chunk(&self, id: &Id) -> Result<Credential, RepositoryError> {...} pub fn remove_chunk(&self, id: &Id) -> Result<(), RepositoryError> {...} pub fn find_chunks(&self, label: &Label) -> Result<Vec<Metadata>, RepositoryError> {...} pub fn find_client_chunks(&self) -> Result<Vec<Metadata>, RepositoryError> {...} pub fn find_our_client_chunks( &self, engine: &Engine, ) -> Result<Vec<ClientChunk>, RepositoryError> {...} pub fn find_credential_chunks(&self) -> Result<Vec<Metadata>, RepositoryError> {...} pub fn get_credential_chunks(&self) -> Result<Vec<Credential>, RepositoryError> {...} pub fn chunk_filename(&self, id: &Id) -> PathBuf {...} }This mixes two concerns: lower level details of chunk storage in a local directory and higher level concepts of different kinds of chunks. These should be separated. I also want to rename some of the methods. A twist I need to consider is that the lower level details are already split into "blob storage" versus "chunk storage".
The server needs the blob storage, because it doesn't deal with chunks as chunks. I'll keep the existing
BlobRepositoryas is today.The local repository builds on top of the blob repository.
impl LocalRepository { pub fn new(blobs: BlobRepository) -> Self {...} pub fn get(&self, id: &Id) -> Result<Chunk, LocalRepositoryError> {...} pub fn put(&self, chunk: &Chunk) -> Result<(), LocalRepositoryError> {...} pub fn delete(&self, id: &Id) -> Result<(), LocalRepositoryError> {...} pub fn find(&self, label: &Label) -> Result<Vec<Id>, LocalRepositoryError> {...} }I already have an
ApiClienttype with about the same interface. I may want to add a trait to make it easier to use them interchangeably. The alternative is to have anenumthat is either a local or remote repository. A trait might make it easier to add more types of repositories. Anenumrequires them to be in the same source tree. Anenumalso needs to have "dispatcher" methods for accessing the underlying type. I'll go with a trait, at least for now.pub trait BackupRepository { type Error; pub fn get(&self, id: &Id) -> Result<Chunk, Self::Error> {...} pub fn put(&self, chunk: &Chunk) -> Result<(), Self::Error> {...} pub fn delete(&self, id: &Id) -> Result<(), Self::Error> {...} pub fn find(&self, label: &Label) -> Result<Vec<Id>, Self::Error> {...} }On top of these two types, I'll want the convenient higher level operations in their own type. Naming it is difficult. I now have
LocalRepositoryandApiClienttypes and aBackupRepositorytrait to unite them. What should the next level of abstraction type be called?I don't like "logical repository" or "helper repository". Those aren't clear and do not capture what the type is actually for. It's actually for managing backup client chunks, and the related concept of credentials. I'll go with
ClientRepositoryfor now.impl ClientRepository { pub fn new(lower: impl BackupRepository) -> Self {...} pub fn get_client(&self, id: &Id, engine: &Engine) -> Result<ClientChunk, ClientRepositoryError> {...} pub fn put_client(&self, client: ClientChunk) -> Result<ClientChunk, ClientRepositoryError> {...} pub fn delete_client(&self, id: &Id) -> Result<(), ClientRepositoryError> {...} pub fn find_client_chunks(&self) -> Result<Id, ClientRepositoryError> {...} pub fn find_our_client_chunks(&self, engine: &Engine) -> Result<Id, ClientRepositoryError> {...} pub fn get_credential(&self, id: &Id) -> Result<Credential, ClientRepositoryError> {...} pub fn put_credential(&self, credential: &Credential) -> Result<CredentialChunk, ClientRepositoryError> {...} pub fn delete_credential(&self, id: &Id) -> Result<(), ClientRepositoryError> {...} pub fn find_credential_chunks(&self) -> Result<Id, ClientRepositoryError> {...} pub fn find_credentials(&self) -> Result<Vec<Id>, ClientRepositoryError> {...} }I have a goal. I need a plan to reach it.
- create
LocalRepositoryfrom currentBackupRepository - create
ClientRepositoryusingLocalRepository, from currentBackupRepository - remove
BackupRepository; up until this, nothing should break - add trait
BackupRepository - change
ClientRepositoryto use the trait - change client code to use new types
- create
Create branch
client-repo. Tests pass.Added
LocalRepositoryby copying the currentBackupRepository, dropping unwanted code, renaming methods, and tidying up.Started on
ClientRepositoryand added credentials. Noticed the lack of types for different kinds of chunks, which may need to be added later. Also, the client repository does not do encryption and decryption itself, and that makes it a little harder to use. May want to address that too, later.Added client chunks to
ClientRepository.Ran out of time.
Summary
Made a plan for how to implement the internal interface for using backup repositories local and remote. Ran out of time, but no serious obstacles. Will continue next time.
Support?
If you'd like to fund Obnam development, see my funding page. My high level goal is described on the architecture page. What is most important about backup software to you?