API REST serverless construida con Azure Functions v4 y .NET 8 que implementa un CRUD completo para la gestión de productos, utilizando Entity Framework Core con PostgreSQL como base de datos.
- Arquitectura
- Tecnologías
- Estructura del Proyecto
- Prerrequisitos
- Configuración
- Ejecución Local
- Endpoints API
- Flujos de la Aplicación
- Documentación OpenAPI
- Docker
┌─────────────────────────────────────────────────────────────────┐
│ Azure Functions Host │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ HTTP │───▶│ Middleware │───▶│ Functions │ │
│ │ Trigger │ │ (Logging) │ │ (Endpoints) │ │
│ └──────────────┘ └─────────────────┘ └───────┬────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ IProductService│ │
│ │ (Service) │ │
│ └───────┬────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ DbContext │ │
│ │ (EF Core) │ │
│ └───────┬────────┘ │
│ │ │
└──────────────────────────────────────────────────────┼───────────┘
│
┌───────▼────────┐
│ PostgreSQL │
│ Database │
└────────────────┘
| Tecnología | Versión | Descripción |
|---|---|---|
| .NET | 8.0 | Framework principal |
| Azure Functions | v4 | Plataforma serverless |
| Entity Framework Core | 8.0.10 | ORM para acceso a datos |
| PostgreSQL (Npgsql) | 8.0.10 | Base de datos relacional |
| Ardalis.Result | 10.1.0 | Patrón Result para manejo de respuestas |
| OpenAPI | 1.5.1 | Documentación de API |
| Application Insights | 2.22.0 | Monitoreo y telemetría |
FunctionApp/
├── Database/
│ ├── Configurations/
│ │ └── ProductConfiguration.cs # Configuración de entidad EF Core
│ ├── Constants/
│ │ └── TableNames.cs # Constantes de nombres de tablas
│ └── ApplicationDbContext.cs # Contexto de base de datos
├── Entities/
│ └── Product.cs # Entidad de dominio
├── Extensions/
│ ├── HostExtensions.cs # Extensiones para migraciones
│ └── HttpRequestExtensions.cs # Extensiones para HttpRequest
├── Functions/
│ ├── CreateProduct.cs # POST /api/products
│ ├── GetAllProducts.cs # GET /api/products
│ ├── GetProductById.cs # GET /api/products/{id}
│ ├── UpdateProduct.cs # PUT /api/products/{id}
│ └── DeleteProduct.cs # DELETE /api/products/{id}
├── Middlewares/
│ └── CustomMiddleware.cs # Middleware de logging
├── Migrations/
│ └── ... # Migraciones de EF Core
├── Services/
│ ├── Abstraction/
│ │ └── IProductService.cs # Interfaz del servicio
│ └── ProductService.cs # Implementación del servicio
├── Program.cs # Punto de entrada
├── host.json # Configuración del host
└── local.settings.json # Configuración local
- .NET 8 SDK
- Azure Functions Core Tools v4
- PostgreSQL (local o en la nube)
- Visual Studio 2022 o VS Code
Crea un archivo local.settings.json en la raíz del proyecto FunctionApp:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"Postgres": "Host=localhost;Database=ProductsDb;Username=postgres;Password=tu_password"
}
}Las migraciones se aplican automáticamente al iniciar la aplicación mediante el método ApplyMigrations() en Program.cs.
Para crear nuevas migraciones manualmente:
cd FunctionApp
dotnet ef migrations add NombreMigracioncd FunctionApp
func start- Abrir la solución
- Establecer
FunctionAppcomo proyecto de inicio - Presionar F5
docker build -t simple-azure-function .
docker run -p 7071:80 simple-azure-functionLa API estará disponible en: http://localhost:7071/api/
| Método | Ruta | Descripción | Request Body | Response |
|---|---|---|---|---|
GET |
/api/products |
Obtener todos los productos | - | List<Product> |
GET |
/api/products/{id} |
Obtener producto por ID | - | Product o 404 |
POST |
/api/products |
Crear nuevo producto | CreateProductRequest |
Guid |
PUT |
/api/products/{id} |
Actualizar producto | UpdateProductRequest |
204 o 404 |
DELETE |
/api/products/{id} |
Eliminar producto | - | 204 o 404 |
CreateProductRequest
{
"name": "Producto Ejemplo",
"description": "Descripción del producto",
"price": 99.99
}UpdateProductRequest
{
"description": "Nueva descripción",
"price": 149.99
}{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Producto Ejemplo",
"description": "Descripción del producto",
"price": 99.99
}┌──────────┐ ┌──────────────┐ ┌────────────────┐ ┌─────────────┐ ┌────────────┐
│ Cliente │────▶│ HTTP Trigger │────▶│ CustomMiddleware│────▶│CreateProduct│────▶│ProductService│
└──────────┘ └──────────────┘ └────────────────┘ └──────┬──────┘ └──────┬─────┘
│ │
│ Create() │
│◀──────────────────│
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ Return │ │ DbContext │
│ Guid │ │ SaveAsync│
└───────────┘ └─────┬─────┘
│
┌─────▼─────┐
│PostgreSQL │
│ INSERT │
└───────────┘
Pasos detallados:
- El cliente envía una petición POST con el body JSON
- El HTTP Trigger activa la función
CreateProduct - El
CustomMiddlewareregistra la entrada en logs - Se deserializa el request usando
HttpRequestExtensions.GetBody<T>() - Se llama a
IProductService.Create()con los datos ProductServicecrea una nueva entidadProduct- EF Core persiste el producto en PostgreSQL
- Se retorna el GUID del producto creado
┌──────────┐ ┌──────────────┐ ┌────────────────┐ ┌───────────────┐
│ Cliente │────▶│ HTTP Trigger │────▶│ CustomMiddleware│────▶│GetAllProducts │
└──────────┘ └──────────────┘ └────────────────┘ └───────┬───────┘
│
┌─────▼─────┐
│ProductService│
│ GetAll() │
└─────┬─────┘
│
┌─────▼─────┐
│ DbContext │
│ToListAsync│
└─────┬─────┘
│
┌─────▼─────┐
│PostgreSQL │
│ SELECT * │
└─────┬─────┘
│
┌─────▼─────┐
│Return JSON│
│List<Product>│
└───────────┘
Pasos detallados:
- El cliente envía una petición GET
- Se ejecuta el middleware de logging
ProductService.GetAll()ejecuta una consulta a la base de datos- EF Core traduce la consulta a SQL
SELECT - Se retorna la lista de productos como JSON
┌──────────┐ ┌──────────────┐ ┌───────────────┐ ┌─────────────┐
│ Cliente │────▶│ HTTP Trigger │────▶│GetProductById │────▶│ProductService│
└──────────┘ └──────────────┘ └───────────────┘ └──────┬──────┘
│
┌─────▼─────┐
│ GetById() │
│Result<T> │
└─────┬─────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Existe │ │ Producto │ │No Existe │
│ ✓ │ │ Found │ │ ✗ │
└─────┬─────┘ └───────────┘ └─────┬─────┘
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ Return │ │ Return │
│ 200 + JSON│ │ 404 │
└───────────┘ └───────────┘
Pasos detallados:
- El cliente envía GET con el GUID en la ruta
ProductService.GetById()busca el producto- Se usa el patrón
Result<T>de Ardalis.Result - Si existe → retorna
200 OKcon el producto - Si no existe → retorna
404 Not Found
┌──────────┐ ┌──────────────┐ ┌─────────────┐ ┌─────────────┐
│ Cliente │────▶│ HTTP Trigger │────▶│UpdateProduct│────▶│ProductService│
│ PUT │ │ │ │ │ │ Update() │
└──────────┘ └──────────────┘ └─────────────┘ └──────┬──────┘
│
┌─────▼─────┐
│Find Product│
│by ID │
└─────┬─────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Existe │ │ Update │ │No Existe │
│ ✓ │─────────▶│Entity │ │ ✗ │
└───────────┘ └─────┬─────┘ └─────┬─────┘
│ │
┌─────▼─────┐ ┌─────▼─────┐
│SaveChanges│ │ Return │
│Return 204 │ │ 404 │
└───────────┘ └───────────┘
Pasos detallados:
- El cliente envía PUT con ID en ruta y datos en body
- Se deserializa
UpdateProductRequest ProductService.Update()busca el producto- Si existe → actualiza
DescriptionyPrice - EF Core persiste los cambios
- Retorna
204 No Contento404 Not Found
┌──────────┐ ┌──────────────┐ ┌─────────────┐ ┌─────────────┐
│ Cliente │────▶│ HTTP Trigger │────▶│DeleteProduct│────▶│ProductService│
│ DELETE │ │ │ │ │ │ Delete() │
└──────────┘ └──────────────┘ └─────────────┘ └──────┬──────┘
│
┌─────▼─────┐
│Find Product│
└─────┬─────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Existe │ │ Remove │ │No Existe │
│ ✓ │─────────▶│ Entity │ │ ✗ │
└───────────┘ └─────┬─────┘ └─────┬─────┘
│ │
┌─────▼─────┐ ┌─────▼─────┐
│SaveChanges│ │ Return │
│Return 204 │ │ 404 │
└───────────┘ └───────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Program.cs - Startup │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. HostBuilder │
│ │ │
│ ├──▶ ConfigureFunctionsWebApplication() │
│ │ └──▶ UseMiddleware<CustomMiddleware>() │
│ │ │
│ ├──▶ ConfigureServices() │
│ │ ├──▶ AddDbContext<ApplicationDbContext>() │
│ │ │ └──▶ UseNpgsql(connectionString) │
│ │ │ │
│ │ └──▶ AddScoped<IProductService, ProductService>() │
│ │ │
│ └──▶ ConfigureOpenApi() │
│ │
│ 2. Build() │
│ │ │
│ └──▶ host.ApplyMigrations() │
│ └──▶ Database.Migrate() │
│ │
│ 3. Run() │
│ └──▶ Aplicación lista para recibir requests │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
La API incluye documentación OpenAPI/Swagger automática. Después de iniciar la aplicación, accede a:
- Swagger UI:
http://localhost:7071/api/swagger/ui - OpenAPI JSON:
http://localhost:7071/api/openapi/v3.json
Cada endpoint está documentado con:
- Operación y tags
- Parámetros de ruta
- Request body schemas
- Response schemas y códigos de estado
El proyecto está configurado para ejecutarse en contenedores Docker con soporte para Linux.
El proyecto incluye configuración para Docker con:
- Target OS: Linux
- Mount directory:
/home/site/wwwroot
Postgres=Host=db;Database=ProductsDb;Username=postgres;Password=password
AzureWebJobsStorage=UseDevelopmentStorage=true
FUNCTIONS_WORKER_RUNTIME=dotnet-isolatedEste proyecto está bajo la licencia MIT.
Repositorio: SimpleAzureFunction