Skip to content

Commit ea2b8a2

Browse files
authored
chore(docs): Document client structure
* Document client structure * Added warning. * PR feedback.
1 parent 03a079f commit ea2b8a2

File tree

1 file changed

+68
-1
lines changed

1 file changed

+68
-1
lines changed

docs/architecture/sdk/internal/index.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ across both mobile and web platforms. It will cover best practices for structuri
1010
platform-specific challenges, and ensuring that your implementation works seamlessly across
1111
Bitwarden’s mobile and web applications.
1212

13-
## Architecture
13+
## Crate structure
1414

1515
The internal SDK is structured as a single
1616
[Git repository](https://github.com/bitwarden/sdk-internal) with multiple internal crates. This
@@ -145,6 +145,73 @@ grouped into application interfaces for consumption. See the
145145
[`VaultClient`](https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-vault/src/vault_client.rs)
146146
as as example.
147147

148+
## Client structure
149+
150+
One of the core concepts of our SDK is the "client". The client groups the SDK API surface into
151+
domain-specific bundles for easier instantiation and use by the consuming application.
152+
153+
There are two recommended approaches for structuring a client, depending on the size of the domain.
154+
155+
### Single file
156+
157+
Define the client struct, its initialization, and all method `impl` blocks in one file. This
158+
minimizes indirection and keeps related code easy to discover. Prefer this structure when the file
159+
is manageable in size (~500 lines, including tests).
160+
161+
```
162+
domain_client.rs
163+
├── DomainClient struct definition and initialization
164+
└── impl DomainClient with full method implementations and tests
165+
```
166+
167+
### Per-method files or subdirectories
168+
169+
When the single file would otherwise become unwieldy (~500 lines, including tests), the client
170+
definition should be split from individual method implementations.
171+
172+
Define the client struct in one file and each method in either its own file or its own subdirectory,
173+
depending on the implementation complexity.
174+
175+
When each method is self-contained and does not require supporting types alongside it, individual
176+
methods can be split into separate files.
177+
178+
```
179+
domain/
180+
├── domain_client.rs # DomainClient struct definition and initialization
181+
├── mod.rs
182+
├── method_name.rs # impl DomainClient { fn method_name() } and tests
183+
└── other_method.rs # impl DomainClient { fn other_method() } and tests
184+
```
185+
186+
For more complex clients, subdirectories can be used to contain the `impl DomainClient` block for
187+
that method, its tests, and any supporting types.
188+
189+
```
190+
domain/
191+
├── domain_client.rs # DomainClient struct definition and initialization
192+
├── mod.rs
193+
└── method_name/
194+
├── mod.rs
195+
├── method_name.rs # impl DomainClient { fn method_name() } and tests
196+
└── request.rs # supporting types (errors, etc.)
197+
```
198+
199+
:::warning
200+
201+
Avoid the thin passthrough pattern, where the client delegates to free functions defined elsewhere.
202+
This creates unnecessary indirection and splits documentation away from the API surface.
203+
204+
```rust
205+
impl LoginClient {
206+
// Avoid delegating the entire implementation to another function like this.
207+
pub async fn login_with_password(&self, data: LoginData) -> Result<()> {
208+
login_with_password(self.client, data).await
209+
}
210+
}
211+
```
212+
213+
:::
214+
148215
## Language bindings
149216

150217
The internal SDK supports mobile and web platforms and uses UniFFI and `wasm-bindgen` to generate

0 commit comments

Comments
 (0)