Volver al inicio

UseCase en desarrollo backend: estructuración de lógica de negocio

El artículo explica el patrón UseCase para describir escenarios de negocio en aplicaciones backend. Cubre estructura, beneficios, limitaciones y ejemplos de implementación en PHP y Laravel, incluyendo pruebas y organización de código.

UseCase: cómo describir procesos de negocio en código para desarrolladores
Advertisement 728x90

Patrón UseCase: Estructurando la lógica de negocio en el backend

UseCase es un patrón arquitectónico para definir escenarios de negocio en aplicaciones. Organiza el código alrededor de acciones específicas del sistema —como realizar un pedido o registrar un usuario— en lugar de operaciones técnicas abstractas. Este enfoque mejora la legibilidad, simplifica las pruebas y permite escalar proyectos de manera efectiva, especialmente en sistemas backend complejos.

¿Qué es UseCase y por qué usarlo?

UseCase representa un escenario de negocio completo en la capa de aplicación. Orquesta una secuencia de pasos, delegando reglas de negocio a servicios de dominio y entidades. Por ejemplo, en vez de una operación de bajo nivel como "escribir en la base de datos", un UseCase describe "el usuario realiza un pedido". Esto evita que la lógica de negocio se disperse por controladores y servicios a medida que crece el proyecto. Sin límites claros en los escenarios, el código se vuelve difícil de entender y mantener.

Beneficios clave de UseCase:

Google AdInline article slot
  • Estructura clara: El sistema se define por capacidades de negocio explícitas, no por abstracciones vagas como Service o Manager.
  • Mejor legibilidad: La arquitectura refleja la lógica de negocio, facilitando responder "¿Qué puede hacer esta app?".
  • Pruebas más fáciles: Pruebas de integración a nivel de escenario verifican el flujo completo del usuario sin romper procesos de negocio.

Limitaciones y mejores prácticas

Evita llamar a un UseCase desde otro para prevenir dependencias ocultas y convertirlos en bloques de lógica reutilizables. En escenarios complejos, está bien si no hay ciclos y los escenarios permanecen explícitos. Para lógica reutilizable, extráela a servicios dedicados o componentes de dominio utilizables en múltiples UseCases.

Dónde invocar UseCase:

  • Controlador
  • Comando de consola
  • Job
  • Listener de eventos

UseCase actúa como el punto de entrada a la lógica de negocio de tu app, mediando interacciones externas con el sistema.

Google AdInline article slot

Por qué los servicios solos no bastan

Los servicios funcionan bien en proyectos simples, pero al crecer la lógica de negocio, surgen problemas. Lo que empieza como un simple "guardar en base de datos" para crear un pedido se infla con verificaciones de stock, aplicación de descuentos, deducción de saldos y envío de notificaciones. Esto crea objetos dios —clases hinchadas con dependencias, métodos y efectos secundarios— que hacen el código difícil de leer y probar.

UseCase sigue una regla simple: un UseCase = un proceso de negocio. La lógica duplicada va a servicios compartidos o componentes de dominio, manteniendo el código manejable al aumentar la complejidad.

Estructura de UseCase en PHP y Laravel

Un UseCase típico tiene tres partes:

Google AdInline article slot
  • Handler: Orquesta pasos y condiciones del escenario, sin lógica de negocio de dominio.
  • DataInput: Objeto de datos de entrada sin lógica —solo datos tipados, preferiblemente inmutables.
  • DataOutput: Objeto de resultado que define un contrato claro (p. ej., DTO).

Ejemplo: Crear un pedido:

class StoreOrderDataInput
{
    public function __construct(
        public readonly int $userId,
        public readonly array $items,
    ) {}
}

class StoreOrderDataOutput
{
    public function __construct(
        public readonly int $orderId,
        public readonly string $status,
    ) {}
}

class StoreOrderHandler
{
    public function __construct(
        private OrderRepository $orders
    ) {}

    public function handle(StoreOrderDataInput $input): StoreOrderDataOutput
    {
        $order = new Order(
            userId: $input->userId,
        );

        $orderSaved = $this->orders->save($order);

        return new StoreOrderDataOutput(
            orderId: $orderSaved->id,
            status: $orderSaved->status
        );
    }
}

Organización y pruebas de UseCases en proyectos

En Laravel, almacena UseCases en una estructura como app/UseCases/V1 con versionado para cambios seguros en contratos. Subdirectorios responden "¿Qué puede hacer el sistema?" —p. ej., StoreOrder, CancelOrder, RegisterUser. Esto refleja capacidades de negocio, no detalles técnicos.

Probar UseCases es directo con pruebas de integración a nivel de escenario. Ejemplo de prueba para StoreOrderHandler:

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class StoreOrderFeatureTest extends TestCase
{
    use RefreshDatabase;

    public function testUserCanCreateOrder(): void
    {
        $user = User::factory()->create();

        $payload = [
            'items' => [
                ['id' => 10, 'qty' => 2],
                ['id' => 15, 'qty' => 1],
            ],
        ];

        $response = $this->actingAs($user)
            ->postJson('/api/orders', $payload);

        $response->assertStatus(200);
        $response->assertJsonStructure([
            'orderId',
            'status'
        ]);
        $this->assertDatabaseHas('orders', [
            'user_id' => $user->id
        ]);
    }
}

Conclusiones clave

  • UseCase define escenarios de negocio, no operaciones técnicas, para una estructura de app más limpia.
  • Resuelve la dispersión de lógica en proyectos grandes, manteniendo el código mantenible.
  • Un UseCase = un proceso de negocio, con lógica compartida en servicios.
  • Las pruebas son más simples mediante pruebas de integración que cubren escenarios completos.
  • La estructura del proyecto refleja funcionalidades de negocio, acelerando la incorporación de nuevos miembros.

— Editorial Team

Advertisement 728x90

Leer después