michangarrito/docs/97-adr/ADR-0006-storage-abstraction.md
rckrdmrd 2c916e75e5 [SIMCO-V4] feat: Agregar documentación SaaS, ADRs e integraciones
Nuevas Épicas (MCH-029 a MCH-033):
- Infraestructura SaaS multi-tenant
- Auth Social (OAuth2)
- Auditoría Empresarial
- Feature Flags
- Onboarding Wizard

Nuevas Integraciones (INT-010 a INT-014):
- Email Providers (SendGrid, Mailgun, SES)
- Storage Cloud (S3, GCS, Azure)
- OAuth Social
- Redis Cache
- Webhooks Outbound

Nuevos ADRs (0004 a 0011):
- Notifications Realtime
- Feature Flags Strategy
- Storage Abstraction
- Webhook Retry Strategy
- Audit Log Retention
- Rate Limiting
- OAuth Social Implementation
- Email Multi-provider

Actualizados:
- MASTER_INVENTORY.yml
- CONTEXT-MAP.yml
- HERENCIA-SIMCO.md
- Mapas de documentación

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 01:43:15 -06:00

210 lines
4.5 KiB
Markdown

---
id: ADR-0006
type: ADR
title: "Storage Abstraction"
status: Accepted
decision_date: 2026-01-10
updated_at: 2026-01-10
simco_version: "4.0.1"
stakeholders:
- "Equipo MiChangarrito"
tags:
- storage
- s3
- r2
- abstraction
- multi-cloud
---
# ADR-0006: Storage Abstraction
## Metadata
| Campo | Valor |
|-------|-------|
| **ID** | ADR-0006 |
| **Estado** | Accepted |
| **Fecha** | 2026-01-10 |
| **Autor** | Architecture Team |
| **Supersede** | - |
---
## Contexto
MiChangarrito necesita almacenar archivos (imagenes de productos, facturas, documentos) en la nube. Queremos:
1. Flexibilidad para cambiar de proveedor
2. Desarrollo local sin depender de cloud
3. Optimizacion de costos (R2 es mas barato que S3)
---
## Decision
**Implementamos una capa de abstraccion con Factory Pattern que soporta S3, Cloudflare R2 y MinIO.**
Todos los proveedores implementan la misma interfaz, permitiendo cambiar de proveedor sin modificar codigo de negocio.
```typescript
interface StorageProvider {
upload(key: string, file: Buffer, options?: UploadOptions): Promise<UploadResult>;
download(key: string): Promise<Buffer>;
delete(key: string): Promise<void>;
getSignedUrl(key: string, expiresIn: number): Promise<string>;
list(prefix: string): Promise<FileInfo[]>;
}
```
---
## Alternativas Consideradas
### Opcion 1: Usar S3 directamente
- **Pros:**
- Simple
- Bien documentado
- **Cons:**
- Vendor lock-in
- Sin desarrollo local facil
- Costos pueden ser altos
### Opcion 2: Abstraccion con Factory (Elegida)
- **Pros:**
- Flexibilidad de proveedor
- MinIO para desarrollo local
- Optimizacion de costos con R2
- Codigo limpio
- **Cons:**
- Complejidad adicional
- Mantener multiples providers
### Opcion 3: Usar libreria existente (flydrive)
- **Pros:**
- Ya implementado
- Multiples drivers
- **Cons:**
- Dependencia externa
- Menos control
- Puede no cubrir todos nuestros casos
---
## Consecuencias
### Positivas
1. **Flexibilidad:** Cambiar proveedor sin impacto en negocio
2. **Desarrollo:** MinIO local sin credenciales cloud
3. **Costos:** Migrar a R2 reduce costos ~75%
4. **Testing:** Facil mock de storage
### Negativas
1. **Complejidad:** Mantener 3 implementaciones
2. **Features especificos:** Podemos perder algunas features unicas
---
## Implementacion
### Factory
```typescript
@Injectable()
export class StorageFactory {
create(provider: StorageProviderType): StorageProvider {
switch (provider) {
case 's3':
return new S3StorageProvider(this.configService);
case 'r2':
return new R2StorageProvider(this.configService);
case 'minio':
return new MinIOStorageProvider(this.configService);
default:
throw new Error(`Unknown storage provider: ${provider}`);
}
}
}
```
### Provider S3
```typescript
class S3StorageProvider implements StorageProvider {
private client: S3Client;
constructor(config: ConfigService) {
this.client = new S3Client({
region: config.get('S3_REGION'),
credentials: {
accessKeyId: config.get('S3_ACCESS_KEY'),
secretAccessKey: config.get('S3_SECRET_KEY'),
},
});
}
async upload(key: string, file: Buffer, options?: UploadOptions): Promise<UploadResult> {
const command = new PutObjectCommand({
Bucket: this.bucket,
Key: key,
Body: file,
ContentType: options?.contentType,
});
await this.client.send(command);
return {
key,
url: `https://${this.bucket}.s3.amazonaws.com/${key}`,
};
}
// ... otros metodos
}
```
### Uso
```typescript
@Injectable()
export class FileService {
constructor(private readonly storageFactory: StorageFactory) {}
async uploadProductImage(productId: string, file: Buffer): Promise<string> {
const storage = this.storageFactory.create(process.env.STORAGE_PROVIDER);
const key = `products/${productId}/image.jpg`;
const result = await storage.upload(key, file, {
contentType: 'image/jpeg',
});
return result.url;
}
}
```
---
## Configuracion por Ambiente
| Ambiente | Proveedor | Razon |
|----------|-----------|-------|
| Development | MinIO | Local, sin credenciales |
| Staging | R2 | Costos bajos |
| Production | S3 o R2 | Segun necesidad |
---
## Referencias
- [AWS S3 SDK](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/)
- [Cloudflare R2](https://developers.cloudflare.com/r2/)
- [MinIO](https://min.io/)
- [INT-011: Storage Cloud](../02-integraciones/INT-011-storage-cloud.md)
---
**Fecha decision:** 2026-01-10
**Autores:** Architecture Team