Más allá de CRUDs: [07] Ingresando a la capa de aplicación

Más allá de CRUDs: [07] Ingresando a la capa de aplicación

Este es el artículo #07 de la serie Laravel: Más allá de CRUDs. Fue originalmente publicado por Brent en su blog (puedes encontrar ahí la serie en su idioma original).

La tabla de contenido que conforma esta serie la tienes aquí.

Dicho esto, continuemos 😉.


En el 1er capítulo, dijimos que una de las características de los proyectos de Laravel orientados al dominio es la siguiente:

[…] Lo más importante es que comiences a pensar en grupos de conceptos de negocio relacionados, en lugar de en grupos de código con las mismas propiedades técnicas.

En otras palabras: agrupa tu código en función de lo que parece en el mundo real, en lugar de su propósito en la base del código.

También escribimos que el dominio y el código de la aplicación son dos cosas que van separadas. Además, las aplicaciones pueden usar varios grupos del dominio a la vez si es necesario, para exponer la funcionalidad del dominio al usuario final.

Sin embargo, ¿qué pertenece exactamente a esta capa de aplicación? ¿Cómo agrupamos el código en ese lugar? Estas preguntas serán respondidas en este capítulo.

Estamos entrando en la capa de aplicación.

Varias aplicaciones

Lo primero que hay que entender es que un proyecto puede tener varias aplicaciones. De hecho, cada proyecto de Laravel ya tiene dos por defecto: las aplicaciones HTTP y las de consola. Aún así, hay varias partes más en tu proyecto que pueden considerarse como una aplicación independiente: cada integración de terceros, una API REST, un front-end para el cliente, una aplicación administrativa y todo lo demás.

Todas estas pueden considerarse como aplicaciones separadas, exponiendo y presentando el dominio para sus propios casos de uso únicos. De hecho, tiendo a pensar en la consola Artisan como otra más en esta lista: es una aplicación utilizada por los desarrolladores para trabajar y manipular el proyecto.

Sin embargo, dado que estamos en desarrollo web, nuestro enfoque principal probablemente estará dentro de las aplicaciones relacionadas con HTTP. Entonces, ¿qué incluyen estos? Echemos un vistazo:

  • Controladores
  • Peticiones
  • Reglas de validación específicas de la aplicación
  • Middleware
  • Recursos
  • ViewModels
  • QueryBuilders: los que analizan consultas de URL

Incluso diría que las vistas blade, los archivos JavaScript y CSS pertenecen a una aplicación y no deben dejarse de lado en una carpeta de recursos. Me doy cuenta de que este es un paso demasiado lejos para muchas personas, pero quería mencionarlo, al menos.

Recuerda, el objetivo de una aplicación es obtener la entrada del usuario, pasarla al dominio y representar la salida de manera utilizable para el usuario. Después de varios capítulos en el código de dominio, no debería sorprendernos que la mayor parte del código de la aplicación sea meramente estructural, a menudo aburrido; pasar datos de un punto a otro.

Sin embargo, hay mucho que contar sobre varios de los conceptos mencionados anteriormente: ViewModels, integraciones de terceros, ¿qué pasa con los jobs?; Abordaremos estos temas en capítulos futuros, pero por ahora queremos centrarnos en las ideas principales detrás de la capa de aplicación y una descripción general de estas.

Estructurando aplicaciones HTTP

Hay un punto muy importante que debemos discutir antes de continuar: ¿Cómo se estructurará generalmente una aplicación HTTP? ¿Deberíamos seguir las convenciones de Laravel, o debemos pensarlo un poco más?

Como estoy dedicando una sección de un capítulo a esta pregunta, probablemente puedas adivinar la respuesta. Así que echemos un vistazo a lo que Laravel recomendaría que hagas por defecto.

App/Admin
Http
Controllers
Kernel.php
Middleware
Requests
Resources
Views
ViewModels

