Concepts
Every OpenDAL API, in every language, follows one model:
Configure a service, build an operator, optionally wrap it with layers, then call operations.
Service
A service is a storage backend: S3, Google Cloud Storage, Azure Blob, a local filesystem, an in-memory store — more than 50 in total.
You never talk to a service directly. You describe it with plain configuration
— bucket, root, endpoint, credentials — and OpenDAL builds the client for you.
Because a service is only configuration, switching backends is a configuration
change, not a code change: s3 in production, fs on your laptop, memory
in tests.
Operator
The operator is the entry point for everything. It is built from one service's configuration, and every storage call in your application goes through it.
Operators are lightweight handles: cheap to create, and safe to share across threads and tasks. There are no connections to pool or cursors to manage. One operator maps to one service and one root path — to work with two buckets, build two operators.
Layer
A layer adds behavior around an operator: automatic retry, logging, timeouts, metrics, concurrency limits.
Layers nest like an onion: every operation passes through every layer on the way in and on the way out. Cross-cutting concerns are composed once at startup instead of being scattered through your code. Applying a layer returns a new operator and leaves the original untouched.
Operation
Operations are the verbs: read, write, stat, list, delete, copy,
rename, and friends. They are the same on every service, so code written
against one backend runs against any other.
Paths follow one rule everywhere: they are always relative to the operator's
root, and a trailing / means a directory. logs/app/ is a directory;
logs/app is a file.
One model, every language
The same four steps, in the language you already ship:
use opendal::layers::RetryLayer;
use opendal::services::S3;
use opendal::Operator;
// 1. Describe the service.
let builder = S3::default().bucket("data");
// 2. Build the operator and 3. wrap it with layers.
let op = Operator::new(builder)?
.layer(RetryLayer::new())
.finish();
// 4. Call operations.
op.write("hello.txt", "Hello, World!").await?;
let bytes = op.read("hello.txt").await?;