Service Layer en FastAPI

Intermedio
FastAPI
FastAPI
Actualizado: 23/09/2025

Separación de responsabilidades

El Service Layer representa una capa intermedia fundamental en aplicaciones FastAPI que se sitúa entre los endpoints de la API y la lógica de acceso a datos. Esta arquitectura permite mantener una separación clara de responsabilidades, donde cada componente tiene un propósito específico y bien definido.

La separación de responsabilidades en FastAPI mediante el Service Layer se basa en el principio de que cada capa debe tener una responsabilidad única y claramente definida. Los endpoints se encargan únicamente de manejar las peticiones HTTP, los servicios contienen la lógica de negocio, y los repositories gestionan el acceso a datos.

Estructura de capas en FastAPI

Una aplicación bien estructurada con Service Layer mantiene la siguiente jerarquía de responsabilidades:

  • Capa de presentación (endpoints): Maneja peticiones HTTP, validación de entrada y formateo de respuestas
  • Capa de servicio: Implementa la lógica de negocio y coordina operaciones
  • Capa de repositorio: Gestiona el acceso y persistencia de datos
  • Capa de modelo: Define las entidades de dominio

Esta estructura permite que cada capa se enfoque en su responsabilidad específica sin conocer los detalles de implementación de otras capas.

Implementación básica del Service Layer

Un servicio en FastAPI encapsula la lógica de negocio relacionada con una entidad o funcionalidad específica. Aquí tienes un ejemplo básico de implementación:

# services/user_service.py
from typing import Optional, List
from repositories.user_repository import UserRepository
from schemas.user_schemas import UserCreate, UserUpdate
from models.user import User

class UserService:
    def __init__(self, user_repository: UserRepository):
        self.user_repository = user_repository
    
    def create_user(self, user_data: UserCreate) -> User:
        # Lógica de negocio antes de crear
        if self._email_exists(user_data.email):
            raise ValueError("El email ya está en uso")
        
        # Aplicar reglas de negocio
        processed_data = self._process_user_data(user_data)
        
        # Delegar la persistencia al repository
        return self.user_repository.create(processed_data)
    
    def _email_exists(self, email: str) -> bool:
        """Validación de regla de negocio privada"""
        existing_user = self.user_repository.find_by_email(email)
        return existing_user is not None
    
    def _process_user_data(self, user_data: UserCreate) -> dict:
        """Procesar datos según reglas de negocio"""
        return {
            "name": user_data.name.strip().title(),
            "email": user_data.email.lower(),
            "is_active": True
        }

Separación de concerns en endpoints

Los endpoints de FastAPI se simplifican considerablemente cuando utilizan el Service Layer, ya que solo necesitan manejar aspectos relacionados con HTTP:

# routes/users.py
from fastapi import APIRouter, Depends, HTTPException
from services.user_service import UserService
from schemas.user_schemas import UserCreate, UserResponse
from dependencies import get_user_service

router = APIRouter()

@router.post("/users/", response_model=UserResponse)
async def create_user(
    user_data: UserCreate,
    user_service: UserService = Depends(get_user_service)
):
    try:
        # El endpoint solo maneja HTTP concerns
        user = user_service.create_user(user_data)
        return user
    except ValueError as e:
        # Traducir excepciones de negocio a HTTP
        raise HTTPException(status_code=400, detail=str(e))

Beneficios de la separación de responsabilidades

La separación clara de responsabilidades mediante Service Layer aporta múltiples ventajas a tu aplicación FastAPI:

  • Mantenibilidad: Cada capa es independiente y puede modificarse sin afectar otras
  • Testabilidad: Los servicios pueden probarse de forma aislada sin depender de la infraestructura HTTP
  • Reutilización: La lógica de negocio puede reutilizarse en diferentes contextos
  • Escalabilidad: Permite dividir el trabajo entre diferentes desarrolladores por capas

Gestión de dependencias entre capas

Para mantener la separación adecuada, es importante gestionar las dependencias correctamente. FastAPI permite inyectar servicios en los endpoints:

# dependencies.py
from fastapi import Depends
from sqlalchemy.orm import Session
from database import get_db
from repositories.user_repository import UserRepository
from services.user_service import UserService

def get_user_repository(db: Session = Depends(get_db)) -> UserRepository:
    return UserRepository(db)

