Realizar Operaciones CRUD Avanzadas con Relaciones y Transacciones

Lectura
15 min~5 min lectura
Objetivo de la lección

Concepto clave Las operaciones CRUD avanzadas en FastAPI van más allá de las operaciones básicas de crear, leer, actualizar y eliminar.

Puntos de control
  • Concepto clave
  • Cómo funciona en la práctica
  • Código en acción
  • Errores comunes

Concepto clave

Las operaciones CRUD avanzadas en FastAPI van más allá de las operaciones básicas de crear, leer, actualizar y eliminar. Cuando trabajas con sistemas de producción, necesitas manejar relaciones complejas entre entidades y garantizar la integridad de los datos mediante transacciones. Piensa en esto como una transferencia bancaria: no puedes simplemente restar dinero de una cuenta sin asegurarte de que se suma a otra. Si algo falla en medio, todo debe revertirse para evitar inconsistencias.

Las relaciones en bases de datos (como uno-a-muchos o muchos-a-muchos) requieren que diseñes tus modelos SQLAlchemy con cuidado y optimices las consultas para evitar el problema N+1. Imagina un sistema de e-commerce donde un pedido tiene múltiples productos. Si consultas cada producto por separado, el rendimiento se desploma. En cambio, debes usar joins y carga ansiosa para traer todo en una sola consulta.

Cómo funciona en la práctica

Vamos a construir un sistema de gestión de proyectos donde los usuarios pueden crear proyectos y asignarles tareas. Cada proyecto tiene un usuario propietario (relación uno-a-muchos) y múltiples tareas (otra relación uno-a-muchos). Implementaremos operaciones CRUD que respeten estas relaciones y usen transacciones para mantener la consistencia.

Paso 1: Define los modelos SQLAlchemy con relaciones. Usa relationship y ForeignKey para conectar las tablas. Paso 2: Crea esquemas Pydantic que incluyan datos anidados (como mostrar tareas dentro de un proyecto). Paso 3: Implementa endpoints en FastAPI que usen transacciones con db.session para operaciones atómicas. Por ejemplo, al crear un proyecto con tareas iniciales, todo debe guardarse o nada.

Código en acción

Modelos SQLAlchemy con relaciones:

from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from database import Base

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)
    email = Column(String, unique=True, index=True)
    projects = relationship("Project", back_populates="owner")

class Project(Base):
    __tablename__ = "projects"
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, index=True)
    owner_id = Column(Integer, ForeignKey("users.id"))
    owner = relationship("User", back_populates="projects")
    tasks = relationship("Task", back_populates="project", cascade="all, delete-orphan")

class Task(Base):
    __tablename__ = "tasks"
    id = Column(Integer, primary_key=True, index=True)
    description = Column(String)
    project_id = Column(Integer, ForeignKey("projects.id"))
    project = relationship("Project", back_populates="tasks")

Endpoint con transacción para crear proyecto y tareas:

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from database import get_db
from models import Project, Task
from schemas import ProjectCreate, ProjectResponse
import logging

router = APIRouter()

@router.post("/projects/", response_model=ProjectResponse)
def create_project_with_tasks(project_data: ProjectCreate, db: Session = Depends(get_db)):
    try:
        # Inicia transacción
        db_project = Project(title=project_data.title, owner_id=project_data.owner_id)
        db.add(db_project)
        db.flush()  # Obtiene ID para usar en tareas
        
        for task_desc in project_data.tasks:
            db_task = Task(description=task_desc, project_id=db_project.id)
            db.add(db_task)
        
        db.commit()  # Confirma todo
        db.refresh(db_project)
        return db_project
    except Exception as e:
        db.rollback()  # Revierte en caso de error
        logging.error(f"Error en transacción: {e}")
        raise HTTPException(status_code=500, detail="Error al crear proyecto")

