Aplicación de Laravel a gran escala

aplicación

Aplicación Laravel a gran escala

¡Seamos sinceros! Mantener una gran aplicación PHP no es fácil.

Aplicación


Todos sabemos que Laravel es el framework PHP más popular hasta el día de hoy. Su estructura de directorio es buena, bien organizada, definida y sencilla. Trabajar con la estructura de proyecto predeterminada proporcionada por Laravel está absolutamente bien cuando estamos trabajando en un proyecto pequeño o mediano. Pero, cuando se trata de una aplicación grande con más de 50 modelos, es el momento en que empezamos a ahogarnos en nuestra propia base de código.

Mantener una aplicación grande no es una broma. Especialmente, cuando no está organizado correctamente. Y la estructura predeterminada de Laravel definitivamente no es muy útil para este escenario.

Pero primero, echemos un vistazo a la estructura predeterminada y en qué se convierte para aplicaciones grandes.

La estructura de la aplicación predeterminada de Laravel es así:

|- app/
   |- Console/
      |- Commands/
   |- Events/
   |- Exceptions/
   |- Http/
      |- Controllers/
      |- Middleware/
   |- Jobs/
   |- Listeners/
   |- Providers/
   |- User.php
|- database/
   |- factories/
   |- migrations/
   |- seeders
|- config/
|- routes/
|- resources/
   |- assets/
   |- lang/
   |- views/

No hay nada de malo en la estructura. Pero, cuando trabajamos en una gran aplicación; normalmente dividimos nuestra lógica empresarial en repositorios, transformadores, etc.…. algo como lo siguiente:

|- app/
   |- Console/
      |- Commands/
   |- Events/
   |- Exceptions/
   |- Http/
      |- Controllers/
      |- Middleware/
   |- Jobs/
   |- Listeners/
   |- Models/
   |- Presenters/
   |- Providers/
   |- Repositories/
   |- Services/
   |- Transformers/
   |- Validators/
|- database/
   |- factories/
   |- migrations/
   |- seeders
|- config/
|- routes/
|- resources/
   |- assets/
   |- lang/
   |- views/

Obviamente, este es un proyecto de Laravel bien estructurado. Ahora, echemos un vistazo dentro de la carpeta Modelos:

|- app/
  |- Models/
     |- User.php
     |- Role.php
     |- Permission.php
     |- Merchant.php
     |- Store.php
     |- Product.php
     |- Category.php
     |- Tag.php
     |- Client.php
     |- Delivery.php
     |- Invoice.php
     |- Wallet.php
     |- Payment.php
     |- Report.php

Bueno, no es tan malo en absoluto. También contamos con Servicios que manejan todas las lógicas comerciales. También hay carpetas de repositorios, transformadores, validadores que tienen la misma o más clases dentro de ellos. Pero, trabajar en una sola entidad / modelo requiere navegar por diferentes carpetas y archivos. A algunos les puede resultar muy tedioso, pero a otros todavía les parece bien.

 

El problema no está en navegar por diferentes carpetas, sino en mantener el código y la intercomunicación entre los servicios.

 

Analicemos la base del código y veamos qué podemos encontrar:

  • Es una aplicación Monolith
  • Es difícil (para algunos desarrolladores) mantener
  • La productividad es baja (es necesario pensar en la interconexión todo el tiempo)
  • La escala es un problema

La solución es obvia: microservicio. Incluso cuando usamos SOA (Arquitectura Orientada a Servicios), todavía tenemos que dividir nuestra aplicación monolítica en partes independientes más pequeñas para escalarlas por separado.

Impresionante, tenemos una solución. Entonces, ¿por qué no lo hacemos? Divida el código en partes más pequeñas. ¡Obviamente es más fácil decirlo que hacerlo!

Por lo general, separar un servicio requiere dos pasos simples:

  • Mueva los elementos secundarios del servicio (modelos, repositorios, transformadores, etc.) a una nueva aplicación de microservicio PHP más pequeña.
  • Vuelva a factorizar las llamadas a la función de servicio para redirigir el objetivo al microservicio (por ejemplo, crear una solicitud HTTP).

