El problema fundamental que aborda este enfoque es el 'vendor lock-in' percibido en arquitecturas serverless. Aunque las funciones como servicio (FaaS) ofrecen beneficios significativos en eficiencia de recursos y escalado automático, la integración profunda con los SDK y servicios específicos de cada proveedor de nube puede dificultar la migración o el despliegue multi-cloud. La tesis es que, mediante una arquitectura de software disciplinada que separe la lógica de negocio central de los detalles de infraestructura específicos de la nube, es posible lograr una portabilidad significativa de la lógica de negocio, mitigando así el riesgo de dependencia del proveedor sin renunciar a los beneficios de serverless. Esto se vuelve crucial en un panorama tecnológico donde la flexibilidad y la resiliencia operativa son prioritarias.
Históricamente, la abstracción de la infraestructura ha sido un objetivo recurrente en la ingeniería de software, desde las máquinas virtuales hasta los contenedores. Serverless, aunque abstrae la gestión del servidor, introduce nuevas capas de acoplamiento a través de sus APIs y modelos de eventos. Este artículo propone aplicar principios de diseño de software probados, como la Clean Architecture, para gestionar este acoplamiento de manera explícita y controlada, permitiendo que la lógica de negocio permanezca 'pura' y desacoplada de la infraestructura subyacente. La relevancia actual de esta aproximación radica en la necesidad de las organizaciones de evitar la dependencia excesiva de un único proveedor, ya sea por razones de costo, estrategia o resiliencia.
Arquitectura del Sistema
La arquitectura propuesta se basa en una adaptación simplificada de la Clean Architecture, estructurada en tres capas principales: Dominio, Aplicación e Infraestructura. La capa de Dominio contiene los objetos de negocio y la lógica de validación básica, siendo completamente agnóstica a la nube. La capa de Aplicación encapsula los casos de uso y la lógica de negocio principal, definiendo interfaces para interactuar con servicios externos (ej. almacenamiento, envío de emails) sin conocer sus implementaciones concretas. Finalmente, la capa de Infraestructura contiene el código específico de cada proveedor de nube, implementando las interfaces definidas en la capa de Aplicación y actuando como adaptadores para servicios como AWS S3/SES o Azure Blob Storage/Communication Services. Esta separación se refuerza mediante módulos Gradle, que imponen restricciones de dependencia en tiempo de compilación, asegurando que las capas internas no dependan de las externas.
Spring Cloud Function actúa como el habilitador principal para ejecutar la lógica de negocio de Spring Boot en entornos FaaS. Proporciona adaptadores específicos para AWS Lambda y Azure Functions, permitiendo que las funciones de Spring se integren con los modelos de invocación de cada plataforma. La inyección de dependencias de Spring se utiliza para conectar dinámicamente las implementaciones de infraestructura específicas de la nube a la lógica de negocio en tiempo de ejecución. La infraestructura como código (IaC) se gestiona con Terraform CDK, que permite definir y desplegar recursos en múltiples nubes utilizando un lenguaje de programación unificado (TypeScript en este caso), generando archivos Terraform subyacentes. Esto desacopla la definición de la infraestructura del código de la aplicación, permitiendo que los triggers HTTP (API Gateway en AWS, HTTP Trigger en Azure) y los eventos de almacenamiento (S3 events, Blob storage events) se configuren de forma declarativa y se vinculen a las funciones desplegadas.
Flujo de Carga y Validación de Documentos
- 1 Cliente Sube documento vía HTTP POST a API Gateway (AWS) o HTTP Trigger (Azure)
- 2 Función Serverless Recibe el documento y delega a la lógica de negocio (Application Layer)
- 3 Lógica de Negocio (Application) Valida el documento usando reglas de dominio
- 4 Interfaz de Almacenamiento Llama a la interfaz 'ObjectStorage' para guardar el documento
- 5 Infraestructura Cloud Implementación específica guarda en S3 (AWS) o Blob Storage (Azure)
- 6 Almacenamiento de Objetos Documento guardado, genera evento de notificación
Flujo de Revisión y Notificación de Documentos
- 1 Almacenamiento de Objetos Evento de creación/modificación de documento (S3 Event, Blob Storage Event)
- 2 Función Serverless Activada por el evento, delega a la lógica de negocio (Application Layer)
- 3 Lógica de Negocio (Application) Genera URI seguro y realiza revisión (lógica de IA o reglas)
- 4 Interfaz de Notificación Llama a la interfaz 'EmailSender' para enviar notificación
- 5 Infraestructura Cloud Implementación específica envía email vía SES (AWS) o ACS (Azure)
- 6 Revisor Humano Recibe email con enlace seguro al documento y comentarios de revisión
| Capa | Tecnología | Justificación |
|---|---|---|
| compute | AWS Lambda | Plataforma FaaS para ejecutar funciones de negocio en AWS, escalado automático y pago por uso. vs Azure Functions, Google Cloud Functions Configuración de handler para Spring Cloud Function, SnapStart para JVM cold start mitigation. |
| compute | Azure Functions | Plataforma FaaS para ejecutar funciones de negocio en Azure, escalado automático y pago por uso. vs AWS Lambda, Google Cloud Functions Configuración de MAIN_CLASS para Spring Cloud Function, Elastic Premium para cold start mitigation. |
| orchestration | Spring Cloud Function | Framework para adaptar aplicaciones Spring Boot a entornos FaaS, facilitando la inyección de dependencias y la portabilidad de la lógica de negocio. vs Quarkus, Micronaut, Serverless Framework (con plugins específicos) |
| storage | AWS S3 | Almacenamiento de objetos duradero y escalable para documentos en AWS. vs Azure Blob Storage, Google Cloud Storage |
| storage | Azure Blob Storage | Almacenamiento de objetos duradero y escalable para documentos en Azure. vs AWS S3, Google Cloud Storage |
| messaging | AWS SES (Simple Email Service) | Servicio de envío de correo electrónico transaccional y de marketing en AWS. vs Azure Communication Services, SendGrid, Mailgun |
| messaging | Azure Communication Services | Servicio de comunicación para envío de correo electrónico y otras interacciones en Azure. vs AWS SES, SendGrid, Mailgun |
| orchestration | Terraform CDK (cdktf) | Herramienta de Infraestructura como Código que permite definir recursos de nube en lenguajes de programación y generar configuraciones de Terraform, facilitando el despliegue multi-cloud. vs Terraform HCL, AWS CDK (nativo), Azure Bicep, Pulumi |
| orchestration | Gradle | Sistema de automatización de construcción para proyectos JVM, utilizado para gestionar dependencias y aplicar restricciones arquitectónicas entre módulos (Clean Architecture). vs Maven Configuración de dependencias entre módulos para enforce la separación de capas. |
Trade-offs
Ganancias
- ▲ Portabilidad de la lógica de negocio
- ▲ Reducción del vendor lock-in
- ▲ Eficiencia de recursos y escalado automático (inherente a serverless)
- △ Flexibilidad para elegir el mejor servicio cloud para cada necesidad
Costes
- △ Complejidad arquitectónica inicial
- △ Curva de aprendizaje para Clean Architecture y Terraform CDK
- △ Overhead de configuración y despliegue multi-cloud
- △ Posible aumento de la superficie de ataque (más proveedores, más configuraciones)
class AzureFunctions(private val validateDocumentService: ValidateDocumentService, private val reviewAndNotifyDocumentService: ReviewAndNotifyDocumentService) {
@FunctionName("UploadDocument")
fun uploadDocument(@HttpTrigger(name = "req", methods = [HttpMethod.POST], authLevel = AuthorizationLevel.FUNCTION) request: HttpRequestMessage<Optional<String>>, context: ExecutionContext): HttpResponseMessage {
// ... lógica de validación y guardado usando validateDocumentService ...
}
@FunctionName("ProcessDocument")
fun processDocument(@BlobTrigger(name = "blob", path = "docs/{name}", connection = "AzureWebJobsStorage") blob: String, name: String, context: ExecutionContext) {
// ... lógica de revisión y notificación usando reviewAndNotifyDocumentService ...
}
}interface ObjectStorage {
fun save(document: Document): String
fun generateSecureUri(blobId: String): String
}
interface EmailSender {
fun sendEmail(review: DocumentReview)
}
// Implementación en AWS
class S3ObjectStorage : ObjectStorage { /* ... */ }
class SESEmailSender : EmailSender { /* ... */ }
// Implementación en Azure
class AzureBlobStorage : ObjectStorage { /* ... */ }
class ACSEmailSender : EmailSender { /* ... */ }// settings.gradle.kts
include("domain", "application", "infrastructure:aws", "infrastructure:azure", "infrastructure:cdk")
// application/build.gradle.kts
dependencies {
implementation(project(":domain"))
}
// infrastructure/aws/build.gradle.kts
dependencies {
implementation(project(":application"))
implementation("org.springframework.cloud:spring-cloud-function-adapter-aws")
// ... AWS SDK dependencies ...
}Fundamentos Teóricos
La filosofía detrás de la separación de preocupaciones y la inversión de dependencias, central en este enfoque, tiene raíces profundas en la ingeniería de software. El principio de Inversión de Dependencias (DIP), uno de los principios SOLID, postula que los módulos de alto nivel no deben depender de módulos de bajo nivel, sino que ambos deben depender de abstracciones. Este principio fue formalizado por Robert C. Martin (conocido como 'Uncle Bob') y es un pilar de la Clean Architecture, la Arquitectura Hexagonal (Ports and Adapters) de Alistair Cockburn y la Arquitectura de Cebolla de Jeffrey Palermo. Estos patrones arquitectónicos buscan proteger la lógica de negocio central de los detalles de implementación externos, ya sean frameworks, bases de datos o, en este caso, proveedores de nube.
La idea de la portabilidad y la abstracción de la infraestructura también se relaciona con conceptos de computación distribuida y sistemas operativos, donde las capas de abstracción (como las APIs POSIX para sistemas de archivos o sockets para redes) permiten que las aplicaciones se ejecuten en diversas plataformas sin modificaciones. En el contexto de la nube, la necesidad de abstracción se ha intensificado, llevando al desarrollo de estándares y herramientas como Kubernetes para la orquestación de contenedores, o frameworks como Spring Cloud Function que buscan proporcionar una capa de abstracción para las funciones serverless. La aplicación de estos principios a FaaS es una extensión natural de décadas de investigación sobre cómo construir sistemas robustos y adaptables frente a cambios en el entorno subyacente.