Desarrollo de aplicaciones con marco de desarrollo rápido de aplicaciones AllcountJS

Publicado: 2022-03-11

La idea del desarrollo rápido de aplicaciones (RAD) nació como respuesta a los modelos tradicionales de desarrollo en cascada. Existen muchas variaciones de RAD; por ejemplo, el desarrollo ágil y el proceso unificado de Rational. Sin embargo, todos estos modelos tienen una cosa en común: su objetivo es generar el máximo valor comercial con un tiempo de desarrollo mínimo a través de la creación de prototipos y el desarrollo iterativo. Para lograr esto, el modelo de Desarrollo Rápido de Aplicaciones se basa en herramientas que facilitan el proceso. En este artículo, exploraremos una de esas herramientas y cómo se puede utilizar para centrarse en el valor comercial y la optimización del proceso de desarrollo.

AllcountJS es un marco emergente de código abierto creado teniendo en cuenta el rápido desarrollo de aplicaciones. Se basa en la idea del desarrollo de aplicaciones declarativas utilizando un código de configuración similar a JSON que describe la estructura y el comportamiento de la aplicación. El marco se ha construido sobre Node.js, Express, MongoDB y se basa en gran medida en AngularJS y Twitter Bootstrap. Aunque depende de patrones declarativos, el marco aún permite una mayor personalización a través del acceso directo a la API donde sea necesario.

AllcountJS como su marco RAD

¿Por qué AllcountJS como su marco RAD?

Según Wikipedia, existen al menos cien herramientas que prometen un desarrollo rápido de aplicaciones, pero esto plantea la pregunta: qué tan rápido es “rápido”. ¿Estas herramientas permiten desarrollar una aplicación centrada en datos en particular en unas pocas horas? O, quizás, es “rápido” si la aplicación se puede desarrollar en unos días o unas semanas. Algunas de estas herramientas incluso afirman que solo se necesitan unos minutos para producir una aplicación que funcione. Sin embargo, es poco probable que pueda crear una aplicación útil en menos de cinco minutos y aún afirmar haber satisfecho todas las necesidades comerciales. AllcountJS no pretende ser una herramienta de este tipo; lo que ofrece AllcountJS es una forma de prototipar una idea en un corto período de tiempo.

Con el marco de AllcountJS, es posible crear una aplicación con una interfaz de usuario generada automáticamente con temas, funciones de administración de usuarios, API RESTful y un puñado de otras funciones con un esfuerzo y un tiempo mínimos. Es posible usar AllcountJS para una amplia variedad de casos de uso, pero se adapta mejor a las aplicaciones en las que tiene diferentes colecciones de objetos con diferentes vistas para ellos. Por lo general, las aplicaciones comerciales son una buena opción para este modelo.

AllcountJS se ha utilizado para construir allcountjs.com, además de un rastreador de proyectos para él. Vale la pena señalar que allcountjs.com es una aplicación personalizada de AllcountJS, y que AllcountJS permite combinar vistas estáticas y dinámicas sin problemas. Incluso permite insertar partes cargadas dinámicamente en contenido estático. Por ejemplo, AllcountJS administra una colección de plantillas de aplicaciones de demostración. Hay un widget de demostración en la página principal de allcountjs.com que carga una plantilla de aplicación aleatoria de esa colección. Un puñado de otras aplicaciones de muestra están disponibles en la galería en allcountjs.com.

Empezando

Para demostrar algunas de las capacidades del marco RAD AllcountJS, crearemos una aplicación simple para Toptal, que llamaremos Toptal Community. Si sigue nuestro blog, es posible que ya sepa que se creó una aplicación similar utilizando Hoodie como parte de una de nuestras publicaciones anteriores. Esta aplicación permitirá a los miembros de la comunidad registrarse, crear eventos y postularse para asistir a ellos.

Para configurar el entorno, debe instalar Node.js, MongoDB y Git. Luego, instale AllcountJS CLI invocando un comando "npm install" y realice el inicio del proyecto:

 npm install -g allcountjs-cli allcountjs init toptal-community-allcount cd toptal-community-allcount npm install

La CLI de AllcountJS le pedirá que ingrese información sobre su proyecto para completar el archivo package.json.

AllcountJS se puede utilizar como servidor independiente o como dependencia. En nuestro primer ejemplo, no vamos a extender AllcountJS, por lo que un servidor independiente debería funcionar para nosotros.