def get_user_service(
    user_repo: UserRepository = Depends(get_user_repository)
) -> UserService:
    return UserService(user_repo)

Esta aproximación garantiza que cada capa reciba sus dependencias de forma controlada, manteniendo el acoplamiento bajo y la cohesión alta entre componentes relacionados.

Composición de servicios

La composición de servicios permite crear funcionalidades complejas combinando múltiples servicios especializados. Este patrón es especialmente útil cuando una operación de negocio requiere coordinar diferentes dominios o entidades dentro de tu aplicación FastAPI.

Principios de composición

Un servicio compuesto actúa como coordinador entre varios servicios especializados, orquestando sus operaciones para completar un flujo de negocio complejo. La clave está en mantener cada servicio enfocado en su dominio específico mientras permite su colaboración.

La composición horizontal ocurre cuando un servicio utiliza múltiples servicios del mismo nivel para completar una operación:

# services/order_service.py
from typing import List
from services.user_service import UserService
from services.product_service import ProductService
from services.inventory_service import InventoryService
from services.payment_service import PaymentService
from schemas.order_schemas import OrderCreate, OrderResponse

class OrderService:
    def __init__(
        self,
        user_service: UserService,
        product_service: ProductService,
        inventory_service: InventoryService,
        payment_service: PaymentService
    ):
        self.user_service = user_service
        self.product_service = product_service
        self.inventory_service = inventory_service
        self.payment_service = payment_service
    
    def process_order(self, order_data: OrderCreate) -> OrderResponse:
        # Coordinar múltiples servicios especializados
        user = self.user_service.validate_user(order_data.user_id)
        products = self.product_service.get_products(order_data.product_ids)
        
        # Verificar disponibilidad antes del procesamiento
        self.inventory_service.reserve_stock(order_data.items)
        
        try:
            # Procesar pago
            payment = self.payment_service.process_payment(
                user_id=user.id,
                amount=self._calculate_total(products, order_data.items)
            )
            
            # Confirmar la orden solo si el pago es exitoso
            return self._create_confirmed_order(user, products, payment)
            
        except PaymentError:
            # Liberar stock reservado en caso de fallo
            self.inventory_service.release_reserved_stock(order_data.items)
            raise

Patrón de servicios agregados

Los servicios agregados encapsulan operaciones que requieren coordinar múltiples entidades relacionadas. Este patrón es útil cuando necesitas mantener consistencia entre diferentes dominios:

# services/ecommerce_service.py
class EcommerceService:
    def __init__(
        self,
        order_service: OrderService,
        notification_service: NotificationService,
        audit_service: AuditService
    ):
        self.order_service = order_service
        self.notification_service = notification_service
        self.audit_service = audit_service
    
    def complete_purchase(self, purchase_data: PurchaseRequest) -> PurchaseResult:
        # Orquestar el flujo completo de compra
        order = self.order_service.process_order(purchase_data.order)
        
        # Registrar la operación para auditoría
        self.audit_service.log_purchase(order.id, purchase_data.user_id)
        
        # Notificar al usuario y al sistema
        self.notification_service.send_order_confirmation(
            user_id=purchase_data.user_id,
            order_id=order.id
        )
        
        return PurchaseResult(
            order=order,
            status="completed",
            notifications_sent=True
        )

Composición con inyección de dependencias

FastAPI facilita la composición de servicios mediante su sistema de inyección de dependencias, permitiendo crear jerarquías complejas de servicios de forma limpia:

# dependencies.py
from fastapi import Depends

def get_order_service(
    user_service: UserService = Depends(get_user_service),
    product_service: ProductService = Depends(get_product_service),
    inventory_service: InventoryService = Depends(get_inventory_service),
    payment_service: PaymentService = Depends(get_payment_service)
) -> OrderService:
    return OrderService(
        user_service,
        product_service, 
        inventory_service,
        payment_service
    )

def get_ecommerce_service(
    order_service: OrderService = Depends(get_order_service),
    notification_service: NotificationService = Depends(get_notification_service),
    audit_service: AuditService = Depends(get_audit_service)
) -> EcommerceService:
    return EcommerceService(order_service, notification_service, audit_service)

Manejo de transacciones en servicios compuestos

