Update plans
This commit is contained in:
@@ -3,14 +3,14 @@
|
||||
## Contexto
|
||||
Con la Opción B el backend genera URLs pre-firmadas (pre-signed) para que el cliente suba ficheros directamente a S3, y luego el backend crea/actualiza entidades en DB referenciando `pictureName/audioName`.
|
||||
|
||||
Esto mejora latencia y descarga la API, pero abre un vector de abuso: un usuario puede llamar muchas veces a `RequestPlayListPictureUpload` / `RequestTrackUploadUrls` y subir muchos objetos a S3 sin llegar a crear la entidad final (objetos “huérfanos”).
|
||||
Esto mejora latencia y descarga el host/servicio, pero abre un vector de abuso: un usuario puede llamar muchas veces a `RequestPlayListPictureUpload` / `RequestTrackUploadUrls` y subir muchos objetos a S3 sin llegar a crear la entidad final (objetos “huérfanos”).
|
||||
|
||||
## Qué pasa hoy si el usuario spamea
|
||||
- Se generan muchas URLs pre-firmadas (costo bajo pero no cero).
|
||||
- El usuario puede subir muchos objetos a S3 sin relación con una entidad en DB.
|
||||
- El impacto principal es:
|
||||
- coste/uso de S3 (storage + requests)
|
||||
- posible saturación del endpoint de generación de URLs
|
||||
- posible saturación del componente de generación de URLs
|
||||
|
||||
## Objetivo
|
||||
Reducir abuso y coste, manteniendo:
|
||||
@@ -18,22 +18,54 @@ Reducir abuso y coste, manteniendo:
|
||||
- backend sin streams
|
||||
- workflows asíncronos + MassTransit Outbox
|
||||
|
||||
## Qué significa “sólido” aquí
|
||||
Como el upload es directo a S3 (o compatible), el backend no puede garantizar “single-use” del upload en el lado del storage mientras una URL sea válida. Por tanto, el objetivo realista es:
|
||||
|
||||
- Asegurar que **cualquier abuso solo pueda impactar un prefijo temporal** barato de limpiar.
|
||||
- Poner límites duros (cuotas) en DB para que el coste tenga un techo.
|
||||
- No aceptar referencias a objetos no válidos (verificación antes de consumir).
|
||||
- Evitar pérdida de datos en reemplazos (no borrar la versión previa hasta éxito).
|
||||
|
||||
## Principio de configuración (no hardcode)
|
||||
Todas las rutas/prefijos/TTLs/límites deben ser **configurables**.
|
||||
|
||||
Regla:
|
||||
- En `Application`/`Infrastructure` no debe haber strings hardcodeadas de keys S3 (p.ej. `temp/`, `uploads/`, `PlayLists/`, `Tracks/`).
|
||||
- Las rutas deben centralizarse en opciones/config (p.ej. `StorageRoutes`, `PlayListRoutes`, `TrackRoutes`, o similar), de forma que cambiar la convención de keys no requiera tocar lógica.
|
||||
|
||||
Los ejemplos de este documento son **convenciones sugeridas** y deben leerse como plantillas parametrizadas.
|
||||
|
||||
## Invariantes de seguridad (reglas que no se rompen)
|
||||
- El cliente solo puede escribir en rutas temporales bajo `{TempRootPrefix}/...` (o el prefijo temporal equivalente). La ruta final **nunca** recibe credenciales pre-firmadas.
|
||||
- Cada intent emite un `Key` **nuevo** (GUID). No hay keys “estables” para reusar.
|
||||
- Una entidad solo apunta a un `pictureName/audioName` si su intent asociado se valida y se consume.
|
||||
- Reemplazos: el borrado de la versión anterior ocurre **al final** del workflow, tras actualizar DB.
|
||||
|
||||
## Prioridad (coste alto)
|
||||
Si el coste es el principal riesgo, el orden recomendado es:
|
||||
1) **Cuotas duras en DB** (intents activos + bytes activos) + job de expiración.
|
||||
2) Prefijo temporal + cleanup por prefijo (lifecycle si existe, y job como fallback).
|
||||
3) Validación de objeto (HEAD) antes de consumir.
|
||||
4) (Opcional) Migrar a pre-signed POST si el backend de storage lo soporta bien.
|
||||
|
||||
## Decisiones validadas (no tocaría)
|
||||
- El orden de prioridad (cuotas → prefijo temporal → HEAD → POST) es el más sensato cuando el **coste** es el riesgo principal.
|
||||
- La advertencia sobre S3-compatible / SeaweedFS es importante: el diseño debe ser seguro sin depender de features avanzadas.
|
||||
- El principio de no hardcodear rutas y centralizar prefijos/convenciones en config es coherente con el estilo del repo.
|
||||
- La semántica de errores debe permanecer separada del mapeo a códigos/respuestas (responsabilidad del host/presentación).
|
||||
- En reemplazos, no borrar la versión anterior hasta éxito del workflow es clave para resiliencia con Courier.
|
||||
|
||||
## Mitigaciones recomendadas (pueden coexistir)
|
||||
|
||||
### 1) Rate limiting por usuario/IP (primera línea de defensa)
|
||||
Aplicar limitación en el host/API (no en `Application`). Ejemplos:
|
||||
- `N` requests/min por `UserId`
|
||||
- opcional: `M` requests/min por IP
|
||||
|
||||
Ventajas:
|
||||
- Corta abuso sin tocar DB/S3.
|
||||
- No cambia dominio.
|
||||
|
||||
Nota: requiere acceso al proyecto host (controllers/minimal API) para aplicarlo.
|
||||
|
||||
### 2) “Upload intents” en DB (control y conciliación)
|
||||
### 1) “Upload intents” en DB (control y conciliación)
|
||||
Persistir una intención de upload cuando se genera un pre-signed URL.
|
||||
|
||||
> Importante: **no usar el nombre original del fichero** como `pictureName/audioName`. La key debe usar un **identificador generado** (p.ej. GUID) para evitar colisiones, inputs “raros” y facilitar cleanup.
|
||||
|
||||
Sugerencia práctica:
|
||||
- `objectName = {guid}.{ext}` (o directamente `{guid}` si la extensión no aporta valor)
|
||||
- Guardar `originalFileName` solo como metadata opcional (si se necesita para UI), pero **nunca** como key.
|
||||
|
||||
Modelo sugerido (mínimo):
|
||||
- `UploadIntent`
|
||||
- `Id`
|
||||
@@ -41,19 +73,67 @@ Modelo sugerido (mínimo):
|
||||
- `Bucket`
|
||||
- `Key`
|
||||
- `ContentType`
|
||||
- `ExpectedSizeBytes` (opcional; si el caller conoce el tamaño)
|
||||
- `ExpiresAt`
|
||||
- `Status`: `Issued | Consumed | Expired`
|
||||
- `CreatedAt`
|
||||
|
||||
Opcional (útil para auditoría/UX):
|
||||
- `OriginalFileName`
|
||||
- `Purpose` (`TrackPicture | TrackAudio | PlayListPicture`)
|
||||
|
||||
Reglas:
|
||||
- Al pedir URL (Request*Upload*):
|
||||
- insertar intent `Issued`
|
||||
- imponer límite por usuario: p.ej. `MaxActiveIntentsPerUser = 5`
|
||||
- si supera el límite: devolver `429 Too Many Requests` (o `Conflict`) según convención del API
|
||||
- imponer límites por usuario (ver “Cuotas”)
|
||||
- si supera el límite: fallar con un error semántico (p.ej. `UploadIntentLimitExceeded`)
|
||||
- Al crear/actualizar entidad (Create*/Update* V2):
|
||||
- validar que el `pictureName/audioName` recibido corresponde a un intent `Issued` del mismo `UserId` y no expirado
|
||||
- validar que el objeto existe en S3 (HEAD) y que tamaño / `Content-Type` cumplen la policy del backend
|
||||
- marcar intent como `Consumed`
|
||||
|
||||
#### Cuotas (anti-cost) 🔴 Alta
|
||||
Además del conteo de intents, imponer **cuotas por bytes** para que el coste tenga techo.
|
||||
|
||||
Recomendación mínima:
|
||||
- `MaxActiveUploadIntentsPerUser` (número)
|
||||
- `MaxActiveUploadBytesPerUser` (bytes) — suma de objetos temporales “en vuelo” por usuario
|
||||
|
||||
Cómo aproximarlo sin streams:
|
||||
- Guardar `ExpectedSizeBytes` en el intent (si está disponible).
|
||||
- Si no se dispone del tamaño, usar un valor conservador por `Purpose` (p.ej. imagen <= X, audio <= Y) y validarlo en HEAD antes de consumir.
|
||||
|
||||
> Nota: si el caller puede mentir sobre tamaño, no confíes ciegamente en `ExpectedSizeBytes`; úsalo como guardrail y valida al consumir.
|
||||
|
||||
#### Semántica de errores (intent inválido) 🟡 Media
|
||||
Definir errores semánticos y dejar el mapeo al “caller” (host/presentación):
|
||||
- `UploadIntentLimitExceeded`
|
||||
- `UploadIntentNotFoundOrNotAccessible` (si se quiere no filtrar existencia)
|
||||
- `UploadIntentAlreadyConsumed`
|
||||
- `UploadIntentExpired`
|
||||
- `UploadObjectNotFound`
|
||||
- `UploadObjectPolicyViolation` (tamaño, `Content-Type`, extensión, etc.)
|
||||
|
||||
#### Nota sobre race conditions en el límite 🟡 Media
|
||||
El check “`count(Issued) < Max`” es vulnerable a concurrencia (dos requests paralelas pueden pasar el check y crear 2 intents extra).
|
||||
|
||||
Mitigaciones (de menor a mayor robustez):
|
||||
- Ejecutar **check + insert** en la **misma transacción** con aislamiento fuerte (p.ej. `SERIALIZABLE`).
|
||||
- Usar un mecanismo de lock por usuario (p.ej. advisory lock en PostgreSQL) durante la emisión.
|
||||
- Modelar el límite con una tabla/contador por usuario y aplicar **update condicional** (estilo “compare-and-swap”).
|
||||
|
||||
#### Job de expiración de intents 🔴 Alta
|
||||
Aunque `ExpiresAt` esté en DB, hace falta un mecanismo que marque y limpie intents caducados:
|
||||
- Un job periódico (worker/cron) que:
|
||||
- marque intents `Issued` con `ExpiresAt < now` como `Expired`
|
||||
- opcionalmente encole el borrado del objeto S3 (si existe)
|
||||
|
||||
Si se aplica lifecycle en S3 (ver sección 2), el job puede ser “solo DB” (marcar `Expired`) y dejar el borrado real a S3.
|
||||
|
||||
Adicionalmente, este job es un buen sitio para:
|
||||
- borrar intents `Expired` antiguos (retención de auditoría configurable)
|
||||
- emitir métricas/contadores (intents expirados por usuario)
|
||||
|
||||
Ventajas:
|
||||
- Previene spam con reglas duras y auditables.
|
||||
- Permite cleanup de huérfanos con certeza.
|
||||
@@ -61,7 +141,7 @@ Ventajas:
|
||||
Coste:
|
||||
- entidad + migración + lógica de validación.
|
||||
|
||||
### 3) Limitar el scope del pre-signed URL (reducción de daño)
|
||||
### 2) Limitar el scope del pre-signed URL (reducción de daño)
|
||||
Aplicar restricciones conservadoras:
|
||||
- Reducir TTL:
|
||||
- `ExpiresInSeconds` más bajo (p.ej. 60–120s en vez de 600s)
|
||||
@@ -69,27 +149,115 @@ Aplicar restricciones conservadoras:
|
||||
- `ContentType` permitido
|
||||
- extensiones permitidas (`FileType`)
|
||||
- Usar keys temporales por usuario:
|
||||
- `PlayLists/TempUploads/{userId}/{guid}.{ext}`
|
||||
- `Tracks/TempUploads/{userId}/{guid}.{ext}`
|
||||
- Opción recomendada (facilita lifecycle por prefijo):
|
||||
- `{TempRootPrefix}/{userId}/playlists/{guid}.{ext}`
|
||||
- `{TempRootPrefix}/{userId}/tracks/{guid}.{ext}`
|
||||
- Alternativa (si se quiere mantener todo bajo `uploads/{userId}`):
|
||||
- `{UploadsRootPrefix}/{userId}/PlayLists/TempUploads/{guid}.{ext}`
|
||||
- `{UploadsRootPrefix}/{userId}/Tracks/TempUploads/{guid}.{ext}`
|
||||
|
||||
Luego, en el workflow/worker:
|
||||
- mover/copiar de `TempUploads/...` a la ruta final (`OriginalPictures/...` / `OriginalAudios/...`)
|
||||
- mover/copiar desde la ruta temporal (p.ej. `temp/...` o `.../TempUploads/...`) a la ruta final (`OriginalPictures/...` / `OriginalAudios/...`)
|
||||
- borrar el objeto temporal
|
||||
|
||||
Ventajas:
|
||||
- Cleanup masivo sencillo (por prefijo `TempUploads/{userId}`)
|
||||
- Cleanup masivo sencillo (por prefijo `{TempRootPrefix}/`)
|
||||
- Reduce el impacto de huérfanos.
|
||||
|
||||
#### S3 Lifecycle Rule en `TempUploads` 🔴 Alta
|
||||
Configurar una **Lifecycle Rule** en el bucket para auto-expirar objetos bajo el prefijo de temporales, por ejemplo:
|
||||
- Prefijo: `{TempRootPrefix}/`
|
||||
- Expiración: 1–7 días (según tolerancia a reintentos)
|
||||
|
||||
Nota: las reglas de lifecycle por **prefijo** no soportan comodines en medio (p.ej. `uploads/*/...`).
|
||||
Si se usa la alternativa `uploads/{userId}/.../TempUploads/...`, para filtrar correctamente se recomienda:
|
||||
- cambiar la convención de key para que el prefijo sea común (p.ej. `{TempRootPrefix}/...`), o
|
||||
- usar **tags** en los objetos (p.ej. `temporary=true`) y filtrar la lifecycle rule por tag (si se puede firmar el tagging en el upload).
|
||||
|
||||
Esto evita que el coste de huérfanos crezca indefinidamente incluso si el job de cleanup falla.
|
||||
|
||||
#### Cleanup backend (fallback) 🔴 Alta
|
||||
No dependas solo de lifecycle del storage (en S3 compatibles puede variar). Añadir un job de cleanup que:
|
||||
- liste por prefijo temporal `{TempRootPrefix}/`
|
||||
- borre objetos con edad > `TempUploadsRetentionDays`
|
||||
|
||||
Este job debe ser:
|
||||
- incremental (paginación)
|
||||
- tolerante a errores (reintentos)
|
||||
- con límites de throughput (para no auto-generar costes por borrar en masa)
|
||||
|
||||
#### Restricción de `Content-Type` en S3 🟡 Media
|
||||
Si el objetivo es enforcement “duro” a nivel de S3 (no solo validación posterior), **pre-signed POST** suele ser más robusto que PUT.
|
||||
|
||||
**pre-signed PUT** (lo actual en este repo):
|
||||
- Permite fijar `Content-Type` si se pre-firma incluyendo ese header.
|
||||
- Es más limitado para imponer condiciones ricas (tamaño, tags, etc.).
|
||||
- Recomendación: combinar con validación posterior (HEAD) antes de consumir el intent.
|
||||
|
||||
**pre-signed POST** (recomendado si buscas más control):
|
||||
- Permite policy conditions como:
|
||||
- `content-length-range` (tamaño máximo)
|
||||
- `eq`/`starts-with` para `Content-Type`
|
||||
- key/prefijo (el cliente no elige keys fuera del scope)
|
||||
- headers requeridos (p.ej. SSE)
|
||||
- opcional: tagging (p.ej. `temporary=true`) para lifecycle por tag
|
||||
|
||||
Trade-off: el cliente sube con `multipart/form-data` (POST), pero el backend sigue sin manejar streams.
|
||||
|
||||
#### Nota S3-compatible / SeaweedFS 🟡 Media
|
||||
No todos los backends S3 compatibles implementan al 100%:
|
||||
- policy conditions de POST
|
||||
- tagging en upload
|
||||
- respuestas/errores idénticos a AWS
|
||||
|
||||
Recomendación:
|
||||
- Diseñar el sistema para ser seguro **sin depender** de condiciones avanzadas (keys únicas + prefijo temporal + cuotas + verificación HEAD).
|
||||
- Tratar POST/condiciones como una mejora opcional, tras validar compatibilidad del backend.
|
||||
|
||||
#### Reemplazos: no borrar originales antiguos hasta éxito ✅
|
||||
Al actualizar una imagen/audio existente:
|
||||
- Subir el nuevo binario a la ruta temporal (p.ej. `{TempRootPrefix}/.../{newGuid}`).
|
||||
- Procesar (resize/transcode) y subir a rutas finales con nombres nuevos.
|
||||
- Actualizar la entidad en DB para apuntar a los nuevos `*PictureName/*AudioFolderName`.
|
||||
- **Solo al final** borrar los objetos antiguos (original + sizes / audio procesado), cuando el workflow haya completado.
|
||||
|
||||
Esto evita perder la versión anterior si el worker falla a mitad (punto crítico para resiliencia).
|
||||
|
||||
## Validación del objeto antes de consumir (HEAD) 🔴 Alta
|
||||
Antes de consumir un intent y actualizar DB:
|
||||
- `HEAD Bucket/Key`
|
||||
- validar `Content-Type` esperado (según `Purpose`)
|
||||
- validar tamaño máximo (`MaxUploadBytes` o por propósito)
|
||||
- (opcional) validar extensión inferida de `Key` si se usa
|
||||
|
||||
Si falla la validación:
|
||||
- no consumir el intent
|
||||
- marcarlo como violación o dejarlo expirar
|
||||
- el cleanup se encarga del objeto
|
||||
|
||||
## Orden de implementación sugerido
|
||||
1) Rate limiting en API (rápido + mayor impacto)
|
||||
2) Upload intents en DB (límite de activos + validación al crear/actualizar)
|
||||
3) Reducir TTL y mover a rutas temporales (`TempUploads/...`) + cleanup
|
||||
1) Upload intents en DB (límite de activos + validación al crear/actualizar)
|
||||
2) Reducir TTL y mover a rutas temporales + S3 lifecycle + cleanup
|
||||
3) Si se requiere enforcement fuerte: migrar a pre-signed POST + policy conditions
|
||||
|
||||
## Parámetros recomendados (config)
|
||||
- `UploadUrlExpiresInSeconds` (default: 120)
|
||||
- `MaxActiveUploadIntentsPerUser` (default: 5)
|
||||
- `MaxActiveUploadBytesPerUser` (default sugerido: depende de coste; p.ej. 200MB)
|
||||
- `AllowedPictureExtensions` / `AllowedAudioExtensions`
|
||||
- `AllowedContentTypes` (lista blanca)
|
||||
- `TempUploadsRetentionDays` (default: 3)
|
||||
- `PresignedUploadMethod` (default sugerido: `POST` si se quiere enforcement fuerte; `PUT` si se prioriza simplicidad)
|
||||
- `MaxUploadBytes` (límite máximo de tamaño por upload)
|
||||
|
||||
Jobs (config):
|
||||
- `UploadIntentsExpirationJobIntervalSeconds`
|
||||
- `TempUploadsCleanupJobIntervalSeconds`
|
||||
- `ExpiredIntentsRetentionDays`
|
||||
|
||||
Rutas/prefijos (ejemplos):
|
||||
- `TempRootPrefix` (default sugerido: `temp`)
|
||||
- `UploadsRootPrefix` (default sugerido: `uploads`)
|
||||
|
||||
## Notas
|
||||
- Estas defensas complementan el Outbox: el Outbox garantiza entrega de eventos tras commit, pero no evita el abuso de generación de URLs o almacenamiento de objetos huérfanos.
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
# Tech stack y arquitectura (MusifyBackend)
|
||||
|
||||
Este documento describe **qué tecnologías usa este repo** y **cómo encajan** (capas, workers, mensajería, storage, observabilidad). Está pensado para alguien que llega de cero o para una IA que necesita contexto.
|
||||
|
||||
## Foto rápida
|
||||
- Runtime: **.NET 10** (C#)
|
||||
- Estilo: **Clean Architecture** (capas `Domain` / `Application` / `Infrastructure`)
|
||||
- Casos de uso: **CQRS** con **DispatchR** (similar a MediatR)
|
||||
- Persistencia: **Entity Framework Core** + **PostgreSQL** (provider `Npgsql`)
|
||||
- Workers / mensajería: **MassTransit** + **RabbitMQ**
|
||||
- Orquestación de workflows: **MassTransit Courier / RoutingSlip** (Activities)
|
||||
- Storage de objetos: **SeaweedFS** expuesto como **S3-compatible** (vía AWS SDK `IAmazonS3`)
|
||||
- Observabilidad: **OpenTelemetry** (traces + metrics, export OTLP)
|
||||
- Procesado multimedia:
|
||||
- Imágenes: **SixLabors.ImageSharp** (resize, WebP)
|
||||
- Audio: **ffmpeg** (transcode a DASH)
|
||||
|
||||
## Estructura del repo (capas)
|
||||
|
||||
### `Domain/` (núcleo de negocio)
|
||||
- Entidades puras (sin dependencias de infraestructura).
|
||||
- Ejemplos: `Track`, `PlayList`, `Upload`, etc.
|
||||
- Estados de negocio relevantes:
|
||||
- `ProcessingStatus` (procesado en curso/finalizado, etc.).
|
||||
- `LifeCycleStatus` (p.ej. `Active`, `Removing`) para deletes asíncronos.
|
||||
|
||||
### `Application/` (casos de uso + contratos)
|
||||
- Contiene comandos/queries/handlers por feature.
|
||||
- CQRS:
|
||||
- `Commands/` y `Queries/` por feature (`PlayLists`, `Tracks`, `Users`, ...).
|
||||
- `Handlers/` ejecutan la lógica de aplicación (coordinar DB + eventos), pero **no implementan** detalles de infraestructura.
|
||||
- Contratos con infraestructura: `Application/Abstractions/Infrastructure/*`
|
||||
- `IDatabase` (abstracto de EF DbContext)
|
||||
- `IEventBus` (publicación de eventos)
|
||||
- `IStorageService`, `IPictureService`, `IAudioTranscoderService`, etc.
|
||||
- Eventos de integración: `Application/Events/*`
|
||||
- Se publican desde `Application` y se consumen en `Infrastructure`.
|
||||
|
||||
### `Infrastructure/` (implementaciones + integraciones)
|
||||
- Implementa las abstracciones de `Application`.
|
||||
- Contiene:
|
||||
- EF Core DbContext y migraciones
|
||||
- MassTransit (consumers, activities, routing slips, outbox)
|
||||
- Integración S3 (SeaweedFS) mediante AWS SDK
|
||||
- Observabilidad OpenTelemetry
|
||||
- Servicios de procesado (ImageSharp/ffmpeg)
|
||||
|
||||
## CQRS con DispatchR
|
||||
- Los “use cases” se modelan como:
|
||||
- **Commands**: mutaciones (crear/actualizar/borrar).
|
||||
- **Queries**: lecturas.
|
||||
- Los handlers viven en `Application/*/Handlers/` y dependen de:
|
||||
- `IDatabase` para persistencia (sin acoplarse al DbContext concreto).
|
||||
- `IEventBus` para emitir eventos cuando hay trabajo asíncrono.
|
||||
|
||||
## Persistencia (EF Core + PostgreSQL)
|
||||
- DbContext: `Infrastructure/Persistence/Database.cs`
|
||||
- Usa `UseNpgsql(connectionString)`.
|
||||
- Incluye tablas de MassTransit Outbox/Inbox (ver sección de mensajería).
|
||||
- DI: `Infrastructure/Persistence/DatabaseDependencyInjection.cs`
|
||||
- Config: `Infrastructure/Configuration/DatabaseConfiguration.cs` (sección `Database`)
|
||||
- `ConnectionString`
|
||||
|
||||
## Mensajería y workers (MassTransit + RabbitMQ)
|
||||
|
||||
### Qué se usa
|
||||
- Broker: RabbitMQ (`MassTransit.RabbitMQ`).
|
||||
- MassTransit está configurado en dos modos:
|
||||
1) **Cliente** (publica eventos): `Infrastructure/MassTransit/DependencyInjection/MassTransitDependencyInjection.cs` → `AddMassTransitClient()`
|
||||
2) **Workers** (consumen y ejecutan activities): `AddMassTransitConsumers()`
|
||||
|
||||
### Outbox / Inbox (robustez)
|
||||
- Se usa **EntityFramework Outbox** (`AddEntityFrameworkOutbox<Database>()`) con:
|
||||
- `UsePostgres()`
|
||||
- `UseBusOutbox()`
|
||||
- `QueryDelay = 1s`
|
||||
- `DuplicateDetectionWindow = 30m`
|
||||
- En el DbContext se registran entidades de MassTransit:
|
||||
- `AddInboxStateEntity()`
|
||||
- `AddOutboxMessageEntity()`
|
||||
- `AddOutboxStateEntity()`
|
||||
|
||||
Idea clave:
|
||||
- La outbox asegura que, tras confirmar la transacción de DB, los mensajes/eventos pendientes se publican de forma fiable.
|
||||
|
||||
### Courier / RoutingSlip (workflows asíncronos)
|
||||
Para operaciones multi-paso (p.ej. imagen/audio y deletes), se usa **MassTransit Courier**:
|
||||
- Un “RoutingSlip” es una lista de pasos.
|
||||
- Cada paso es una **Activity** (con args + log opcional).
|
||||
- Existen dos tipos que aparecen en este repo:
|
||||
- `ExecuteActivity` (endpoint `<name>_execute`), típicamente acciones atómicas.
|
||||
- `Activity` (con compensación/log), típico para pasos de workflow.
|
||||
|
||||
Dónde mirar:
|
||||
- Builders: `Infrastructure/MassTransit/RoutingSlip/Builders/*`
|
||||
- Activities: `Infrastructure/MassTransit/Activities/*`
|
||||
- Consumers (punto de entrada del workflow): `Infrastructure/MassTransit/Consumers/*`
|
||||
- Registro de endpoints: `Infrastructure/MassTransit/DependencyInjection/*`
|
||||
|
||||
Ejemplos de workflows presentes:
|
||||
- Procesado de imagen (resize + upload + update DB)
|
||||
- Procesado de audio (transcode DASH + upload + update DB)
|
||||
- Deletes asíncronos (marcar “removing”, borrar objetos, borrar filas)
|
||||
|
||||
### Config de MassTransit
|
||||
- `Infrastructure/Configuration/MassTransitConfiguration.cs` (sección `MassTransit`)
|
||||
- `Address`
|
||||
- `Username`
|
||||
- `Password`
|
||||
|
||||
## Storage de objetos (SeaweedFS como S3-compatible)
|
||||
|
||||
### Qué se usa
|
||||
- SDK: `Amazon.S3` (`IAmazonS3`).
|
||||
- Implementación: `Infrastructure/Services/StorageService.cs` (usa `IAmazonS3`).
|
||||
- DI: `Infrastructure/Services/ServicesDependencyInjection.cs` → `AddStorageService()`
|
||||
|
||||
### Config
|
||||
- `Infrastructure/Configuration/InfrastructureStorageConfiguration.cs` (sección `InfrastructureStorage`)
|
||||
- `Address` (ServiceURL)
|
||||
- `AccessKey` / `SecretAccessKey`
|
||||
- `ForcePathStyle` (útil en S3-compatibles)
|
||||
- `UseHttp`
|
||||
|
||||
Notas prácticas (S3-compatible):
|
||||
- Algunas features avanzadas de AWS S3 pueden no comportarse igual (depende de SeaweedFS). Diseñar workflows para que sean correctos con el subconjunto básico: PUT/GET/HEAD/LIST/DELETE y paths bien definidos.
|
||||
|
||||
## Procesado multimedia
|
||||
|
||||
### Imágenes
|
||||
- Librería: `SixLabors.ImageSharp`
|
||||
- Servicio: `Infrastructure/Services/PictureService.cs`
|
||||
- Contrato: `Application/Abstractions/Infrastructure/IPictureService.cs`
|
||||
- Patrón: workers descargan/transforman/suben; la app coordina por eventos.
|
||||
|
||||
### Audio
|
||||
- Herramienta: `ffmpeg` (binario externo)
|
||||
- Config: `Infrastructure/Configuration/AudioTranscoderConfiguration.cs` (sección `AudioTranscoder`)
|
||||
- Timeout, codec/bitrate/channels/sample-rate, segmentación DASH, etc.
|
||||
- Servicio: `Infrastructure/Services/AudioTranscoderService.cs`
|
||||
- Contrato: `Application/Abstractions/Infrastructure/IAudioTranscoderService.cs`
|
||||
|
||||
### Directorio temporal (workers)
|
||||
- Config: `Infrastructure/Configuration/WorkerConfiguration.cs` (sección `Workers`)
|
||||
- `Routes.TemporaryFilesDirectory`
|
||||
|
||||
## Observabilidad (OpenTelemetry)
|
||||
- DI: `Infrastructure/Observability/OpenTelemetryDependencyInjection.cs`
|
||||
- Config: `Infrastructure/Configuration/OpenTelemetryConfiguration.cs` (sección `OpenTelemetry`)
|
||||
- `OtlpEndpoint`
|
||||
|
||||
Qué instrumenta:
|
||||
- Tracing:
|
||||
- `MassTransit`
|
||||
- ASP.NET Core
|
||||
- HttpClient
|
||||
- EF Core
|
||||
- Exporter OTLP
|
||||
- Métricas:
|
||||
- `MassTransit`
|
||||
- ASP.NET Core
|
||||
- HttpClient
|
||||
- Runtime + Process
|
||||
- Exporter OTLP
|
||||
|
||||
## Identidad (Keycloak)
|
||||
- El repo asume integración con **Keycloak** (ver `AGENTS.md` y nombres como `GetUserByKeycloakQuery`).
|
||||
- La capa `Application` trata el “Keycloak Id” como identificador de usuario en algunos casos de uso.
|
||||
- La integración concreta (tokens, validación, etc.) puede vivir fuera de estas 3 capas o en otro proyecto/host.
|
||||
|
||||
## Glosario rápido
|
||||
- **Command / Query**: operación de escritura / lectura.
|
||||
- **Handler**: clase que ejecuta el caso de uso (CQRS).
|
||||
- **EventBus**: abstracción para publicar eventos (MassTransit por debajo).
|
||||
- **Consumer**: “listener” de mensajes en una cola.
|
||||
- **RoutingSlip**: workflow compuesto por Activities.
|
||||
- **Activity**: paso del workflow (puede tener log/compensación).
|
||||
- **Outbox**: patrón para publicar eventos de forma fiable tras commit de DB.
|
||||
|
||||
## “Dónde empiezo a leer” (ruta recomendada)
|
||||
1) Arquitectura general: `AGENTS.md`
|
||||
2) Casos de uso: `Application/*/Commands`, `Application/*/Queries` y sus `Handlers/`
|
||||
3) Persistencia: `Infrastructure/Persistence/Database.cs` + migraciones
|
||||
4) Mensajería/workflows: `Infrastructure/MassTransit/*`
|
||||
5) Integraciones: `Infrastructure/Services/*` (S3, imagen, audio)
|
||||
6) Observabilidad: `Infrastructure/Observability/OpenTelemetryDependencyInjection.cs`
|
||||
Reference in New Issue
Block a user