Dentro de este directorio app-config recién creado, reemplazaremos el contenido del archivo JavaScript main.js con el siguiente fragmento de código:

 A.app({ appName: "Toptal Community", onlyAuthenticated: true, allowSignUp: true, appIcon: "rocket", menuItems: [{ name: "Events", entityTypeId: "Event", icon: "calendar" }, { name: "My Events", entityTypeId: "MyEvent", icon: "calendar" }], entities: function(Fields) { return { Event: { title: "Events", fields: { eventName: Fields.text("Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required(), appliedUsers: Fields.relation("Applied users", "AppliedUser", "event") }, referenceName: "eventName", sorting: [['date', -1], ['time', -1]], actions: [{ id: "apply", name: "Apply", actionTarget: 'single-item', perform: function (User, Actions, Crud) { return Crud.actionContextCrud().readEntity(Actions.selectedEntityId()).then(function (eventToApply) { var userEventCrud = Crud.crudForEntityType('UserEvent'); return userEventCrud.find({filtering: {"user": User.id, "event": eventToApply.id}}).then(function (events) { if (events.length) { return Actions.modalResult("Can't apply to event", "You've already applied to this event"); } else { return userEventCrud.createEntity({ user: {id: User.id}, event: {id: eventToApply.id}, date: eventToApply.date, time: eventToApply.time }).then(function () { return Actions.navigateToEntityTypeResult("MyEvent") }); } }); }) } }] }, UserEvent: { fields: { user: Fields.fixedReference("User", "OnlyNameUser").required(), event: Fields.fixedReference("Event", "Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required() }, filtering: function (User) { return {"user.id": User.id} }, sorting: [['date', -1], ['time', -1]], views: { MyEvent: { title: "My Events", showInGrid: ['event', 'date', 'time'], permissions: { write: [], delete: null } }, AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } } }, User: { views: { OnlyNameUser: { permissions: { read: null, write: ['admin'] } }, fields: { username: Fields.text("User name") } } } } } });

Aunque AllcountJS funciona con repositorios Git, por simplicidad no lo usaremos en este tutorial. Para ejecutar la aplicación Toptal Community, todo lo que tenemos que hacer es invocar el comando de ejecución AllcountJS CLI en el directorio toptal-community-allcount.

 allcountjs run

Vale la pena señalar que MongoDB debería estar ejecutándose cuando se ejecuta este comando. Si todo va bien, la aplicación debería estar funcionando en http://localhost:9080.

Para iniciar sesión, utilice el nombre de usuario "admin" y la contraseña "admin".

Menos de 100 líneas

Es posible que haya notado que la aplicación definida en main.js tomó solo 91 líneas de código. Estas líneas incluyen la declaración de todos los comportamientos que puede observar cuando navega a http://localhost:9080. Entonces, ¿qué está pasando exactamente debajo del capó? Echemos un vistazo más de cerca a cada aspecto de la aplicación y veamos cómo se relaciona el código con ellos.

Iniciar sesión, crear cuenta

La primera página que ve después de abrir la aplicación es un inicio de sesión. Esto funciona como una página de registro, suponiendo que la casilla de verificación, etiquetada como "Registrarse", esté marcada antes de enviar el formulario.

Iniciar sesión, crear cuenta

Esta página se muestra porque el archivo main.js declara que solo los usuarios autenticados pueden usar esta aplicación. Además, permite que los usuarios se registren desde esta página. Las siguientes dos líneas son todo lo que fue necesario para esto:

 A.app({ ..., onlyAuthenticated: true, allowSignUp: true, ... })

Pagina de bienvenida

Después de iniciar sesión, será redirigido a una página de bienvenida con un menú de aplicaciones. Esta parte de la aplicación se genera automáticamente, en función de los elementos del menú definidos en la tecla "menuItems".

ejemplo de pagina de bienvenida

Junto con un par de otras configuraciones relevantes, el menú se define en el archivo main.js de la siguiente manera:

 A.app({ ..., appName: "Toptal Community", appIcon: "rocket", menuItems: [{ name: "Events", entityTypeId: "Event", icon: "calendar" }, { name: "My Events", entityTypeId: "MyEvent", icon: "calendar" }], ... });

AllcountJS usa íconos de Font Awesome, por lo que todos los nombres de íconos a los que se hace referencia en la configuración se asignan a nombres de íconos de Font Awesome.

Navegación y edición de eventos

