Consider a storage abstraction:
interface BlobStore:
method put(key: String, data: Bytes) -> Result
method get(key: String) -> Result<Bytes>
method delete(key: String) -> Result
method exists(key: String) -> Boolean
Two implementations:
class LocalFileBlobStore implements BlobStore:
method put(key, data):
path = join(base_dir, key)
write_file(path, data)
return Success
method get(key):
path = join(base_dir, key)
if not file_exists(path):
return NotFound
return read_file(path)
class S3BlobStore implements BlobStore:
method put(key, data):
s3_client.put_object(bucket, key, data)
return Success
method get(key):
try:
return s3_client.get_object(bucket, key)
catch NoSuchKey:
return NotFound
Callers depend on BlobStore. The choice of local disk or
S3 is a deployment decision invisible to callers. Testing uses
LocalFileBlobStore or an in-memory stub. Production uses
S3BlobStore. The abstraction holds.
Where does it break? If S3BlobStore.put occasionally
returns EventuallyConsistent — an S3-specific result type —
and callers start to handle it, the abstraction has leaked. Now callers
know about S3. Removing S3 requires changing callers. The interface has
become a lie.