## Classes
Classes were designed based on domain-driven design (DDD). The main entity is a blob lifecycle (equivalent to a logical blob). 

```mermaid
classDiagram
    class VOBlobURI {
        logical_path: str
        logical_name: str
        version: str
    }
    class VOBlobMetadata {
        uri: VOBlobURI
        size_bytes: int
        created_at: datetime
        tags: Dict[str, str]
    }
    class ENTBlobLifecycle {
        logical_name: str
        logical_path: str
        versions: List[VOBlobMetadata]
        content_type: Optional[str]
    }
    class BlobStoragePort~TStorageObject~ {
        <<abstract>>
        upload_from_file()
        upload_from_memory()
        download_to_file()
        download_to_memory()
        download_as_stream()
        list_blobs()
        delete()
        get_lifecycle_entity()
    }
    class AzureBlobAdaptor {
        <<implements BlobStoragePort[AzureBlobProperties]>>
    }
    class LocalFilesystemAdaptor {
        <<implements BlobStoragePort[pathlib.Path]>>
    }

    VOBlobMetadata --> VOBlobURI
    ENTBlobLifecycle --> VOBlobMetadata
    BlobStoragePort --> ENTBlobLifecycle
    AzureBlobAdaptor --|> BlobStoragePort
    LocalFilesystemAdaptor --|> BlobStoragePort
```

## Decision for Versioning
After consultation with Lennard, we came to a decision to use built-in versioning for cloud adapters, and filename suffixing with content hash for local filesystem adapter. This is to limit the magical abstractions to local. 

Local filesystem will essentially have different files for different versions of a blob. 

E.g. `folder1/file_123.txt` and `folder1/file_345.txt` are two versions of a logical blob with path `folder1/file.txt`, where `123` and `345` are content hashes. 

Cloud blob adapters will use built-in versioning instead. This way, when our developers use this package, they can expect to retrieve the right files, even if they access blob directly. One logical blob will most likely be the same as a blob on the cloud, depending on the cloud versioning implementation. 

## Gotchas
### Azure Blob Storage
- With soft delete and versioning enabled, deleting a blob means making the current version a previous version. In other words, there exists no current version. All versions are previous versions. 
- There are 2 types of blob versions: 
    - **Previous**: A previous version. 
    - **Deleted**: A deleted version that will be deleted after the soft delete retention period (default = 7 days).
- **Order of deletion** is important. Deleting versions, then the blob itself, will not mark the blob for deletion! 🤯 Deleting the blob first, then its versions instead, will. You can see this difference by looking at the retention period. The former will have `-` while the latter will have some number. 
- Deleting a blob does **NOT** mean that the versions will be deleted. We thus need to explicitly delete the blob and its versions.
- When undeleting/restoring soft deleted blob versions, all other previous soft deleted versions will be undeleted as well.
- The latest previous version of a deleted blob cannot be undeleted/restored. We have to copy it instead and duplicate it. So when restoring a blob, we end up with 2 copies of the most recent version + 1 copy of each older version (new total = `n + 1` vs original total = `n`). **This is unavoidable**.
- When listing blobs, metadata is **NOT** retrieved even though it exists as an attribute in the returned object. We have to call `get_blob_properties` to retrieve the metadata individually. 