|
| 1 | +# Architectural Decision: Feature Subdirectory for S3 Tables |
| 2 | + |
| 3 | +## Decision |
| 4 | + |
| 5 | +S3 Tables support will be implemented in a **feature subdirectory** `src/s3/tables/` rather than mixing with the existing flat S3 module structure. |
| 6 | + |
| 7 | +## Context |
| 8 | + |
| 9 | +The existing MinIO Rust SDK uses a flat structure under `src/s3/`: |
| 10 | +- `src/s3/builders/` - All S3 operation builders (50+ files) |
| 11 | +- `src/s3/client/` - All S3 client methods (50+ files) |
| 12 | +- `src/s3/response/` - All S3 response types (50+ files) |
| 13 | + |
| 14 | +Each S3 operation (e.g., `CreateBucket`, `PutObject`, `GetObject`) has three corresponding files across these directories. |
| 15 | + |
| 16 | +## Rationale for Subdirectory Approach |
| 17 | + |
| 18 | +We chose to use a subdirectory `src/s3/tables/` with its own nested `builders/`, `client/`, and `response/` directories for the following reasons: |
| 19 | + |
| 20 | +### 1. Separate API Surface |
| 21 | + |
| 22 | +S3 Tables is a completely distinct API: |
| 23 | +- **Different base path**: `/tables/v1/*` vs standard S3 paths |
| 24 | +- **Different semantics**: Catalog/metadata operations vs object storage operations |
| 25 | +- **Different concepts**: Warehouses, namespaces, tables vs buckets and objects |
| 26 | + |
| 27 | +### 2. Different Client Type |
| 28 | + |
| 29 | +Tables operations use `TablesClient` (which wraps `MinioClient`) rather than direct `MinioClient` methods: |
| 30 | + |
| 31 | +```rust |
| 32 | +// S3 operations (existing) |
| 33 | +let response = client.put_object("bucket", "key") |
| 34 | + .build() |
| 35 | + .send() |
| 36 | + .await?; |
| 37 | + |
| 38 | +// Tables operations (new) |
| 39 | +let tables = TablesClient::new(client); |
| 40 | +let response = tables.create_table("warehouse", "namespace", "table") |
| 41 | + .schema(schema) |
| 42 | + .build() |
| 43 | + .send() |
| 44 | + .await?; |
| 45 | +``` |
| 46 | + |
| 47 | +This creates a natural API boundary and prevents confusion. |
| 48 | + |
| 49 | +### 3. Distinct Authentication |
| 50 | + |
| 51 | +Tables uses different authentication: |
| 52 | +- **Service name**: `s3tables` vs `s3` |
| 53 | +- **Policy actions**: `s3tables:CreateTable`, `s3tables:CreateWarehouse` vs `s3:PutObject`, `s3:CreateBucket` |
| 54 | +- **Resource format**: `bucket/{warehouse}/table` vs `bucket/key` |
| 55 | + |
| 56 | +### 4. Substantial Type System |
| 57 | + |
| 58 | +Iceberg schema types form a significant type hierarchy that deserves isolated organization: |
| 59 | +- `Schema`, `Field`, `FieldType`, `StructType`, `ListType`, `MapType` |
| 60 | +- `PartitionSpec`, `PartitionField`, `Transform` |
| 61 | +- `SortOrder`, `SortField`, `SortDirection` |
| 62 | +- `Requirement` (10+ variants) |
| 63 | +- `Update` (12+ variants) |
| 64 | +- `Snapshot`, `SnapshotRef` |
| 65 | +- Table metadata structures |
| 66 | + |
| 67 | +These types are specific to Iceberg and don't overlap with S3 concepts. |
| 68 | + |
| 69 | +### 5. Feature Flag Potential |
| 70 | + |
| 71 | +The subdirectory structure enables future feature-flagging: |
| 72 | + |
| 73 | +```toml |
| 74 | +[features] |
| 75 | +default = [] |
| 76 | +tables = [] # Optional S3 Tables / Iceberg support |
| 77 | +``` |
| 78 | + |
| 79 | +This allows users to opt-out of Tables support if they only need basic S3 operations, reducing compile time and binary size. |
| 80 | + |
| 81 | +### 6. Cognitive Load |
| 82 | + |
| 83 | +Mixing operations would create significant navigation challenges: |
| 84 | +- **S3 operations**: ~50 existing operations |
| 85 | +- **Tables operations**: ~20 new operations |
| 86 | +- **Total in flat structure**: ~70 files in each of `builders/`, `client/`, `response/` |
| 87 | + |
| 88 | +The subdirectory approach keeps related code together and makes it easier to understand the codebase. |
| 89 | + |
| 90 | +### 7. Clear Boundaries |
| 91 | + |
| 92 | +Developers can easily distinguish: |
| 93 | +- **S3 operations**: `use minio::s3::{MinioClient, builders::*}` |
| 94 | +- **Tables operations**: `use minio::s3::tables::{TablesClient, builders::*}` |
| 95 | + |
| 96 | +The import paths immediately convey which API surface is being used. |
| 97 | + |
| 98 | +## Alternative Considered: Flat Structure |
| 99 | + |
| 100 | +We considered maintaining the flat structure: |
| 101 | + |
| 102 | +``` |
| 103 | +src/s3/ |
| 104 | +├── builders/ |
| 105 | +│ ├── put_object.rs # Existing S3 |
| 106 | +│ ├── get_object.rs # Existing S3 |
| 107 | +│ ├── create_warehouse.rs # New Tables |
| 108 | +│ ├── create_table.rs # New Tables |
| 109 | +│ └── ... (70+ files total) |
| 110 | +``` |
| 111 | + |
| 112 | +**Rejected because**: |
| 113 | +- Mixes two conceptually different APIs in the same namespace |
| 114 | +- `TablesClient` methods would need to reach across module boundaries |
| 115 | +- Harder to feature-flag or maintain separately |
| 116 | +- Increased cognitive load when navigating codebase |
| 117 | +- Blurs the distinction between object storage and table catalog operations |
| 118 | + |
| 119 | +## Implementation Structure |
| 120 | + |
| 121 | +The chosen structure: |
| 122 | + |
| 123 | +``` |
| 124 | +src/s3/ |
| 125 | +├── tables/ # ← Feature subdirectory |
| 126 | +│ ├── mod.rs # Export TablesClient, types |
| 127 | +│ ├── client.rs # TablesClient definition |
| 128 | +│ ├── types.rs # Tables-specific types |
| 129 | +│ ├── error.rs # TablesError enum |
| 130 | +│ ├── iceberg.rs # Iceberg schema types |
| 131 | +│ ├── builders/ |
| 132 | +│ │ ├── mod.rs |
| 133 | +│ │ ├── create_warehouse.rs |
| 134 | +│ │ ├── create_table.rs |
| 135 | +│ │ └── ... (~20 files) |
| 136 | +│ ├── client/ |
| 137 | +│ │ ├── mod.rs |
| 138 | +│ │ ├── create_warehouse.rs |
| 139 | +│ │ ├── create_table.rs |
| 140 | +│ │ └── ... (~20 files) |
| 141 | +│ └── response/ |
| 142 | +│ ├── mod.rs |
| 143 | +│ ├── create_warehouse.rs |
| 144 | +│ ├── create_table.rs |
| 145 | +│ └── ... (~20 files) |
| 146 | +``` |
| 147 | + |
| 148 | +## Benefits |
| 149 | + |
| 150 | +1. **Modularity**: Tables can be maintained, tested, and documented independently |
| 151 | +2. **Clarity**: Import paths clearly indicate API surface |
| 152 | +3. **Scalability**: Future additions (views, materialized views) can be added to `tables/` module |
| 153 | +4. **Feature flags**: Easy to make Tables support optional |
| 154 | +5. **Cognitive boundaries**: Developers know where to find Tables-specific code |
| 155 | +6. **Type isolation**: Iceberg types don't pollute S3 namespace |
| 156 | + |
| 157 | +## Precedent |
| 158 | + |
| 159 | +This pattern is common in Rust ecosystems: |
| 160 | +- `tokio` has separate `tokio::net`, `tokio::fs`, `tokio::sync` modules |
| 161 | +- `aws-sdk-rust` has separate crates for each service |
| 162 | +- `rusoto` had separate sub-crates per AWS service |
| 163 | + |
| 164 | +## Decision Date |
| 165 | + |
| 166 | +October 2024 |
| 167 | + |
| 168 | +## Status |
| 169 | + |
| 170 | +**Accepted** - To be implemented in Phase 1 of Tables support. |
0 commit comments