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 BlobRepository as 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 ApiClient type 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 an enum that is either a local or remote repository. A trait might make it easier to add more types of repositories. An enum requires them to be in the same source tree. An enum also 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 LocalRepository and ApiClient types and a BackupRepository trait 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 ClientRepository for 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 LocalRepository from current BackupRepository
    • create ClientRepository using LocalRepository, from current BackupRepository
    • remove BackupRepository; up until this, nothing should break
    • add trait BackupRepository
    • change ClientRepository to use the trait
    • change client code to use new types
  • Create branch client-repo. Tests pass.

  • Added LocalRepository by copying the current BackupRepository, dropping unwanted code, renaming methods, and tidying up.

  • Started on ClientRepository and 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?