Développement d'applications avec Rapid Application Development Framework AllcountJS
Publié: 2022-03-11L'idée du développement rapide d'applications (RAD) est née en réponse aux modèles traditionnels de développement en cascade. De nombreuses variantes de RAD existent; par exemple, le développement Agile et le Rational Unified Process. Cependant, tous ces modèles ont une chose en commun : ils visent à générer une valeur commerciale maximale avec un temps de développement minimal grâce au prototypage et au développement itératif. Pour ce faire, le modèle de développement rapide d'applications s'appuie sur des outils qui facilitent le processus. Dans cet article, nous allons explorer un tel outil et comment il peut être utilisé pour se concentrer sur la valeur commerciale et l'optimisation du processus de développement.
AllcountJS est un framework open source émergent conçu pour le développement rapide d'applications. Il est basé sur l'idée du développement d'applications déclaratives à l'aide d'un code de configuration de type JSON qui décrit la structure et le comportement de l'application. Le framework a été construit sur Node.js, Express, MongoDB et s'appuie fortement sur AngularJS et Twitter Bootstrap. Bien qu'il repose sur des modèles déclaratifs, le framework permet toujours une personnalisation supplémentaire grâce à un accès direct à l'API si nécessaire.
Pourquoi AllcountJS comme framework RAD ?
Selon Wikipédia, il existe au moins une centaine d'outils qui promettent un développement rapide d'applications, mais cela soulève la question : à quel point est « rapide ». Ces outils permettent-ils de développer une application centrée sur les données en quelques heures ? Ou peut-être est-elle « rapide » si l'application peut être développée en quelques jours ou quelques semaines. Certains de ces outils prétendent même que quelques minutes suffisent pour produire une application fonctionnelle. Cependant, il est peu probable que vous puissiez créer une application utile en moins de cinq minutes tout en prétendant avoir satisfait tous les besoins de l'entreprise. AllcountJS ne prétend pas être un tel outil ; ce que propose AllcountJS est un moyen de prototyper une idée en peu de temps.
Avec le framework AllcountJS, il est possible de créer une application avec une interface utilisateur à thème générée automatiquement, des fonctionnalités de gestion des utilisateurs, une API RESTful et une poignée d'autres fonctionnalités avec un minimum d'effort et de temps. Il est possible d'utiliser AllcountJS pour une grande variété de cas d'utilisation, mais il convient mieux aux applications où vous avez différentes collections d'objets avec différentes vues pour eux. En règle générale, les applications métier conviennent parfaitement à ce modèle.
AllcountJS a été utilisé pour créer allcountjs.com, ainsi qu'un suivi de projet pour celui-ci. Il convient de noter que allcountjs.com est une application AllcountJS personnalisée et qu'AllcountJS permet de combiner des vues statiques et dynamiques avec peu de tracas. Il permet même d'insérer des parties chargées dynamiquement dans du contenu statique. Par exemple, AllcountJS gère une collection de modèles d'applications de démonstration. Il y a un widget de démonstration sur la page principale de allcountjs.com qui charge un modèle d'application aléatoire à partir de cette collection. Une poignée d'autres exemples d'applications sont disponibles dans la galerie sur allcountjs.com.
Commencer
Pour démontrer certaines des capacités du framework RAD AllcountJS, nous allons créer une application simple pour Toptal, que nous appellerons Toptal Community. Si vous suivez notre blog, vous savez peut-être déjà qu'une application similaire a été créée à l'aide de Hoodie dans le cadre de l'un de nos précédents articles de blog. Cette application permettra aux membres de la communauté de s'inscrire, de créer des événements et de postuler pour y assister.
Afin de configurer l'environnement, vous devez installer Node.js, MongoDB et Git. Ensuite, installez AllcountJS CLI en appelant une commande "npm install" et effectuez project init :
npm install -g allcountjs-cli allcountjs init toptal-community-allcount cd toptal-community-allcount npm install
AllcountJS CLI vous demandera de saisir des informations sur votre projet afin de pré-remplir package.json.
AllcountJS peut être utilisé comme serveur autonome ou comme dépendance. Dans notre premier exemple, nous n'allons pas étendre AllcountJS, donc un serveur autonome devrait fonctionner pour nous.
Dans ce répertoire app-config nouvellement créé, nous remplacerons le contenu du fichier JavaScript main.js par l'extrait de code suivant :
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") } } } } } });
Bien que AllcountJS fonctionne avec les référentiels Git, par souci de simplicité, nous ne l'utiliserons pas dans ce tutoriel. Pour exécuter l'application Toptal Community, tout ce que nous avons à faire est d'invoquer la commande d'exécution de la CLI AllcountJS dans le répertoire toptal-community-allcount.
allcountjs run
Il convient de noter que MongoDB doit être en cours d'exécution lorsque cette commande est exécutée. Si tout se passe bien, l'application devrait être opérationnelle à l'adresse http://localhost:9080.
Pour vous connecter, veuillez utiliser le nom d'utilisateur "admin" et le mot de passe "admin".
Moins de 100 lignes
Vous avez peut-être remarqué que l'application définie dans main.js ne prenait que 91 lignes de code. Ces lignes incluent la déclaration de tous les comportements que vous pouvez observer lorsque vous naviguez vers http://localhost:9080. Alors, que se passe-t-il exactement, sous le capot ? Examinons de plus près chaque aspect de l'application et voyons comment le code les concerne.
Vous connecter vous inscrire
La première page que vous voyez après avoir ouvert l'application est une page de connexion. Cela sert également de page d'inscription, en supposant que la case - intitulée "S'inscrire" - est cochée avant de soumettre le formulaire.
Cette page s'affiche car le fichier main.js déclare que seuls les utilisateurs authentifiés peuvent utiliser cette application. De plus, il permet aux utilisateurs de s'inscrire à partir de cette page. Les deux lignes suivantes suffisent pour cela :
A.app({ ..., onlyAuthenticated: true, allowSignUp: true, ... })
Page d'accueil
Après vous être connecté, vous serez redirigé vers une page d'accueil avec un menu d'application. Cette partie de l'application est générée automatiquement, sur la base des éléments de menu définis sous la clé "menuItems".
Avec quelques autres configurations pertinentes, le menu est défini dans le fichier main.js comme suit :
A.app({ ..., appName: "Toptal Community", appIcon: "rocket", menuItems: [{ name: "Events", entityTypeId: "Event", icon: "calendar" }, { name: "My Events", entityTypeId: "MyEvent", icon: "calendar" }], ... });
AllcountJS utilise les icônes Font Awesome, de sorte que tous les noms d'icônes référencés dans la configuration sont mappés aux noms d'icônes Font Awesome.
Parcourir et modifier des événements
Après avoir cliqué sur "Événements" dans le menu, vous serez redirigé vers la vue Événements illustrée dans la capture d'écran ci-dessous. Il s'agit d'une vue AllcountJS standard qui fournit certaines fonctionnalités CRUD génériques sur les entités correspondantes. Ici, vous pouvez rechercher des événements, créer de nouveaux événements et modifier ou supprimer ceux qui existent déjà. Il existe deux modes de cette interface CRUD : liste et formulaire. Cette partie de l'application est configurée via les quelques lignes de code JavaScript suivantes.
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]], ... } } } });
Cet exemple montre comment les descriptions d'entité sont configurées dans AllcountJS. Remarquez comment nous utilisons une fonction pour définir les entités ; chaque propriété de la configuration AllcountJS peut être une fonction. Ces fonctions peuvent demander que les dépendances soient résolues via ses noms d'arguments. Avant que la fonction ne soit appelée, les dépendances appropriées sont injectées. Ici, "Fields" est l'une des API de configuration AllcountJS utilisées pour décrire les champs d'entité. La propriété "Entities" contient des paires nom-valeur où le nom est un identifiant de type d'entité et la valeur est sa description. Un type d'entité pour les événements est décrit, dans cet exemple, où le titre est "Événements". D'autres configurations, telles que l'ordre de tri par défaut, le nom de référence, etc., peuvent également être définies ici. L'ordre de tri par défaut est défini par un tableau de noms de champs et de directions, tandis que le nom de référence est défini par une chaîne (en savoir plus ici).
Ce type d'entité particulier a été défini comme ayant quatre champs : "eventName", "date", "time" et "appliedUsers", dont les trois premiers sont conservés dans la base de données. Ces champs sont obligatoires, comme indiqué par l'utilisation de « required() ». Les valeurs dans ces champs avec de telles règles sont validées avant que le formulaire ne soit soumis sur le front-end, comme indiqué dans la capture d'écran ci-dessous. AllcountJS combine les validations côté client et côté serveur pour offrir la meilleure expérience utilisateur. Le quatrième champ est une relation qui contient une liste d'utilisateurs qui ont postulé pour assister à l'événement. Naturellement, ce champ n'est pas conservé dans la base de données et est rempli en sélectionnant uniquement les entités AppliedUser pertinentes pour l'événement.
Candidature pour assister à des événements
Lorsqu'un utilisateur sélectionne un événement particulier, la barre d'outils affiche un bouton intitulé "Appliquer". Cliquer dessus ajoute l'événement au calendrier de l'utilisateur. Dans AllcountJS, des actions similaires à celle-ci peuvent être configurées en les déclarant simplement dans la configuration :

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 propriété "actions" de tout type d'entité prend un tableau d'objets qui décrivent le comportement de chaque action personnalisée. Chaque objet a une propriété "id" qui définit un identifiant unique pour l'action, la propriété "name" définit le nom d'affichage et la propriété "actionTarget" est utilisée pour définir le contexte de l'action. Définir "actionTarget" sur "single-item" indique que l'action doit être effectuée avec un événement particulier. Une fonction définie sous la propriété « perform » est la logique exécutée lorsque cette action est effectuée, typiquement lorsque l'utilisateur clique sur le bouton correspondant.
Des dépendances peuvent être demandées par cette fonction. Par exemple, dans cet exemple, la fonction dépend de "Utilisateur", "Actions" et "Crud". Lorsqu'une action se produit, une référence à l'utilisateur, invoquant cette action, peut être obtenue en exigeant la dépendance "Utilisateur". La dépendance "Crud", qui permet la manipulation de l'état de la base de données pour ces entités, est également demandée ici. Les deux méthodes qui renvoient une instance de l'objet Crud sont : La méthode "actionContextCrud()" - renvoie CRUD pour le type d'entité "Event" puisque l'action "Apply" lui appartient, tandis que la méthode "crudForEntityType()" - renvoie CRUD pour tout type d'entité identifié par son ID de type.
La mise en place de l'action commence par vérifier si cet événement est déjà programmé pour l'utilisateur, et si ce n'est pas le cas, il en crée un. S'il est déjà planifié, une boîte de dialogue s'affiche en renvoyant la valeur de l'appel à "Actions.modalResult()". En plus d'afficher un modal, une action peut effectuer différents types d'opérations de la même manière, telles que "naviguer vers la vue", "actualiser la vue", "afficher la boîte de dialogue", etc.
Calendrier utilisateur des événements appliqués
Après avoir postulé avec succès à un événement, le navigateur est redirigé vers la vue "Mes événements", qui affiche une liste des événements auxquels l'utilisateur a postulé. La vue est définie par la configuration suivante :
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'] } } },
Dans ce cas, nous utilisons une nouvelle propriété de configuration, "filtrage". Comme dans notre exemple précédent, cette fonction s'appuie également sur la dépendance "Utilisateur". Si la fonction renvoie un objet, il est traité comme une requête MongoDB ; la requête filtre la collection pour les événements qui appartiennent uniquement à l'utilisateur actuel.
Une autre propriété intéressante est "Vues". "View" est un type d'entité normal, mais sa collection MongoDB est la même que pour le type d'entité parent. Cela permet de créer des vues visuellement différentes pour les mêmes données dans la base de données. En fait, nous avons utilisé cette fonctionnalité pour créer deux vues différentes pour "UserEvent :" "MyEvent" et "AppliedUser". Étant donné que le prototype des sous-vues est défini sur le type d'entité parent, les propriétés qui ne sont pas remplacées sont « héritées » du type parent.
Liste des participants à l'événement
Après avoir postulé à un événement, d'autres utilisateurs peuvent voir une liste de tous les utilisateurs qui prévoient d'y assister. Ceci est généré à la suite des éléments de configuration suivants dans main.js :
AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } // ... appliedUsers: Fields.relation("Applied users", "AppliedUser", "event")
"AppliedUser" est une vue en lecture seule pour un type d'entité "MyEvent". Cette autorisation en lecture seule est appliquée en définissant un tableau vide sur la propriété "Write" de l'objet d'autorisations. De plus, comme l'autorisation "Lire" n'est pas définie, par défaut, la lecture est autorisée pour tous les utilisateurs.
Extension des implémentations par défaut
L'inconvénient typique des frameworks RAD est le manque de flexibilité. Une fois que vous avez créé votre application et que vous devez la personnaliser, vous pouvez rencontrer des obstacles importants. AllcountJS est développé dans un souci d'extensibilité et permet le remplacement de chaque bloc de construction à l'intérieur.
Pour y parvenir, AllcountJS utilise sa propre implémentation de Dependency Injection (DI). DI permet au développeur de remplacer les comportements par défaut du framework via des points d'extension et, en même temps, lui permet de réutiliser les implémentations existantes. De nombreux aspects de l'extension du framework RAD sont décrits dans les documentations. Dans cette section, nous allons explorer comment nous pouvons étendre deux des nombreux composants du framework, la logique côté serveur et les vues.
Poursuivant avec notre exemple Toptal Community, intégrons une source de données externe pour agréger les données d'événement. Imaginons qu'il y ait des articles de Toptal Blog discutant des plans d'événements la veille de chaque événement. Avec Node.js, il devrait être possible d'analyser le flux RSS du blog et d'extraire ces données. Pour ce faire, nous aurons besoin de quelques dépendances npm supplémentaires, telles que "request", "xml2js" (pour charger le flux RSS du blog Toptal), "q" (pour implémenter les promesses) et "moment" (pour analyser les dates). Ces dépendances peuvent être installées en appelant l'ensemble de commandes suivant :
npm install xml2js npm install request npm install q npm install moment
Créons un autre fichier JavaScript, nommons-le "toptal-community.js" dans le répertoire toptal-community-allcount et remplissons-le avec ce qui suit :
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')); } });
Dans ce fichier, nous définissons une dépendance appelée "DiscussionEventsImport", que nous pouvons utiliser dans notre fichier main.js en ajoutant une action d'importation sur le type d'entité "Event".
{ id: "import-blog-events", name: "Import Blog Events", actionTarget: "all-items", perform: function (DiscussionEventsImport, Actions) { return DiscussionEventsImport.importEvents().then(function () { return Actions.refreshResult() }); } }
Puisqu'il est important de redémarrer le serveur après avoir apporté quelques modifications aux fichiers JavaScript, vous pouvez tuer l'instance précédente et la redémarrer en exécutant la même commande qu'auparavant :
node toptal-community.js
Si tout se passe bien, vous verrez quelque chose comme la capture d'écran ci-dessous après avoir exécuté l'action "Importer des événements de blog".
Jusqu'ici tout va bien, mais ne nous arrêtons pas là. Les vues par défaut fonctionnent, mais elles peuvent parfois être ennuyeuses. Personnalisons-les un peu.
Vous aimez les cartes ? Tout le monde aime les cartes ! Pour créer une vue de carte, placez ce qui suit dans un fichier nommé events.jade dans le répertoire 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()
Après cela, référencez-le simplement à partir de l'entité "Event" dans main.js en tant que "customView: "events"". Exécutez votre application et vous devriez voir une interface basée sur des cartes au lieu de l'interface tabulaire par défaut.
Conclusion
De nos jours, le flux de développement d'applications Web est similaire dans de nombreuses technologies Web, où certaines opérations sont répétées encore et encore. ça en vaut vraiment la peine? Peut-être est-il temps de repenser la manière dont vos applications web sont développées ?
AllcountJS fournit une approche alternative aux cadres de développement d'applications rapides ; vous commencez par créer un squelette pour l'application en définissant des descriptions d'entité, puis ajoutez des vues et des personnalisations de comportement autour de celui-ci. Comme vous pouvez le voir, avec AllcountJS, nous avons créé une application simple mais entièrement fonctionnelle, en moins d'une centaine de lignes de code. Peut-être qu'il ne répond pas à toutes les exigences de production, mais il est personnalisable. Tout cela fait d'AllcountJS un bon outil pour démarrer rapidement les applications Web.