Ir al contenido principal
Laravel, shipping fast.

Capitulo 2

Arquitectura central

Julian Beaujardin

Aquí entramos en la estructura real de tu API. No la filosofía abstracta de la que hablábamos antes, sino los patrones concretos y prácticos que usarás en cada endpoint. Cuando digo «arquitectura» no me refiero a diagramas sobredimensionados ni a abstracciones complejas que suenan inteligentes en una reunión. Me refiero a la forma concreta de organizar tu código —dónde vive la validación, cómo se da formato a las respuestas, qué hacen realmente los controladores— para que mañana puedas añadir un endpoint nuevo con la misma facilidad que hoy, sin repensar nada.

Piensa en la última vez que heredaste código desordenado. Quizá era la API de otra persona. Quizá era tu propia API de hace tres años. En cualquier caso, probablemente recuerdes la sensación: cada endpoint era distinto. La validación ocurría de tres formas. Las respuestas tenían estructuras inconsistentes. Los controladores tenían doscientas líneas mezclando lo HTTP con lo de negocio. Tenías que leer cada endpoint como un arqueólogo desenterrando ruinas.

No quieres ser quien escribe código así. Y desde luego no quieres ser quien lo hereda.

La buena noticia: puedes evitarlo por completo con los patrones adecuados. Este capítulo cubre los patrones fundacionales que aparecerán en cada endpoint que construyas: Controllers, FormRequests, Response Wrappers, DTOs, Resources, Mappers y Facades.

No son conceptos nuevos que tengas que aprender. No son abstracciones complejas. Son conceptos que Laravel ya trae, aplicados de forma consistente. Los reconocerás todos. La potencia viene de usarlos igual, en todas partes, hasta que la consistencia se vuelve automática.

Al terminar este capítulo entenderás el plano que seguirá tu API entera. Cada endpoint de licencias, cada endpoint de usuarios, cada endpoint de dominios: todos usarán los mismos patrones. Esa consistencia es lo que te permite publicar el endpoint cincuenta tan rápido como el primero.

Controladores: mantenlos simples

Una opinión polémica: la mayoría de los controladores están gordos. Hacen demasiado. Validación, lógica de negocio, transformación, formato de respuesta, todo en un método.

Los controladores deberían ser obvios y aburridos.

Los controladores gordos se vuelven imposibles de probar y más difíciles de leer y de cambiar. El siguiente desarrollador ve que la validación ocurre en línea en el controlador, y la copia. Ve lógica de negocio mezclada, y añade más. Para el endpoint número diez tienes diez patrones distintos. Para el número cien mantienes una base de código donde cada endpoint hace las cosas de forma diferente.

// Bad: Fat controller doing everything
public function create(Request $request): Response
{
    if (!$request->has('name') || !$request->has('domain')) {
        return response()->json(['error' => 'Name and domain required'], 422);
    }

    $client = new GuzzleHttp\Client();
    $response = $client->post('https://api.statamic.com/licenses', [
        'json' => [
            'name' => $request->input('name'),
            'domain' => $request->input('domain'),
        ],
        'headers' => [
            'Authorization' => 'Bearer ' . config('services.statamic.key')
        ],
    ]);

    $data = json_decode($response->getBody(), true);

    $formatted = [
        'key' => $data['key'],
        'name' => $data['name'],
        'domain' => $data['domain'],
        'status' => $data['status'],
    ];

    return response()->json([
        'data' => $formatted
    ], 201);
}

Este es el enemigo. No la complejidad: la inconsistencia. La inconsistencia significa que cada desarrollador tiene que entender el patrón concreto de cada endpoint que toca. Significa que las revisiones duran horas porque no sólo revisas la lógica: estás analizando una estructura distinta en cada endpoint. Significa que la gente nueva pasa semanas aprendiendo las reglas no escritas de tu API en lugar de publicar funcionalidades.

La regla es simple: los controladores reciben peticiones y devuelven respuestas. Todo lo demás es trabajo de otro.

// Good: Thin controller delegating work
public function create(CreateLicenseRequest $request): Responsable
{
    return new ModelResponse(
        data: new LicenseResource(
            resource: LicenseDTOMapper::toDTO(
                LicenseFacade::addLicense(
                    name: $request->validated('name'),
                    domain: $request->validated('domain'),
                )
            ),
        ),
        status: Response::HTTP_CREATED, //201
    );
}

Cada paso está claro:

  • El FormRequest se encarga de la validación
  • El Responsable se encarga de lo HTTP (código de estado, cabeceras, estructura)
  • El Resource se encarga del formato JSON (de DTO a respuesta de API)
  • El Mapper::toDTO() se encarga de la transformación (de datos crudos a objeto tipado)
  • La Facade se encarga de la lógica de negocio

Lo lees una vez y entiendes qué hace. Sin sorpresas. Sin lógica enterrada. Cuando algo tenga que cambiar —un campo nuevo, otra validación, otro proveedor— sabes exactamente dónde mirar. Cuando necesites añadir registro, va en la facade. Cuando necesites una comprobación de permisos, va en el FormRequest. Cada responsabilidad vive en un solo sitio.

Todos los controladores se ven finos. La gente nueva se incorpora y entiende de inmediato cada endpoint porque ha visto el patrón una vez. Las revisiones son rápidas porque la estructura es predecible. Probar es simple porque cada componente está aislado. El trabajo de tu controlador es recibir la petición HTTP, delegar en servicios o facades para la lógica de negocio, mapear DTOs si hace falta, dar formato a la respuesta y devolverla. Eso es todo. Todo lo demás va en otro sitio.