Pero ahora necesita encontrar todos los archivos relacionados con ese servicio. Y sorprendentemente, puede descubrir que ha utilizado sus modelos o repositorios en algún otro lugar del código al omitir el servicio (hice esto en el pasado). Pero este no es el único problema, hay más. Y si los resumimos:

  • Hay demasiados archivos para considerar
  • La probabilidad de cometer errores es alta
  • Crea frustración en el desarrollador
  • A veces es necesario reconsiderar la lógica del dominio.
  • R.I.P. a nuevos desarrolladores

La última razón es muy importante porque es muy difícil para un nuevo desarrollador comprender toda la aplicación en poco tiempo. Pero el director del proyecto no le dará mucho tiempo. Esto conduce a parches de mono, colocación incorrecta del código y el próximo nuevo desarrollador estará más confundido.

Afortunadamente, ya tenemos una solución: HMVC. Dividir toda la aplicación en partes más pequeñas donde cada parte tiene sus propios archivos y carpetas como la  carpeta, y se carga a través de la carga automática , como se muestra a continuación:

|- auth/
   |- Exceptions/
   |- Http/
   |- Listeners/
   |- Models/
   |- Presenters/
   |- Providers/
   |- Repositories/
   |- Services/
   |- Transformers/
   |- Validators/
|- merchant/
   |- Console/
   |- Events/
   |- Exceptions/
   |- Http/
   |- Jobs/
   |- Listeners/
   |- Models/
   |- Presenters/
   |- Providers/
   |- Repositories/
   |- Services/
   |- Transformers/
   |- Validators/
|- database/
   |- factories/
   |- migrations/
   |- seeders
|- config/
|- routes/
|- resources/
   |- assets/
   |- lang/
   |- views/

Pero HMVC agrega más complejidad y cuando queremos mover un módulo en particular a un microservicio; todavía necesitamos mantener los controladores, middlewares, etc.en la base de código principal. La mayoría de las veces, pasar al microservicio requiere redefinir las rutas y los controladores. Por tanto, tenemos que hacer trabajos redundantes. Así que no soy un gran admirador de esta estructura. Porque quiero separar solo lo que (debo) tener para no separar nada más.

El diseño controlado por dominio puede ser una solución

No hay una solucion perfecta. Todo tiene una compensación. Pero todos tienen una preferencia. No voy a hablar sobre el diseño controlado por dominio (DDD) aquí. Desarrolladorul DeLaUnu escribió una gran descripción sobre DDD aquí.

DDD estructura su aplicación Laravel en 4 partes (incluso, solo 3):

  • Aplicación: generalmente contiene, controlador, middleware, ruta
  • Dominio: generalmente contiene lógica empresarial (modelo, repositorio, transformador, política, etc.)
  • Infraestructura: generalmente contiene servicios comunes como registro, correo electrónico, etc.
  • Interfaz: normalmente contiene vistas, idioma y activos.

Si es así de fácil, ¿por qué no estructuramos nuestra aplicación de esta manera y usamos el espacio de nombres?

|- app/
   |- Http/ (Application)
      |- Controllers/
      |- Middleware/
|- Domain/
   |- Models/
   |- Repositories/
   |- Presenters/
   |- Transformers/
   |- Validators/
   |- Services/
|- Infrastructure/
   |- Console/
   |- Exceptions/
   |- Providers/
   |- Events/
   |- Jobs/
   |- Listeners/
|- resources/ (Interface)
   |- assets/
   |- lang/
   |- views/
|- routes/
   |- api.php
   |- web.php

Porque dividir el proyecto en carpetas no va a funcionar. Solo significa que no estamos agregando nada más que un espacio de nombres principal.

Momento de la verdad

En este momento probablemente ya esté molesto y solo quiera ver la solución. Asi que aqui esta:

|- app/
   |- Http/
      |- Controllers/
      |- Middleware/
   |- Providers/
   |- Account/
      |- Console/
      |- Exceptions/
      |- Events/
      |- Jobs/
      |- Listeners/
      |- Models/
         |- User.php
         |- Role.php
         |- Permission.php
      |- Repositories/
      |- Presenters/
      |- Transformers/
      |- Validators/
      |- Auth.php
      |- Acl.php
   |- Merchant/
   |- Payment/
   |- Invoice/
|- resources/
|- routes/

El son los archivos de servicio dentro de la carpeta . Los controladores solo accederán a estas dos clases y llamarán a sus funciones. Otras clases (fuera del dominio) nunca sabrán sobre las otras clases restantes en la  / carpeta. Las funciones dentro de estos servicios solo recibirán tipos de datos PHP básicos como  y POPO (Plain Old PHP Object) pero no instancias de clase. Ejemplo:

Observe que la función de  recibe una  de atributos en lugar del objeto Usuario. Es importante porque se supone que la otra clase que llama a la función no conoce la existencia del modelo de . Esta es la regla básica de toda esta estructura.

Cuando queremos separar el código

Nuestra aplicación se ha vuelto grande y queremos separar el dominio de la  en un microservicio separado y convertirlo en un servidor .

Entonces, solo movemos las siguientes partes:

|- Account/
   |- Console/
   |- Exceptions/
   |- Events/
   |- Jobs/
   |- Listeners/
   |- Models/
      |- User.php
      |- Role.php
      |- Permission.php
   |- Repositories/
   |- Presenters/
   |- Transformers/
   |- Validators/
   |- Auth.php
   |- Acl.php

A una nueva aplicación de Laravel (o tal vez Lumen):

|- app/
   |- Http/
      |- Controllers/
      | - Middleware/
   |- Account/
      |- Events/
      |- Jobs/
      |- Listeners/
      |- Models/
         |- User.php
         |- Role.php
         |- Permission.php
      |- Repositories/
      |- Presenters/
      |- Transformers/
      |- Validators/
      |- Auth.php
      |- Acl.php
|- routes/
|- resources/

Por supuesto, tenemos que escribir código en los controladores y rutas, ya que necesitamos convertirlo en un servidor .

Pero, ¿qué cambio debemos hacer en la base de código principal?

Solo conservamos los archivos de servicio  y cambiamos los códigos dentro de sus funciones en solicitudes HTTP (u otros métodos como mensajería) dirigidos al microservicio recién creado.

...
public function login(array $credentials) {
 // change the code here
}
...

Toda la aplicación seguirá siendo la misma. Y la estructura de la aplicación se verá así:

|- app/
   |- Console/
   |- Exceptions/
   |- Http/
      |- Controllers/
      |- Middleware/
   |- Providers/
   |- Account/
      |- Auth.php
      |- Acl.php
   |- Merchant/
   |- Payment/
   |- Invoice/
|- resources/
|- routes/

Y con este menor esfuerzo, puede mover una parte de su código a un microservicio completamente separado. No puedo pensar en ninguna otra forma de hacer lo menos posible mientras se mueve una parte del código a un microservicio.

Compensaciones

Como dije antes, todo tiene una compensación, esta solución no es diferente. ¡Y aquí tenemos un problema con la migración! Porque en la estructura de carpetas anterior (antes de la separación) todos los archivos de migración se encuentran dentro del directorio . Pero cuando queremos separar un dominio, también necesitamos identificar y mover las migraciones de ese dominio. Esto podría ser difícil porque no tenemos una indicación clara de qué migración pertenece a qué dominio. De alguna manera necesitamos poner un identificador en los archivos de migración.

Ese identificador podría ser un prefijo de dominio. Por ejemplo, podemos nombrar el archivo de migración  en lugar de . También podemos usar el nombre de la tabla  en lugar de  si queremos. que prefiero identificar qué tablas mover durante la separación. Separar los archivos de migración puede ser un poco frustrante, pero si usamos un prefijo o cualquier tipo de marcador, el proceso será menos doloroso con seguridad.

Recent Post