Esta estructura está bien en proyectos pequeños, pero sinceramente, no escala bien. Para aclarar lo que quiero decir con esto, te mostraré la estructura de documentos de una aplicación de administración en uno de nuestros proyectos para clientes. Obviamente no puedo revelar demasiada información sobre este proyecto, así que borré la mayoría de los nombres de clase. Sin embargo, como hemos estado utilizando la facturación como ejemplo a lo largo de esta serie, dejé el nombre de algunas clases relacionadas con la factura dentro de la aplicación de administración. Dale un vistazo.

Ah y por cierto, ¡disfruta desplazándote hacia abajo!

App/Admin
Controllers
█████████
███████████████████.php
████████████████████████████████.php
███████████████████████.php
██████████████████████████████████.php
███████████████████████.php
█████████████████████████.php
███████████████████████.php
███████████████████████████████.php
██████████████████.php
████████████████.php
████████████
████████████████████████████████.php
████████████████
████████████████████████████████.php
█████████████████████████████████████████.php
██████████████████████████████.php
████████████████████████████████.php
███████████████████████████████.php
████████████████████████████████.php
█████████████████████████████████.php
██████████████████████████████████.php
█████████████████████████████████.php
██████████████████████████████.php
█████████████████████████████.php
█████████████████████████████████████████.php
█████████████████████████████████████.php
██████████████████████████████████.php
█████████████████████████████████████████.php
████████████████████████████████████.php
█████████████
█████████████████████████████.php
███████████████████████████.php
█████████████████████████████.php
███████████████████████████████████████████.php
███████████████████████████████████████.php
████████████████████████████.php
█████████████████████████████.php
██████████████████████████████.php
█████████████████████████.php
███
████████████████████.php
██████████████████████.php
█████████████████████.php
█████████████.php
█████████████████████.php
██████████████████.php
██████████████████.php
██████████████████████.php
██████████████████████.php
███████████████████.php
████████████████████.php
██████████████████.php
██████████████████████████.php
████████████████████.php
██████████████████.php
████████████████.php
███████████████████.php
█████████████████████.php
████████████
███████████████████████.php
██████████████████.php
████████████████████.php
████████████████████.php
████████████████████████
█████████████████████████████████████████.php
██████████████████████████████████████.php
█████████████████████████████████.php
██████████████████████████████.php
███████████████████████████████.php
███████████████████████████████████████.php
███████████████████████████████.php
████████████████████████████████████████.php
█████████████████████████████████████.php
Invoices
████████████████████████████████████.php
█████████████████████.php
IgnoreMissedInvoicesController.php
████████████████.php
██████████████████████.php
████████████████████.php
InvoiceStatusController.php
InvoicesController.php
MissedInvoicesController.php
█████████████.php
RefreshMissedInvoicesController
█████████████.php
████████
█████████████████████.php
██████████████████.php
██████████████████.php
███████████████████
████████████████████████.php
████████████████████████████.php
███████████████████.php
████████████████████.php
████████████████████.php
██████████████████████████.php
███████████████████████████.php
██████████████████████████████████.php
███████████████████████████████████.php
██████████████████████████.php
███████████████████████████████.php
████████████████████████████████.php
████████████████████████.php
████████████████████████.php
█████████████████████.php
██████████████████████████.php
██████████████████████████████.php
██████████████████████████.php
███████████████████████.php
██████████████████████.php
████████████████████████████.php
███████████████████████.php
█████████████████████████████.php
██████████████████████.php
███████████████████████████████.php
███████████████████████.php
███████████████████████.php
███████████████████████████████.php
████████████████████████.php
██████████████████████████████.php
███████████████████████████████.php
█████████████████████████.php
██████████████████████.php
███████████████████████████.php
█████████████████████████████████.php
███████████████████████████.php
████████████████████████████.php
████████████████████.php
███████████████.php
███████████████.php
███████████████.php
█████████████
█████████████████████.php
█████████████████████████████.php
████████████████████████████.php
███████████████████████████.php
██████████████████████████.php
██████████████████████████.php
█████████████████████████.php
██████████████████.php
█████████████████.php
█████████████████████████.php
██████████████████████.php
████████
███████████████████.php
███████████████████████████.php
█████████████████████.php
█████████████████.php
███████████████
█████████████████.php
███████████████.php
██████████████.php
████████████████████████.php
██████████████████████████.php
██████████████████████████.php
███████████████████.php
████████████████████.php
██████████
███████████████████████████.php
██████████████████.php
███████████████████.php
███████████████.php
████████████████████████.php
█████████████████████████████.php
████████████████████████.php
█████████████████████.php
████████████████████.php
████████████████████████.php
████████████████████████████.php
███████████████████████.php
███████████████████.php
███████████████████████.php
████████████████.php
██████████████████.php
█████████████████.php
██████████████████.php
█████████████████████████████.php
██████████████████████.php
████████████████████.php
████████████████████████.php
███████████████████.php
███████████████.php
██████████████████.php
███████
████████████████.php
███████████████.php
Filters
████████████████████.php
███████████████████████████.php
█████████████████████████.php
██████████████████████████████████.php
██████████████████████.php
█████████████████████████████.php
██████████████████████████.php
████████████████.php
███████████.php
███████████.php
████████████████.php
InvoiceMonthFilter.php
InvoiceOfferFilter.php
InvoiceStatusFilter.php
InvoiceYearFilter.php
█████████████████████.php
███████████.php
███████████████████.php
Middleware
██████████████████████████.php
█████████████████████.php
█████████████████████████████████.php
█████████████████████████████████████.php
EnsureValidHabitantInvoiceCollectionSettingsMiddleware.php
EnsureValidInvoiceDraftSettingsMiddleware.php
██████████████████████████████████.php
EnsureValidOwnerInvoiceCollectionSettingsMiddleware.php
██████████████████████.php
█████████████████████.php
█████████████████████████████.php
█████████████████████████████████.php
████████████████████.php
███████████████████.php
█████████████████.php
Queries
██████████████████.php
███████████████████████████.php
██████████████████.php
████████████████████████.php
██████████████████████.php
██████████████████.php
██████████████████████.php
███████████████████.php
████████████████████████.php
█████████████████████████████.php
████████████████████████.php
████████████████████.php
█████████████████████.php
███████████████████.php
████████████████████.php
█████████████████████████.php
██████████████████████.php
███████████████████████.php
██████████████████.php
██████████████████████████████████.php
███████████████████████████.php
█████████████████████.php
InvoiceCollectionIndexQuery.php
InvoiceIndexQuery.php
█████████████████████████████.php
███████████████████████.php
███████████████.php
████████████████████████████.php
████████████████████████.php
██████████████████.php
█████████████████████.php
█████████████████████████████.php
████████████████████.php
████████████████.php
██████████████████.php
█████████████████████████.php
████████████████████████.php
█████████████████████.php
██████████████████.php
███████████████████.php
███████████████.php
███████████████.php
Requests
█████████████████████████.php
█████████████████████.php
██████████████.php
██████████████.php
██████████████.php
████████████████.php
██████████████████████████████.php
███████████████████████.php
███████████████████████████████.php
█████████████████████████████.php
InvoiceRequest.php
██████████████████████.php
███████████████████.php
█████████████████████.php
████████████.php
████████████████████.php
████████████████████████████████████.php
██████████████████████████████████.php
██████████████████.php
███████.php
██████████████████████.php
████████████.php
███████████.php
████████████████████████.php
Resources
████████████████.php
███████████████.php
██████████████.php
███████████████████████.php
█████████████████████████████.php
███████████████████████████.php
███████████████████.php
███████████████.php
████████████████████████████.php
██████████████████████.php
████████████████████████████████████.php
████████████████████.php
█████████████████████████████████.php
███████████████.php
█████████████████████████████.php
████████████████████.php
████████████████████.php
█████████████████████████████████████.php
████████████████████████.php
████████████████.php
████████████████████.php
█████████████████████████████████████.php
███████████████████████████████.php
███████████████████████████.php
████████████████████.php
█████████████████████.php
█████████████████████████.php
█████████████████████.php
█████████████████.php
█████████████████████.php
██████████████████.php
█████████████████████████.php
█████████████████.php
████████████████.php
████████████████████.php
███████████████████████████████.php
████████████████████████████████.php
████████████████████████████████.php
█████████████████████████████.php
███████████████████████████████.php
████████████████████████.php
████████████████████████████.php
████████████████.php
█████████████████████.php
███████████████████████.php
█████████████████.php
Invoices
InvoiceCollectionDataResource.php
InvoiceCollectionResource.php
InvoiceDataResource.php
InvoiceDraftResource.php
InvoiceLineDataResource.php
InvoiceLineResource.php
InvoiceResource.php
██████████████████.php
█████████████████.php
█████████████.php
InvoiceIndexResource.php
InvoiceLabelResource.php
InvoiceMainOverviewResource.php
InvoiceResource.php
████████████████████.php
█████████████.php
███████████████.php
██████████████████████████.php
████████████████████.php
██████████████████.php
██████████████.php
█████████████████████████████.php
██████████████████████████.php
█████████████████████.php
█████████████████████████.php
█████████████.php
██████████████████████.php
███████████████████.php
███████████████.php
███████████████.php
███████████████.php
█████████████████████.php
█████████████.php
█████████████████.php
███████████████████.php
███████████████████████.php
██████████████.php
██████████████████████████.php
█████████████████.php
██████████████████████.php
█████████████.php
█████████████████.php
████████████.php
███████████████████████.php
████████████████.php
████████████████████.php
████████████████████████████.php
█████████████████████.php
██████████████████████████.php
█████████████████.php
█████████████████████.php
███████████████████.php
████████████.php
████████████████.php
████████████.php
█████████████████████.php
ViewModels
█████████████████.php
███████████████.php
████████████████████████.php
█████████████████.php
████████████████████.php
█████████████████████████████████.php
████████████████████████████.php
██████████████████████████.php
██████████████████████████████.php
████████████████████████.php
█████████████████.php
██████████████████████████████.php
█████████████████████████.php
█████████████████████.php
█████████████.php
████████████████.php
██████████████████.php
█████████████████████.php
██████████████████████.php
██████████████████████████.php
██████████████████████.php
██████████████████.php
████████████████████.php
███████████████████.php
██████████████████.php
█████████████████████████████.php
██████████████████████████.php
█████████████████████.php
█████████████████.php
██████████████████████████.php
███████████████.php
███████████████████████████.php
████████████████████████
████████████████.php
█████████████████.php
██████████████████.php
███████████████████████.php
█████████████████████████.php
███████████████████████████████.php
███████████████████████████████.php
███████████████████████.php
██████████████████.php
InvoiceCollectionHabitantContractPreviewViewModel.php
InvoiceCollectionOwnerContractPreviewViewModel.php
InvoiceCollectionPreviewViewModel.php
InvoiceDraftViewModel.php
InvoiceIndexViewModel.php
InvoiceLabelsViewModel.php
InvoiceStatusViewModel.php
█████████████████.php
█████████████████████.php
██████████████████████.php
█████████████.php
██████████████████.php
███████████████████.php
██████████████.php
██████████████████████.php
████████████████████████████.php
██████████████████████████████████████.php
████████████████.php
█████████████████.php
████████████████████████.php
████████████████████████.php
█████████████████████.php
██████████████████.php
████████████████████.php
██████████████████████████████.php
█████████████████████████.php
███████████████████████████████.php
██████████████████.php
███████████████.php
██████████████.php
████████████████████.php
████████████████████████.php
█████████████████.php
█████████████████████████.php
██████████████████.php
████████████████████████████.php
█████████████████████████████.php
█████████████████████.php
██████████████████████.php
██████████████████.php
██████████████████████.php
█████████████████████████.php
██████████████████████.php
█████████████████.php
█████████████.php
█████████████.php
██████████████████████.php
█████████.php
█████████████████.php