Después de hacer clic en "Eventos" en el menú, accederá a la vista de Eventos que se muestra en la captura de pantalla a continuación. Es una vista estándar de AllcountJS que proporciona algunas funcionalidades CRUD genéricas en las entidades correspondientes. Aquí, puede buscar eventos, crear nuevos eventos y editar o eliminar los existentes. Hay dos modos de esta interfaz CRUD: lista y formulario. Esta parte de la aplicación se configura a través de las siguientes pocas líneas de código JavaScript.

 A.app({ ..., entities: function(Fields) { return { Event: { title: "Events", fields: { eventName: Fields.text("Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required(), appliedUsers: Fields.relation("Applied users", "AppliedUser", "event") }, referenceName: "eventName", sorting: [['date', -1], ['time', -1]], ... } } } });

Este ejemplo muestra cómo se configuran las descripciones de entidades en AllcountJS. Observe cómo estamos usando una función para definir las entidades; cada propiedad de la configuración de AllcountJS puede ser una función. Estas funciones pueden solicitar que las dependencias se resuelvan a través de sus nombres de argumento. Antes de llamar a la función, se inyectan las dependencias apropiadas. Aquí, "Campos" es una de las API de configuración de AllcountJS que se utiliza para describir campos de entidad. La propiedad “Entidades” contiene pares nombre-valor donde el nombre es un identificador de tipo de entidad y el valor es su descripción. En este ejemplo, se describe un tipo de entidad para eventos, donde el título es "Eventos". Aquí también se pueden definir otras configuraciones, como el orden de clasificación predeterminado, el nombre de referencia y similares. El orden de clasificación predeterminado se define a través de una matriz de nombres de campo y direcciones, mientras que el nombre de referencia se define a través de una cadena (lea más aquí).

función allcountJS

Este tipo de entidad en particular se ha definido con cuatro campos: "eventName", "date", "time" y "appliedUsers", los tres primeros de los cuales se conservan en la base de datos. Estos campos son obligatorios, como lo indica el uso de "requerido()". Los valores en estos campos con tales reglas se validan antes de enviar el formulario en el front-end, como se muestra en la captura de pantalla a continuación. AllcountJS combina validaciones del lado del cliente y del lado del servidor para brindar la mejor experiencia de usuario. El cuarto campo es una relación que contiene una lista de usuarios que han solicitado asistir al evento. Naturalmente, este campo no se conserva en la base de datos y se completa seleccionando solo las entidades AppliedUser relevantes para el evento.

reglas de desarrollo allcountjs

Solicitud para asistir a eventos

Cuando un usuario selecciona un evento en particular, la barra de herramientas muestra un botón con la etiqueta "Aplicar". Al hacer clic en él, se agrega el evento a la agenda del usuario. En AllcountJS, se pueden configurar acciones similares a esta simplemente declarándolas en la configuración:

 actions: [{ id: "apply", name: "Apply", actionTarget: 'single-item', perform: function (User, Actions, Crud) { return Crud.actionContextCrud().readEntity(Actions.selectedEntityId()).then(function (eventToApply) { var userEventCrud = Crud.crudForEntityType('UserEvent'); return userEventCrud.find({filtering: {"user": User.id, "event": eventToApply.id}}).then(function (events) { if (events.length) { return Actions.modalResult("Can't apply to event", "You've already applied to this event"); } else { return userEventCrud.createEntity({ user: {id: User.id}, event: {id: eventToApply.id}, date: eventToApply.date, time: eventToApply.time }).then(function () { return Actions.navigateToEntityTypeResult("MyEvent") }); } }); }) } }]

La propiedad "acciones" de cualquier tipo de entidad toma una matriz de objetos que describen el comportamiento de cada acción personalizada. Cada objeto tiene una propiedad "id" que define un identificador único para la acción, la propiedad "nombre" define el nombre para mostrar y la propiedad "actionTarget" se usa para definir el contexto de la acción. Establecer "actionTarget" en "single-item" indica que la acción debe realizarse con un evento en particular. Una función definida bajo la propiedad “realizar” es la lógica que se ejecuta cuando se realiza esta acción, normalmente cuando el usuario hace clic en el botón correspondiente.

Esta función puede solicitar dependencias. Por ejemplo, en este ejemplo, la función depende de "Usuario", "Acciones" y "Crud". Cuando ocurre una acción, se puede obtener una referencia al usuario, invocando esta acción, requiriendo la dependencia "Usuario". Aquí también se solicita la dependencia "Crud", que permite la manipulación del estado de la base de datos para estas entidades. Los dos métodos que devuelven una instancia del objeto Crud son: El método “actionContextCrud()” - devuelve CRUD para el tipo de entidad “Evento” ya que la acción “Aplicar” le pertenece, mientras que el método “crudForEntityType()” - devuelve CRUD para cualquier tipo de entidad identificado por su ID de tipo.

Dependencias CRUD

La implementación de la acción comienza comprobando si este evento ya está programado para el usuario, y si no, crea uno. Si ya está programado, se muestra un cuadro de diálogo devolviendo el valor de llamar a "Actions.modalResult()". Además de mostrar un modal, una acción puede realizar diferentes tipos de operaciones de manera similar, como "navegar para ver", "actualizar vista", "mostrar diálogo", etc.

implementación de la acción

Horario de usuario de eventos aplicados

Después de aplicar con éxito a un evento, el navegador se redirige a la vista "Mis eventos", que muestra una lista de eventos a los que el usuario se ha aplicado. La vista está definida por la siguiente configuración:

 UserEvent: { fields: { user: Fields.fixedReference("User", "OnlyNameUser").required(), event: Fields.fixedReference("Event", "Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required() }, filtering: function (User) { return {"user.id": User.id} }, sorting: [['date', -1], ['time', -1]], views: { MyEvent: { title: "My Events", showInGrid: ['event', 'date', 'time'], permissions: { write: [], delete: null } }, AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } } },

En este caso, estamos usando una nueva propiedad de configuración, "filtrado". Al igual que con nuestro ejemplo anterior, esta función también se basa en la dependencia "Usuario". Si la función devuelve un objeto, se trata como una consulta MongoDB; la consulta filtra la colección de eventos que pertenecen solo al usuario actual.

Otra propiedad interesante es "Vistas". "Ver" es un tipo de entidad regular, pero su colección MongoDB es la misma que para el tipo de entidad principal. Esto hace posible crear vistas visualmente diferentes para los mismos datos en la base de datos. De hecho, usamos esta función para crear dos vistas diferentes para "UserEvent:" "MyEvent" y "AppliedUser". Dado que el prototipo de las subvistas se establece en el tipo de entidad principal, las propiedades que no se anulan se "heredan" del tipo principal.

puntos de vista

Listado de asistentes al evento

Después de aplicar a un evento, otros usuarios pueden ver una lista de todos los usuarios que planean asistir. Esto se genera como resultado de los siguientes elementos de configuración en main.js:

 AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } // ... appliedUsers: Fields.relation("Applied users", "AppliedUser", "event")

"AppliedUser" es una vista de solo lectura para un tipo de entidad "MyEvent". Este permiso de solo lectura se aplica configurando una matriz vacía en la propiedad "Escritura" del objeto de permisos. Además, como el permiso de "Lectura" no está definido, por defecto, la lectura está permitida para todos los usuarios.

usuario aplicado para mi tipo de evento

Ampliación de implementaciones predeterminadas

El inconveniente típico de los marcos RAD es la falta de flexibilidad. Una vez que haya creado su aplicación y necesite personalizarla, es posible que encuentre obstáculos importantes. AllcountJS se desarrolló teniendo en cuenta la extensibilidad y permite el reemplazo de cada bloque de construcción interno.

Para lograrlo, AllcountJS utiliza su propia implementación de inyección de dependencia (DI). DI permite al desarrollador anular los comportamientos predeterminados del marco a través de puntos de extensión y, al mismo tiempo, lo permite mediante la reutilización de implementaciones existentes. Muchos aspectos de la extensión del marco RAD se describen en la documentación. En esta sección, exploraremos cómo podemos extender dos de los muchos componentes en el marco, la lógica del lado del servidor y las vistas.

Continuando con nuestro ejemplo de la Comunidad Toptal, integremos una fuente de datos externa para agregar datos de eventos. Imaginemos que hay publicaciones en el blog de Toptal que discuten planes para eventos el día anterior a cada evento. Con Node.js, debería ser posible analizar la fuente RSS del blog y extraer dichos datos. Para hacer esto, necesitaremos algunas dependencias adicionales de npm, como "request", "xml2js" (para cargar el feed RSS del blog de Toptal), "q" (para implementar promesas) y "moment" (para analizar fechas). Estas dependencias se pueden instalar invocando el siguiente conjunto de comandos:

 npm install xml2js npm install request npm install q npm install moment

Vamos a crear otro archivo JavaScript, asígnele el nombre "toptal-community.js" en el directorio toptal-community-allcount y complételo con lo siguiente:

 var request = require('request'); var Q = require('q'); var xml2js = require('xml2js'); var moment = require('moment'); var injection = require('allcountjs'); injection.bindFactory('port', 9080); injection.bindFactory('dbUrl', 'mongodb://localhost:27017/toptal-community'); injection.bindFactory('gitRepoUrl', 'app-config'); injection.bindFactory('DiscussionEventsImport', function (Crud) { return { importEvents: function () { return Q.nfcall(request, "https://www.toptal.com/blog.rss").then(function (responseAndBody) { var body = responseAndBody[1]; return Q.nfcall(xml2js.parseString, body).then (function (feed) { var events = feed.rss.channel[0].item.map(function (item) { return { eventName: "Discussion of " + item.title, date: moment(item.pubDate, "DD MMM YYYY").add(1, 'day').toDate(), time: "12:00" }}); var crud = Crud.crudForEntityType('Event'); return Q.all(events.map(function (event) { return crud.find({query: {eventName: event.eventName}}).then(function (createdEvent) { if (!createdEvent[0]) { return crud.createEntity(event); } }); } )); }); }) } }; }); var server = injection.inject('allcountServerStartup'); server.startup(function (errors) { if (errors) { throw new Error(errors.join('\n')); } });

En este archivo, estamos definiendo una dependencia llamada "DiscussionEventsImport", que podemos usar en nuestro archivo main.js agregando una acción de importación en el tipo de entidad "Evento".

 { id: "import-blog-events", name: "Import Blog Events", actionTarget: "all-items", perform: function (DiscussionEventsImport, Actions) { return DiscussionEventsImport.importEvents().then(function () { return Actions.refreshResult() }); } }

Dado que es importante reiniciar el servidor después de realizar algunos cambios en los archivos de JavaScript, puede eliminar la instancia anterior y volver a iniciarla ejecutando el mismo comando que antes:

 node toptal-community.js

Si todo va bien, verá algo como la captura de pantalla a continuación después de ejecutar la acción "Importar eventos de blog".

Acción Importar eventos de blog

Hasta aquí todo bien, pero no nos detengamos aquí. Las vistas predeterminadas funcionan, pero a veces pueden ser aburridas. Vamos a personalizarlos un poco.

¿Te gustan las cartas? ¡A todos les gustan las cartas! Para hacer una vista de tarjeta, coloque lo siguiente en un archivo llamado events.jade dentro del directorio app-config:

 extends main include mixins block vars - var hasToolbar = true block content .refresh-form-controller(ng-app='allcount', ng-controller='EntityViewController') +defaultToolbar() .container.screen-container(ng-cloak) +defaultList() .row: .col-lg-4.col-md-6.col-xs-12(ng-repeat="item in items") .panel.panel-default .panel-heading h3 {{item.date | date}} {{item.time}} div button.btn.btn-default.btn-xs(ng-if="!isInEditMode", lc-tooltip="View", ng-click="navigate(item.id)"): i.glyphicon.glyphicon-chevron-right |   button.btn.btn-danger.btn-xs(ng-if="isInEditMode", lc-tooltip="Delete", ng-click="deleteEntity(item)"): i.glyphicon.glyphicon-trash .panel-body h3 {{item.eventName}} +noEntries() +defaultEditAndCreateForms() block js +entityJs()

Después de eso, simplemente consúltelo desde la entidad "Evento" en main.js como "vista personalizada: "eventos"". Ejecute su aplicación y debería ver una interfaz basada en tarjeta en lugar de la tabular predeterminada.

Entidad de evento en main.js

Conclusión

Hoy en día, el flujo de desarrollo de aplicaciones web es similar en muchas tecnologías web, donde algunas operaciones se repiten una y otra vez. ¿Realmente vale la pena? Tal vez, ¿es hora de repensar la forma en que se desarrollan sus aplicaciones web?

AllcountJS proporciona un enfoque alternativo a los marcos de trabajo de desarrollo rápido de aplicaciones; comienza creando un esqueleto para la aplicación definiendo descripciones de entidades y luego agrega vistas y personalizaciones de comportamiento a su alrededor. Como puede ver, con AllcountJS, creamos una aplicación simple pero completamente funcional, en menos de cien líneas de código. Tal vez no cumpla con todos los requisitos de producción, pero es personalizable. Todo esto hace de AllcountJS una buena herramienta para iniciar rápidamente aplicaciones web.