Errores comunes

  • No usar transacciones en operaciones múltiples: Si creas un proyecto y sus tareas en inserciones separadas sin transacción, un fallo puede dejar datos inconsistentes. Siempre envuelve operaciones relacionadas en una transacción.
  • Problema N+1 en consultas: Al recuperar proyectos con sus tareas, hacer una consulta por cada tarea es ineficiente. Usa joinedload de SQLAlchemy para cargar todo en una consulta.
  • Olvidar manejo de excepciones: Sin un bloque try-except y rollback, los errores pueden dejar la transacción abierta, bloqueando recursos.
  • No validar relaciones en esquemas: Asegúrate de que los esquemas Pydantic reflejen las relaciones para validar datos anidados antes de llegar a la base de datos.
  • Ignorar concurrencia: En sistemas con múltiples usuarios, usa with_for_update o versionado optimista para evitar condiciones de carrera.

Checklist de dominio

  1. ¿Puedes definir modelos SQLAlchemy con relaciones uno-a-muchos y muchos-a-muchos correctamente?
  2. ¿Implementas transacciones para operaciones atómicas que involucran múltiples entidades?
  3. ¿Optimizas consultas con joins o carga ansiosa para evitar el problema N+1?
  4. ¿Manejas excepciones y rollbacks en cada operación transaccional?
  5. ¿Usas esquemas Pydantic anidados para validar y serializar datos relacionados?
  6. ¿Diseñas endpoints que devuelven datos anidados (como proyectos con tareas) eficientemente?
  7. ¿Pruebas escenarios de error, como fallos a mitad de transacción, en tu código?

Sistema de Gestión de Biblioteca con Préstamos y Devoluciones

Implementa un sistema CRUD avanzado para una biblioteca donde los libros pueden ser prestados a usuarios, manejando relaciones y transacciones.

  1. Crea los modelos SQLAlchemy: Book (con campos como id, título, autor, disponible), User (id, nombre, email), y Loan (id, book_id, user_id, fecha_préstamo, fecha_devolución). Establece relaciones: un usuario puede tener múltiples préstamos, un libro puede estar en un préstamo a la vez.
  2. Implementa un endpoint POST /loans/ que cree un préstamo. Debe: verificar que el libro esté disponible, crear el registro de préstamo, y actualizar el libro a no disponible. Usa una transacción para asegurar que ambas operaciones sean atómicas.
  3. Crea un endpoint PUT /loans/{loan_id}/return para devolver un libro. Debe: marcar el préstamo como devuelto (agregar fecha_devolución), y actualizar el libro a disponible. Nuevamente, usa transacción.
  4. Implementa un endpoint GET /users/{user_id}/loans que liste todos los préstamos de un usuario con detalles del libro, optimizado para evitar consultas N+1.
  5. Prueba tu implementación con casos como préstamo de libro no disponible o errores durante la transacción.
Pistas
  • Usa ForeignKey en el modelo Loan para conectar con Book y User.
  • En la transacción, considera usar db.flush() para obtener IDs antes de confirmar.
  • Para optimizar la consulta de préstamos, investiga joinedload en SQLAlchemy.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.

Laboratorio de práctica

Antes de marcar esta lección como completa, escribí una evidencia breve para Dominio Profesional de FastAPI: Construcción de APIs Escalables y Seguras: un ejemplo, una decisión, una captura, una mini demo o una nota que puedas reutilizar en portfolio.

Reflexión rápida

¿Qué cambiarías en tu forma de trabajar después de aplicar realizar operaciones crud avanzadas con relaciones y transacciones?

De lección a portfolio

Convertí esta lección en una prueba técnica visible.

Una app pequeña publicada, con README y decisiones explicadas, funciona mejor que una lista de tecnologías sueltas.

Paso 1

Creá una demo mínima que use el concepto de la lección.

Paso 2

Escribí un README corto con objetivo, stack, decisión técnica y mejora futura.

Paso 3

Publicá la demo y enlazala desde tu perfil profesional.

Newsletter Cursalo

Recibí rutas y cursos nuevos

Sumate para recibir recursos orientados a empleo y portfolio.

  • Rutas de empleo
  • Cursos prácticos
  • Portfolio y entrevistas

Sin spam. También podés entrar con tu cuenta para guardar progreso. Iniciá sesión

Realizar Operaciones CRUD Avanzadas con Relacio... | CursaloFalar no WhatsApp