¡Hola de nuevo!

Eso fue mucho por desplazar. Sin embargo, no estoy bromeando: así es como se veía uno de nuestros proyectos después de un año y medio de desarrollo. Y ten en cuenta que este es solo el código de la aplicación de administración, no incluye nada relacionado con el dominio.

Entonces, ¿cuál es el problema central aquí? En realidad, es lo mismo que con nuestro código de dominio en el capítulo 1: estamos agrupando nuestro código en función de las propiedades técnicas, en lugar de su significado en el mundo real; controladores con controladores, solicitudes con solicitudes, ver modelos con ver modelos, etc.

Una vez más, un concepto como las facturas se extiende a través de múltiples directorios y se mezcla con docenas de otras clases. Incluso con el mejor soporte IDE, es muy difícil comprender la aplicación en su conjunto y no hay forma de obtener una visión general de lo que está sucediendo.

¿La solución? No hay sorpresas aquí, espero; es lo mismo que hicimos con los dominios: agrupa el código que se relaciona. En este ejemplo, las facturas:

Admin
Invoices
Controllers
IgnoreMissedInvoicesController.php
InvoiceStatusController.php
InvoicesController.php
MissedInvoicesController.php
RefreshMissedInvoicesController.php
Filters
InvoiceMonthFilter.php
InvoiceOfferFilter.php
InvoiceStatusFilter.php
InvoiceYearFilter.php
Middleware
EnsureValidHabitantInvoiceCollectionSettingsMiddleware.php
EnsureValidInvoiceDraftSettingsMiddleware.php
EnsureValidOwnerInvoiceCollectionSettingsMiddleware.php
Queries
InvoiceCollectionIndexQuery.php
InvoiceIndexQuery.php
Requests
InvoiceRequest.php
Resources
InvoiceCollectionDataResource.php
InvoiceCollectionResource.php
InvoiceDataResource.php
InvoiceDraftResource.php
InvoiceIndexResource.php
InvoiceLabelResource.php
InvoiceLineDataResource.php
InvoiceLineResource.php
InvoiceMainOverviewResource.php
InvoiceResource.php
InvoiceResource.php
ViewModels
InvoiceCollectionHabitantContractPreviewViewModel.php
InvoiceCollectionOwnerContractPreviewViewModel.php
InvoiceCollectionPreviewViewModel.php
InvoiceDraftViewModel.php
InvoiceIndexViewModel.php
InvoiceLabelsViewModel.php
InvoiceStatusViewModel.php

