node.js - requerimientos - Arquitectura modular del nodo
npm node js (3)
Estoy construyendo una aplicación de nodejs que es bastante grande ahora. En un intento por evitar una aplicación de nodo monolítico, he recorrido el camino arquitectónico de un sistema más modular que divide varios componentes en módulos npm separados. Estos se publican usando npm y se instalan en los módulos dependientes. Tengo alrededor de 6 módulos diferentes (los cuales querría dividir en más) y ahora es difícil administrar los paquetes.
Los problemas son:
- Hay una dependencia anidada, así que si cambio el módulo A y el módulo B depende del módulo A y el módulo C depende del módulo B, entonces cuando actualice el módulo AI, necesito publicar una nueva versión, lo que significa que necesito actualizarlo en el módulo. B, lo que significa que también necesito publicar eso y, finalmente, necesito instalar esa nueva versión en el módulo A ... puedes ver dónde puede ser una molestia. Lo que es más, la actualización de las versiones en todo el package.json es manual y, por lo tanto, propensa a errores, y la espera de cada publicación lleva mucho tiempo.
- Los módulos pueden compartir dependencias npm y, por lo tanto, a veces se producen conflictos cuando los paquetes se actualizan. Cuantos más módulos, mayor es la posibilidad de conflicto.
Los beneficios son que tenemos un sistema muy modular donde las bibliotecas se pueden reutilizar fácilmente y existe una clara jerarquía de módulos implementados ya que no puede haber dependencias circulares.
Las posibles soluciones son:
Monolito : para administrar las dependencias como una sola aplicación en un solo repositorio con cada módulo convirtiéndose en un servicio. Esto significa que solo se necesita una actualización y todas las apis del módulo estarán sincronizadas. Sin embargo, hacer referencia a las bibliotecas en el código puede ser un poco molesto (ya que creo que tendrán que ser referenciadas en relación con el archivo local), no estoy seguro de cómo se puede hacer cumplir una jerarquía estructural entre los módulos y la reutilización del código Sé más difícil con los módulos fuera del repositorio.
Microservicios - Para hacer de cada módulo un microservicio . Esto mantiene todos los beneficios del sistema modular, pero me preocupa que agregará mucha complejidad a la construcción y la administración de todos los servicios se convertirá en un trabajo de tiempo completo en sí mismo.
Siga avanzando : encuentre una manera de mantener la arquitectura actual pero elimine el problema de enviar actualizaciones, etc. Tal vez scripts para actualizar versiones y shrinkwrap para garantizar las dependencias correctas. Creo que esto sería difícil y potencialmente lo conduciría a ser un sistema monolítico de una variedad diferente.
La opción 1 me parece la más manejable, pero no quiero perder la estructura modular si no tengo que hacerlo.
Esta es una pregunta bastante amplia, pero cualquier sugerencia / consejo / comentario sería realmente útil.
Gracias
¿Has considerado diferentes estructuras modulares? La decisión de tener microservicios o un monolito afecta la forma en que los componentes se comunican entre sí, la escalabilidad y la capacidad de implementación del sistema, pero aún deberá seguir las mejores prácticas en el diseño de paquetes. De lo contrario, tendrá la misma reacción en cadena cuando actualice un paquete de bajo nivel.
Su estructura actual del paquete C dependiendo del paquete B dependiendo del paquete A causa dificultades en la administración de paquetes porque necesita realizar demasiadas modificaciones a los paquetes de nivel inferior.
Este tipo de problema es una señal de que el diseño del paquete es demasiado adelantado, mientras que el diseño del paquete se debe hacer sobre la marcha.
La ventaja de su estructura actual es que no tiene dependencias acílicas. Si actualiza el módulo B, sabe exactamente que el módulo C está afectado, y nada cambia con el módulo A. Debe seguir siendo así.
Gestionando Dependencias
Estructura de dependencia
Hay principios de diseño de paquetes que están directamente relacionados con los problemas que tiene:
El principio de dependencia estable
Depende en la dirección de la estabilidad.
Dada la estructura original C -> B -> A
, la mayoría de sus cambios deberían ocurrir en C
y A
no debería haber ninguna razón para cambiar.
El principio de abstracciones estables
Un paquete debe ser tan abstracto como estable.
Se relaciona con el principio anterior. Las clases abstractas omiten implementaciones específicas, y hay muchas maneras de hacerlo con Javascript.
Si sigue bien estos principios, podría encontrar que no es un problema tener más de tres paquetes, porque los paquetes de nivel inferior no cambiarán mucho.
¿Qué debería estar en un paquete?
Paquete por característica
Los marcos MVC tan populares en estos días tienen una estructura que separa el controlador, el modelo y la vista en diferentes carpetas. Esta estructura no se escala muy bien, y después de que el proyecto se haya expandido por un tiempo, se vuelve difícil visualizar lo que hace el proyecto, cómo se conectan las diferentes partes entre sí, y no es muy conveniente ver todos los archivos relacionados con una característica particular. Este enfoque llamado paquete por capa no se expande muy bien.
Una mejor manera de organizar sus paquetes sería empaquetar por función . Ahora la colocación de capas no es algo malo, y cuando usted empaqueta por característica, aún debería tener esta estructura en capas.
Estoy trabajando en un modelo teórico para resolver este problema, y solo estoy haciendo un poco de investigación, un poco de experimentación y un poco de sentido común.
Lista de Principios Modulares
- El código está organizado físicamente en carpetas por función.
- El código hace una, y solo una cosa, y lo hace muy bien.
- Cada característica se puede agregar o eliminar en cualquier momento porque no tiene dependencias con otras características.
- Todas las comunicaciones con otros módulos no se hacen directamente. En su lugar se utiliza un Sandbox o middleware.
- Si varios módulos requieren una funcionalidad común, lo toman de una estructura semi-inmutable () jerárquica superior.
Ventajas y desventajas Este enfoque busca un objetivo específico: el acoplamiento suelto. La idea subyacente es que cada módulo se puede implementar por sí solo, se puede desarrollar y probar individualmente, y muchas personas pueden contribuir al mismo tiempo con características. Mira WordPress, o nodo de ecosistemas. Toma este ejemplo y muévelo hacia tu proyecto.
Un ejemplo
CSS ha sido un claro ejemplo para mí de cómo puede funcionar este enfoque modular. Si tiene un sitio con muchas páginas y muchas secciones en él, y su cliente desea que cada sección tenga una variante del aspecto, es probable que termine con algunos cientos de definiciones de CSS en un archivo grande de CSS minificado.
La gobernanza de ese CSS puede requerir variables, preprocesador, PostCSS, manejo de Javascript ... pero la verdad es que nunca usarás más de unas pocas definiciones de CSS en cada página.
Si todas las páginas se pueden dividir en tipos modulares y cada tipo con sus propias reglas, probablemente puede terminar con mucho más código, pero los archivos más pequeños se aplican uno a la vez. Probablemente no necesitará minimizar ningún archivo porque todos tienen lo que se necesita.
Propuesta de estructura de carpeta Estoy trabajando en la idea de que todo el código debe organizarse de la misma manera. Carpetas principales:
- núcleo
- extensiones
- motores
Dentro de cada una una estructura como esta: núcleo.
----- administrador de extensiones
------------ css
------------ img
------------ medios
------------ js
------------ ver
------------ db
------------ datos
------------ aux
------------ config.json
------------ README.txt
------------ settings.txt
----- caja de arena
------------ css
------------ img
------------ medios
------------ js
------------ ver
------------ db
------------ datos
------------ aux ------------ config.json
------------ README.txt
------------ settings.txt
----- global
------------ css
------------ img
------------ medios
------------ js
------------ ver
------------ db
------------ datos
------------ aux
------------ config.json
------------ README.txt
------------ settings.txt
extensiones
----- citas
------------ css
------------ img
------------ medios
------------ js
------------ ver
------------ db
------------ datos
------------ aux
------------ config.json
------------ README.txt
------------ settings.txt
----- calendario
------------ css
------------ img
------------ medios
------------ js
------------ ver
------------ db
------------ datos
------------ aux
------------ config.json
------------ README.txt
------------ settings.txt
----- promociones
------------ css
------------ img
------------ medios
------------ js
------------ ver
------------ db
------------ datos
------------ aux
------------ config.json
------------ README.txt
------------ settings.txt
Espero que estas ideas puedan ayudar, cualquier comentario es bienvenido.
Recomiendo ir por la solución 2.
- Desglosa todo tu código en pequeños módulos.
- Implementar acoplamiento suelto con emisores de eventos.
- no hay ningún valor agregado en el almacenamiento de cada módulo como su propio paquete npm, a menos que se utilicen de forma independiente fuera de su aplicación.
Los dos problemas que ha descrito están causados simplemente por el hecho de que cada módulo se almacena de forma independiente como un paquete npm.
Beneficios
- El problema 1 se resuelve ya que ya no es necesario administrar paquetes npm en
package.json
. - El problema 2 se resuelve ya que solo tiene un
package.json
administra todas las dependencias - Todavía tiene una estructura modular limpia gracias al uso de módulos node.js separados.
Ejemplo de implementación
Hace unos meses, reformulé una aplicación node.js monolítica utilizando estos principios, lo que realmente facilitó el mantenimiento y no agregó gastos generales al proceso de construcción.
El patrón es el siguiente:
módulo principal es app.js
var sys = require(''sys'')
, events = require(''events'')
, UserModel = require(''./model/user'') // this is a microservice taking care of managing user data
, Logic = require(''./controller/logic'') // this is another microservice doing some work
var App = function (server, app) {
this.controller = (
logic: new Logic(this) // "this" is the app instance, it''s passed to all microservices, which will benefit from the access to event emitter...
}
this.model = {
new UserModel(this)
}
// ...
}
sys.inherits(App, events.EventEmitter)
module.exports = App
Un microservicio se ve así:
/**
* Logic functions
*
* this controller does ...
*
* @constructor
*
*/
function Logic(app) {
/****************************************
*
* Private methods
*
****************************************/
/**
* this is a private function in the controller
*
* @private
*/
function computeSomething () {
var res = 3
app.emit(''computed'', res) // emit event, that can be liseted to by some other modules
return res
}
/****************************************
*
* Public methods
*
****************************************/
/**
*
* This function can be called directly by the other modules using "app.controller.logic.work()"
*
*/
this.work = function () {
return ''result is '' + computeSomething()
}
/****************************************
*
* Event listeners
*
****************************************/
/**
* listener: event from the app - loose-coupling magic happens thanks to this. Recommended over public functions.
*
* @private
*/
app.on(''data-ready'', this.work)
}
module.exports = Logic