
La comunidad moderna de desarrolladores est谩 ahora m谩s que nunca sujeta a la moda y las tendencias, y esto es especialmente cierto para el mundo del desarrollo front-end. Nuestros marcos y nuevas pr谩cticas son el valor principal, y la mayor铆a de los CV, vacantes y programas de conferencias consisten en enumerarlos. Y aunque el desarrollo de ideas y herramientas no es negativo en s铆 mismo, sino que debido al deseo constante de los desarrolladores de seguir tendencias evasivas, comenzamos a olvidar la importancia del conocimiento te贸rico general sobre la arquitectura de aplicaciones.
La prevalencia del valor de ajuste sobre el conocimiento de la teor铆a y las mejores pr谩cticas ha llevado al hecho de que la mayor铆a de los proyectos nuevos de hoy en d铆a tienen un nivel extremadamente bajo de mantenimiento, creando as铆 inconvenientes significativos para los desarrolladores (la alta complejidad constante de estudiar y modificar el c贸digo) y para los clientes (tasas bajas y alto costo de desarrollo).
Para influir al menos de alguna manera en la situaci贸n actual, hoy me gustar铆a contarles sobre qu茅 es una buena arquitectura, c贸mo es aplicable a las interfaces web y, lo m谩s importante, c贸mo evoluciona con el tiempo.
NB : Como ejemplos en el art铆culo, solo se usar谩n los marcos con los que el autor trat贸 directamente, y se prestar谩 una atenci贸n significativa a React y Redux aqu铆. Pero, a pesar de esto, muchas de las ideas y principios descritos aqu铆 son de naturaleza general y pueden proyectarse con m谩s o menos 茅xito en otras tecnolog铆as de desarrollo de interfaces.Arquitectura para tontos
Para comenzar, tratemos con el t茅rmino en s铆. En palabras simples, la arquitectura de cualquier sistema es la definici贸n de sus componentes y el esquema de interacci贸n entre ellos. Este es un tipo de base conceptual sobre la cual se implementar谩 m谩s adelante la implementaci贸n.
La tarea de la arquitectura es satisfacer los requisitos externos para el sistema dise帽ado. Estos requisitos var铆an de un proyecto a otro y pueden ser bastante espec铆ficos, pero en el caso general son para facilitar los procesos de modificaci贸n y expansi贸n de las soluciones desarrolladas.
En cuanto a la calidad de la arquitectura, generalmente se expresa en las siguientes propiedades:
-
Acompa帽amiento : la predisposici贸n ya mencionada del sistema para estudiar y modificar (la dificultad de detectar y corregir errores, ampliar la funcionalidad, adaptar la soluci贸n a otro entorno o condiciones)
-
Reemplazabilidad : la capacidad de cambiar la implementaci贸n de cualquier elemento del sistema sin afectar a otros elementos
-
Testabilidad : la capacidad de verificar el correcto funcionamiento del elemento (la capacidad de controlar el elemento y observar su estado)
-
Portabilidad : la capacidad de reutilizar un elemento en otros sistemas
-
Usabilidad : el grado general de conveniencia del sistema cuando es operado por el usuario final
Tambi茅n se menciona por separado uno de los principios clave de la construcci贸n de una arquitectura de calidad: el principio de
separaci贸n de preocupaciones . Consiste en el hecho de que cualquier elemento del sistema debe ser responsable exclusivamente de una sola tarea (aplicada, por cierto, al c贸digo de la aplicaci贸n: ver
principio de responsabilidad 煤nica ).
Ahora que tenemos una idea del concepto de arquitectura, veamos qu茅 patrones de dise帽o arquitect贸nico nos pueden ofrecer en el contexto de las interfaces.
Las tres palabras mas importantes
Uno de los patrones m谩s famosos de desarrollo de interfaz es MVC (Model-View-Controller), cuyo concepto clave es dividir la l贸gica de la interfaz en tres partes separadas:
1.
Modelo : es responsable de recibir, almacenar y procesar datos
2.
Vista : responsable de la visualizaci贸n de datos
3.
Controlador : controla el modelo y la vista
Este patr贸n tambi茅n incluye una descripci贸n de la interacci贸n entre ellos, pero aqu铆 esta informaci贸n se omitir谩 debido al hecho de que despu茅s de alg煤n tiempo al p煤blico en general se le present贸 una modificaci贸n mejorada de este patr贸n llamado MVP (Model-View-Presenter), que es este esquema original interacci贸n muy simplificada:

Como estamos hablando espec铆ficamente de interfaces web, utilizamos otro elemento bastante importante que generalmente acompa帽a a la implementaci贸n de estos patrones: un enrutador. Su tarea es leer la URL y llamar a los presentadores asociados con ella.
El esquema anterior funciona de la siguiente manera:
1. El enrutador lee la URL y llama al presentador asociado
2-5. El presentador recurre al modelo y obtiene los datos necesarios de 茅l.
6. El presentador transfiere datos del Modelo a la Vista, lo que implementa su visualizaci贸n.
7. Durante la interacci贸n del usuario con la interfaz, View notifica al presentador sobre esto, lo que nos devuelve al segundo punto
Como ha demostrado la pr谩ctica, MVC y MVP no son una arquitectura ideal y universal, pero a煤n hacen una cosa muy importante: indican tres 谩reas clave de responsabilidad, sin las cuales no se puede implementar ninguna interfaz de una forma u otra.
NB: En general, los conceptos de Controlador y Presentador significan lo mismo, y la diferencia en su nombre es necesaria solo para diferenciar los patrones mencionados, que solo difieren en la implementaci贸n de las comunicaciones .MVC y representaci贸n del servidor
A pesar de que MVC es un patr贸n para implementar un cliente, tambi茅n encuentra su aplicaci贸n en el servidor. Adem谩s, es en el contexto del servidor que es m谩s f谩cil demostrar los principios de su funcionamiento.
En los casos en que se trata de sitios de informaci贸n cl谩sicos, donde la tarea del servidor web es generar p谩ginas HTML para el usuario, MVC tambi茅n nos permite organizar una arquitectura de aplicaci贸n bastante concisa:
- El enrutador lee los datos de la solicitud HTTP recibida
(GET / user-profile / 1) y llama al controlador asociado
(UsersController.getProfilePage (1))- El controlador llama al Modelo para obtener la informaci贸n necesaria de la base de datos
(UsersModel.get (1))- El controlador pasa los datos recibidos a View
(View.render ('users / profile', user)) y recibe el marcado HTML de 茅l, que lo devuelve al cliente
En este caso, View generalmente se implementa de la siguiente manera:

const templates = { 'users/profile': ` <div class="user-profile"> <h2>{{ name}}</h2> <p>E-mail: {{ email }}</p> <p> Projects: {{#each projects}} <a href="/projects/{{id}}">{{name}}</a> {{/each}} </p> <a href=/user-profile/1/edit>Edit</a> </div> ` }; class View { render(templateName, data) { const htmlMarkup = TemplateEngine.render(templates[templateName], data); return htmlMarkup; } }
NB: el c贸digo anterior se ha simplificado intencionalmente para usarlo como ejemplo. En proyectos reales, las plantillas se exportan a archivos separados y pasan por la etapa de compilaci贸n antes de su uso (ver Handlebars.compile () o _.template () ).Aqu铆 se utilizan los llamados motores de plantillas, que nos proporcionan herramientas para una descripci贸n conveniente de las plantillas de texto y mecanismos para sustituir datos reales en ellos.
Tal enfoque para la implementaci贸n de View no solo demuestra una separaci贸n ideal de responsabilidades, sino que tambi茅n proporciona un alto grado de comprobabilidad: para verificar la exactitud de la pantalla, es suficiente para nosotros comparar la l铆nea de referencia con la l铆nea que obtuvimos del motor de plantillas.
Por lo tanto, al usar MVC, obtenemos una arquitectura casi perfecta, donde cada uno de sus elementos tiene un prop贸sito muy espec铆fico, conectividad m铆nima y tambi茅n tiene un alto nivel de capacidad de prueba y portabilidad.
En cuanto al enfoque con la generaci贸n de marcado HTML utilizando herramientas de servidor, debido a la baja UX, este enfoque gradualmente comenz贸 a ser reemplazado por SPA.
Backbone y MVP
Uno de los primeros marcos para llevar completamente la l贸gica de visualizaci贸n al cliente fue
Backbone.js . La implementaci贸n de Router, Presenter y Model en ella es bastante est谩ndar, pero la nueva implementaci贸n de View merece nuestra atenci贸n:

const UserProfile = Backbone.View.extend({ tagName: 'div', className: 'user-profile', events: { 'click .button.edit': 'openEditDialog', }, openEditDialog: function(event) {
Obviamente, la implementaci贸n del mapeo se ha vuelto mucho m谩s complicada: se ha agregado a la estandarizaci贸n elemental escuchar los eventos del modelo y el DOM, as铆 como la l贸gica de su procesamiento. Adem谩s, para mostrar los cambios en la interfaz, es altamente deseable no volver a renderizar completamente la Vista, sino hacer un trabajo m谩s fino con elementos DOM espec铆ficos (generalmente usando jQuery), lo que requiere escribir mucho c贸digo adicional.
Debido a la complicaci贸n general de la implementaci贸n de View, sus pruebas se volvieron m谩s complicadas, ya que ahora estamos trabajando directamente con el 谩rbol DOM, para las pruebas necesitamos usar herramientas adicionales que proporcionen o emulen el entorno del navegador.
Y los problemas con la nueva implementaci贸n de View no terminaron all铆:
Adem谩s de lo anterior, es bastante dif铆cil de usar anidado en la Vista de cada uno. Con el tiempo, este problema se resolvi贸 con la ayuda de
Regions en
Marionette.js , pero antes de eso, los desarrolladores tuvieron que inventar sus propios trucos para resolver este problema bastante simple y que a menudo surge.
Y el 煤ltimo. Las interfaces dise帽adas de esta manera estaban predispuestas a datos no sincronizados, ya que todos los modelos exist铆an aislados a nivel de diferentes presentadores, luego, al cambiar los datos en una parte de la interfaz, generalmente no se actualizaban en otra.
Pero, a pesar de estos problemas, este enfoque fue m谩s que viable, y el desarrollo mencionado anteriormente de Backbone en forma de
Marionette todav铆a se puede aplicar con 茅xito para el desarrollo de SPA.
Reaccionar y vac铆o
Es dif铆cil de creer, pero en el momento de su lanzamiento inicial,
React.js caus贸 mucho escepticismo entre la comunidad de desarrolladores. Este escepticismo fue tan grande que durante mucho tiempo se public贸 el siguiente texto en el sitio web oficial:
Dale cinco minutos
Reaccionar desaf铆a mucha sabidur铆a convencional, y a primera vista algunas de las ideas pueden parecer locas.
Y esto a pesar del hecho de que, a diferencia de la mayor铆a de sus competidores y predecesores, React no era un marco completo y era solo una peque帽a biblioteca para facilitar la visualizaci贸n de datos en el DOM:
React es una biblioteca de JavaScript para crear interfaces de usuario de Facebook e Instagram. Muchas personas optan por pensar en React como la V en MVC.
El concepto principal que nos ofrece React es el concepto de un componente, que, de hecho, nos proporciona una nueva forma de implementar View:
class User extends React.Component { handleEdit() {
Reaccionar fue incre铆blemente agradable de usar. Entre sus ventajas innegables se encontraban hasta el d铆a de hoy:
1)
Declaraci贸n y reactividad . Ya no es necesario actualizar manualmente el DOM al cambiar los datos mostrados.
2)
La composici贸n de los componentes . Construir y explorar el 谩rbol de Vista se ha convertido en una acci贸n completamente elemental.
Pero, desafortunadamente, React tiene varios problemas. Uno de los m谩s importantes es el hecho de que React no es un marco completo y, por lo tanto, no nos ofrece ning煤n tipo de arquitectura de aplicaci贸n o herramientas completas para su implementaci贸n.
驴Por qu茅 esto est谩 escrito en defectos? S铆, porque ahora React es la soluci贸n m谩s popular para desarrollar aplicaciones web (
prueba ,
otra prueba y otra prueba ), es un punto de entrada para los nuevos desarrolladores front-end, pero al mismo tiempo no ofrece ni promueve arquitectura, ni enfoques y mejores pr谩cticas para construir aplicaciones completas. Adem谩s, inventa y promueve sus propios enfoques personalizados como
HOC o
Hooks , que no se usan fuera del ecosistema React. Como resultado, cada aplicaci贸n React resuelve problemas t铆picos a su manera, y generalmente no lo hace de la manera m谩s correcta.
Este problema se puede demostrar con la ayuda de uno de los errores m谩s comunes de los desarrolladores de React, que consiste en el abuso de componentes:
Si la 煤nica herramienta que tienes es un martillo, todo comienza a parecerse a un clavo.
Con su ayuda, los desarrolladores resuelven una gama completamente impensable de tareas que van mucho m谩s all谩 del alcance de la visualizaci贸n de datos. En realidad, con la ayuda de componentes, implementan absolutamente todo, desde
consultas de medios desde CSS hasta
enrutamiento .
Reaccionar y Redux
La restauraci贸n del orden en la estructura de las aplicaciones React se vio facilitada en gran medida por la aparici贸n y popularizaci贸n de
Redux . Si React es una vista de MVP, entonces Redux nos ofreci贸 una variaci贸n bastante conveniente de Model.
La idea principal de Redux es la transferencia de datos y la l贸gica de trabajar con ellos en un 煤nico almac茅n de datos centralizado: el denominado Almac茅n. Este enfoque resuelve completamente el problema de la duplicaci贸n y desincronizaci贸n de datos, del que hablamos un poco antes, y tambi茅n ofrece muchas otras comodidades, que, entre otras cosas, incluyen la facilidad de estudiar el estado actual de los datos en la aplicaci贸n.
Otra caracter铆stica igualmente importante es la forma de comunicaci贸n entre la Tienda y otras partes de la aplicaci贸n. En lugar de acceder directamente a la Tienda o sus datos, se nos ofrece usar las llamadas Acciones (objetos simples que describen el evento o comando), que proporcionan un nivel d茅bil de
acoplamiento flojo entre la Tienda y la fuente del evento, lo que aumenta significativamente el grado de mantenimiento del proyecto. Por lo tanto, Redux no solo obliga a los desarrolladores a utilizar enfoques arquitect贸nicos m谩s correctos, sino que tambi茅n le permite aprovechar las diversas ventajas del
abastecimiento de
eventos : ahora en el proceso de depuraci贸n podemos ver f谩cilmente el historial de acciones en la aplicaci贸n, su impacto en los datos y, si es necesario, toda esta informaci贸n puede exportarse , que tambi茅n es extremadamente 煤til al analizar errores de producci贸n.
El esquema general de la aplicaci贸n que usa React / Redux se puede representar de la siguiente manera:

Los componentes de reacci贸n siguen siendo responsables de mostrar los datos. Idealmente, estos componentes deben ser limpios y funcionales, pero si es necesario, pueden tener un estado local y una l贸gica asociada (por ejemplo, para implementar la ocultaci贸n / visualizaci贸n de un elemento espec铆fico o el preprocesamiento b谩sico de una acci贸n del usuario).
Cuando un usuario realiza una acci贸n en la interfaz, el componente simplemente llama a la funci贸n de controlador correspondiente, que recibe del exterior junto con los datos para su visualizaci贸n.
Los llamados componentes de contenedor act煤an como presentadores para nosotros: son los que ejercen el control sobre los componentes de la pantalla y su interacci贸n con los datos. Se crean utilizando la funci贸n de
conexi贸n , que ampl铆a la funcionalidad del componente que se le pasa, agregando una suscripci贸n para cambiar los datos en la Tienda y permiti茅ndonos determinar qu茅 manejadores de datos y eventos se le deben pasar.
Y si todo est谩 claro con los datos aqu铆 (simplemente asignamos datos del almacenamiento a los "accesorios" esperados), entonces me gustar铆a detenerme en los controladores de eventos con m谩s detalle: no solo env铆an Acciones a la Tienda, sino que tambi茅n pueden contener l贸gica adicional para procesar el evento. por ejemplo, incluir ramificaci贸n, realizar redireccionamientos autom谩ticos y realizar cualquier otro trabajo espec铆fico para el presentador.
Otro punto importante con respecto a los componentes del contenedor: debido al hecho de que se crean a trav茅s del HOC, los desarrolladores a menudo describen componentes de visualizaci贸n y componentes del contenedor dentro de un solo m贸dulo y exportan solo el contenedor. Este no es el enfoque correcto, ya que para la posibilidad de probar y reutilizar el componente de visualizaci贸n, debe estar completamente separado del contenedor y preferiblemente extra铆do en un archivo separado.
Bueno, lo 煤ltimo que a煤n no hemos considerado es la Tienda. Nos sirve como una implementaci贸n bastante espec铆fica del Modelo y consta de varios componentes: Estado (un objeto que contiene todos nuestros datos), Middleware (un conjunto de funciones que preprocesan todas las Acciones recibidas), Reductor (una funci贸n que modifica los datos en Estado) y algunos o un controlador de efectos secundarios responsable de ejecutar operaciones asincr贸nicas (acceder a sistemas externos, etc.).
El problema m谩s com煤n aqu铆 es la forma de nuestro Estado. Formalmente, Redux no nos impone ninguna restricci贸n y no da recomendaciones sobre cu谩l deber铆a ser este objeto. Los desarrolladores pueden almacenar absolutamente cualquier informaci贸n en 茅l (incluido el
estado de los formularios y la
informaci贸n del enrutador ), estos datos pueden ser de cualquier tipo (no est谩
prohibido almacenar incluso funciones e instancias de objetos) y tener cualquier nivel de anidamiento. De hecho, esto nuevamente lleva al hecho de que de un proyecto a otro obtenemos un enfoque completamente diferente al uso de State, lo que una vez m谩s causa cierto desconcierto.
Para empezar, aceptamos que no tenemos que mantener absolutamente todos los datos de la aplicaci贸n en el estado; esto se
indica claramente
en la documentaci贸n . Aunque almacenar parte de los datos dentro del estado de los componentes crea ciertos inconvenientes al navegar por el historial de acciones durante el proceso de depuraci贸n (el estado interno de los componentes siempre permanece sin cambios), la transferencia de estos datos al Estado crea a煤n m谩s dificultades: esto aumenta significativamente su tama帽o y requiere la creaci贸n de a煤n m谩s Acciones y reductores.
En cuanto al almacenamiento de cualquier otro dato local en estado, generalmente tratamos con alguna configuraci贸n de interfaz general, que es un conjunto de pares clave-valor. En este caso, podemos hacerlo f谩cilmente con un objeto simple y un reductor para 茅l.
Y si estamos hablando de almacenar datos de fuentes externas, en funci贸n del hecho de que en el desarrollo de interfaces en la gran mayor铆a de los casos estamos tratando con CRUD cl谩sico, entonces para almacenar datos del servidor tiene sentido tratar State como un RDBMS: las claves son el nombre recursos, y detr谩s de ellos hay matrices almacenadas de objetos cargados (
sin anidamiento ) e informaci贸n opcional para ellos (por ejemplo, el n煤mero total de registros en el servidor para crear paginaci贸n). La forma general de estos datos debe ser lo m谩s uniforme posible; esto nos permitir谩 simplificar la creaci贸n de reductores para cada tipo de recurso:
const getModelReducer = modelName => (models = [], action) => { const isModelAction = modelActionTypes.includes(action.type); if (isModelAction && action.modelName === modelName) { switch (action.type) { case 'ADD_MODELS': return collection.add(action.models, models); case 'CHANGE_MODEL': return collection.change(action.model, models); case 'REMOVE_MODEL': return collection.remove(action.model, models); case 'RESET_STATE': return []; } } return models; };
Bueno, otro punto que me gustar铆a discutir en el contexto del uso de Redux es la implementaci贸n de efectos secundarios.
En primer lugar, olv铆date por completo de
Redux Thunk : la transformaci贸n de las acciones propuestas por 茅l en funciones con efectos secundarios, aunque es una soluci贸n funcional, pero combina los conceptos b谩sicos de nuestra arquitectura y reduce sus ventajas a nada.
Redux Saga nos ofrece un enfoque mucho m谩s correcto para implementar efectos secundarios, aunque hay algunas preguntas con respecto a su implementaci贸n t茅cnica.
A continuaci贸n, intente unificar lo m谩s posible sus efectos secundarios que acceden al servidor. Al igual que la forma de estado y los reductores, casi siempre podemos implementar la l贸gica de crear solicitudes al servidor utilizando un 煤nico controlador. Por ejemplo, en el caso de la API RESTful, esto se puede lograr escuchando acciones generalizadas como:
{ type: 'CREATE_MODEL', payload: { model: 'reviews', attributes: { title: '...', text: '...' } } }
... y creando las mismas solicitudes HTTP generalizadas en ellos:
POST /api/reviews { title: '...', text: '...' }
Siguiendo conscientemente todos los consejos anteriores, puede obtener, si no una arquitectura ideal, al menos cerca de ella.
Futuro brillante
El desarrollo moderno de las interfaces web realmente ha dado un importante paso adelante, y ahora estamos viviendo en un momento en que una parte importante de los principales problemas ya se han resuelto de una forma u otra. Pero esto no significa en absoluto que en el futuro no habr谩 nuevas revoluciones.
Si intenta mirar hacia el futuro, lo m谩s probable es que veamos lo siguiente:
1. Enfoque de componentes sin JSXEl concepto de componentes ha demostrado ser extremadamente exitoso y, muy probablemente, veremos su popularizaci贸n a煤n mayor. Pero JSX en s铆 puede y debe morir. S铆, es realmente bastante conveniente de usar, pero, sin embargo, no es un est谩ndar generalmente aceptado ni un c贸digo JS v谩lido. Las bibliotecas para implementar interfaces, sin importar cu谩n buenas sean, no deber铆an inventar nuevos est谩ndares, que luego deben implementarse una y otra vez en cada posible kit de herramientas de desarrollo.
2. Contenedores estatales sin ReduxEl uso de un almac茅n de datos centralizado, propuesto por Redux, tambi茅n fue una soluci贸n extremadamente exitosa, y en el futuro deber铆a convertirse en una especie de est谩ndar en el desarrollo de interfaces, pero su arquitectura interna y su implementaci贸n pueden sufrir ciertos cambios y simplificaciones.
3. Mejora de la intercambiabilidad de la biblioteca.Creo que con el tiempo, la comunidad de desarrolladores front-end se dar谩 cuenta de los beneficios de maximizar la intercambiabilidad de la biblioteca y dejar谩 de encerrarse en sus peque帽os ecosistemas. Todos los componentes de las aplicaciones (enrutadores, contenedores de estado, etc.) deben ser extremadamente universales y su reemplazo no debe requerir una refactorizaci贸n masiva o una reescritura de la aplicaci贸n desde cero.
驴Por qu茅 todo esto?
Si tratamos de generalizar la informaci贸n presentada anteriormente y reducirla a una forma m谩s simple y corta, obtenemos algunos puntos bastante generales:
- Para el desarrollo exitoso de aplicaciones, el conocimiento del lenguaje y el marco no es suficiente, se debe prestar atenci贸n a las cosas te贸ricas generales: arquitectura de aplicaciones, mejores pr谩cticas y patrones de dise帽o.
"La 煤nica constante es el cambio". Los enfoques de labranza y desarrollo continuar谩n cambiando, por lo que los proyectos grandes y de larga duraci贸n deben prestar la debida atenci贸n a la arquitectura; sin ella, la introducci贸n de nuevas herramientas y pr谩cticas ser谩 extremadamente dif铆cil.
Y eso es probablemente todo para m铆. Muchas gracias a todos los que encontraron la fuerza para leer el art铆culo hasta el final. Si tiene alguna pregunta o comentario, lo invito a comentar.