¿Qué tal eso? Cuando trabajes con facturas, tiene un lugar al que ir para saber qué código está disponible para ti. Tiendo a llamar a estos grupos “módulos de aplicación” o “módulos” para abreviar; y puedo decirte por experiencia que hacen la vida mucho más fácil cuando trabajas en proyectos de esta escala.

¿Esto significa que los módulos deben asignarse uno a uno en el dominio? ¡Definitivamente no! Eso sí, podría haber cierta superposición, pero no es obligatorio. Por ejemplo: tenemos un módulo de configuración dentro de la aplicación de administración, que toca varios grupos de dominio a la vez. No tendría sentido tener controladores de configuración separados, ver modelos, etc., distribuidos en múltiples módulos: cuando estamos trabajando en la configuración, es una característica en sí misma; no uno que se extendió por varios módulos solo para estar sincronizado con el dominio.

Otra pregunta que podría surgir al observar esta estructura es.. ¿qué hacer con las clases de propósito general?. Cosas como una clase de solicitud base, middleware que se usa en todas partes, etc… ¿Recuerdas el namespace Support en el capítulo uno? ¡Precisamente existe para esos casos! El soporte contiene todo el código que debería ser accesible a nivel global, pero bien podría haber sido parte del framework.


Ahora que tienes una visión general de cómo podemos estructurar las aplicaciones, es hora de ver algunos de los patrones que usamos ahí para facilitarnos la vida. Comenzaremos con eso la próxima vez, cuando hablemos de modelos de vista (view models).

2019-2024 © kennyhorna.com | @kennyhorna