Cuando múltiples servicios participan en una operación, es crucial manejar las transacciones de forma coordinada. Una aproximación común es usar el patrón Unit of Work:

# services/transaction_service.py
from contextlib import contextmanager
from sqlalchemy.orm import Session

class TransactionalService:
    def __init__(self, db_session: Session):
        self.db = db_session
    
    @contextmanager
    def transaction(self):
        """Context manager para operaciones transaccionales"""
        try:
            yield self.db
            self.db.commit()
        except Exception:
            self.db.rollback()
            raise
        finally:
            self.db.close()

class CompositeOrderService(OrderService):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    
    def process_order_transactionally(self, order_data: OrderCreate) -> OrderResponse:
        with self.transaction() as tx:
            # Todas las operaciones dentro de la misma transacción
            user = self.user_service.validate_user(order_data.user_id)
            
            # Reservar inventario
            reservation = self.inventory_service.reserve_with_transaction(
                order_data.items, tx
            )
            
            # Procesar pago
            payment = self.payment_service.process_with_transaction(
                user.id, self._calculate_total(order_data.items), tx
            )
            
            # Crear orden final
            return self._create_order_with_transaction(
                user, reservation, payment, tx
            )

Composición condicional

Los servicios pueden componer dinámicamente otros servicios basándose en condiciones específicas del negocio:

# services/flexible_order_service.py
class FlexibleOrderService:
    def __init__(
        self,
        standard_payment_service: PaymentService,
        premium_payment_service: PremiumPaymentService,
        user_service: UserService
    ):
        self.standard_payment = standard_payment_service
        self.premium_payment = premium_payment_service
        self.user_service = user_service
    
    def process_adaptive_order(self, order_data: OrderCreate) -> OrderResponse:
        user = self.user_service.get_user(order_data.user_id)
        
        # Seleccionar servicio de pago basado en el tipo de usuario
        payment_service = (
            self.premium_payment if user.is_premium 
            else self.standard_payment
        )
        
        # Aplicar lógica específica según el contexto
        if user.is_premium:
            return self._process_premium_order(order_data, payment_service)
        else:
            return self._process_standard_order(order_data, payment_service)

Comunicación entre servicios compuestos

Para mantener el bajo acoplamiento, los servicios compuestos deben comunicarse a través de interfaces bien definidas y evitar el conocimiento directo de implementaciones internas:

# services/catalog_service.py
from abc import ABC, abstractmethod
from typing import Protocol

class ProductPricingProtocol(Protocol):
    def calculate_price(self, product_id: int, quantity: int) -> float: ...

class CatalogService:
    def __init__(
        self,
        product_service: ProductService,
        pricing_service: ProductPricingProtocol,  # Interface, no implementación
        recommendation_service: RecommendationService
    ):
        self.product_service = product_service
        self.pricing_service = pricing_service
        self.recommendation_service = recommendation_service
    
    def get_product_catalog(self, user_id: int, category: str) -> CatalogResponse:
        # Obtener productos base
        products = self.product_service.get_by_category(category)
        
        # Enriquecer con precios dinámicos
        enriched_products = []
        for product in products:
            price = self.pricing_service.calculate_price(product.id, 1)
            enriched_products.append({
                **product.model_dump(),
                "current_price": price
            })
        
        # Agregar recomendaciones personalizadas
        recommendations = self.recommendation_service.get_for_user(user_id)
        
        return CatalogResponse(
            products=enriched_products,
            recommendations=recommendations,
            category=category
        )

Esta aproximación de composición de servicios permite crear aplicaciones FastAPI escalables y mantenibles, donde cada servicio mantiene su responsabilidad específica mientras colabora efectivamente con otros para completar operaciones de negocio complejas.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en FastAPI

Documentación oficial de FastAPI
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, FastAPI es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de FastAPI

Explora más contenido relacionado con FastAPI y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender la importancia de la separación de responsabilidades en aplicaciones FastAPI.
  • Aprender la estructura de capas: endpoints, servicios, repositorios y modelos.
  • Implementar un Service Layer que encapsule la lógica de negocio.
  • Gestionar la inyección de dependencias para mantener bajo acoplamiento.
  • Aplicar patrones de composición de servicios para operaciones complejas y transaccionales.