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>
4.5 KiB
4.5 KiB
| id | type | title | status | decision_date | updated_at | simco_version | stakeholders | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ADR-0006 | ADR | Storage Abstraction | Accepted | 2026-01-10 | 2026-01-10 | 4.0.1 |
|
|
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:
- Flexibilidad para cambiar de proveedor
- Desarrollo local sin depender de cloud
- 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.
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
- Flexibilidad: Cambiar proveedor sin impacto en negocio
- Desarrollo: MinIO local sin credenciales cloud
- Costos: Migrar a R2 reduce costos ~75%
- Testing: Facil mock de storage
Negativas
- Complejidad: Mantener 3 implementaciones
- Features especificos: Podemos perder algunas features unicas
Implementacion
Factory
@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
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
@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
Fecha decision: 2026-01-10 Autores: Architecture Team