Dezvoltare de aplicații cu cadrul de dezvoltare rapidă a aplicațiilor AllcountJS
Publicat: 2022-03-11Ideea de dezvoltare rapidă a aplicațiilor (RAD) a luat naștere ca răspuns la modelele tradiționale de dezvoltare în cascadă. Există multe variante ale RAD; de exemplu, dezvoltarea Agile și Procesul Rațional Unificat. Cu toate acestea, toate astfel de modele au un lucru în comun: ele urmăresc să ofere valoare maximă de afaceri cu un timp minim de dezvoltare prin prototipare și dezvoltare iterativă. Pentru a realiza acest lucru, modelul de dezvoltare rapidă a aplicațiilor se bazează pe instrumente care ușurează procesul. În acest articol, vom explora un astfel de instrument și cum poate fi folosit pentru a se concentra pe valoarea afacerii și optimizarea procesului de dezvoltare.
AllcountJS este un cadru open source emergent construit având în vedere dezvoltarea rapidă a aplicațiilor. Se bazează pe ideea dezvoltării aplicației declarative folosind codul de configurare asemănător JSON care descrie structura și comportamentul aplicației. Cadrul a fost construit pe Node.js, Express, MongoDB și se bazează în mare măsură pe AngularJS și Twitter Bootstrap. Deși se bazează pe modele declarative, cadrul permite totuși personalizare suplimentară prin acces direct la API acolo unde este necesar.
De ce AllcountJS ca cadru RAD?
Potrivit Wikipedia, există cel puțin o sută de instrumente care promit o dezvoltare rapidă a aplicațiilor, dar acest lucru ridică întrebarea: cât de rapid este „rapid”. Aceste instrumente permit dezvoltarea unei anumite aplicații centrate pe date în câteva ore? Sau, poate, este „rapid” dacă aplicația poate fi dezvoltată în câteva zile sau câteva săptămâni. Unele dintre aceste instrumente susțin chiar că doar câteva minute sunt necesare pentru a produce o aplicație funcțională. Cu toate acestea, este puțin probabil să creați o aplicație utilă în mai puțin de cinci minute și să pretindeți că ați satisfăcut orice nevoie de afaceri. AllcountJS nu pretinde a fi un astfel de instrument; ceea ce oferă AllcountJS este o modalitate de a prototip o idee într-o perioadă scurtă de timp.
Cu framework-ul AllcountJS, este posibil să construiți o aplicație cu o interfață de utilizator tematică generată automat, funcții de gestionare a utilizatorilor, API RESTful și o mână de alte caracteristici cu efort și timp minim. Este posibil să utilizați AllcountJS pentru o mare varietate de cazuri de utilizare, dar se potrivește cel mai bine aplicațiilor în care aveți colecții diferite de obiecte cu vederi diferite pentru acestea. De obicei, aplicațiile de afaceri sunt potrivite pentru acest model.
AllcountJS a fost folosit pentru a construi allcountjs.com, plus un monitor de proiect pentru acesta. Merită remarcat faptul că allcountjs.com este o aplicație personalizată AllcountJS și că AllcountJS permite combinarea atât a vizualizărilor statice, cât și a celor dinamice, fără probleme. Permite chiar și piesele încărcate dinamic să fie introduse în conținut static. De exemplu, AllcountJS gestionează o colecție de șabloane de aplicații demonstrative. Există un widget demonstrativ pe pagina principală a allcountjs.com care încarcă un șablon de aplicație aleatoriu din acea colecție. O mână de alte exemple de aplicații sunt disponibile în galeria de la allcountjs.com.
Noțiuni de bază
Pentru a demonstra unele dintre capacitățile cadrului RAD AllcountJS, vom crea o aplicație simplă pentru Toptal, pe care o vom numi Comunitatea Toptal. Dacă urmăriți blogul nostru, este posibil să știți deja că o aplicație similară a fost creată folosind Hoodie ca parte a uneia dintre postările noastre anterioare de pe blog. Această aplicație va permite membrilor comunității să se înscrie, să creeze evenimente și să aplice pentru a participa la ele.
Pentru a configura mediul, ar trebui să instalați Node.js, MongoDB și Git. Apoi, instalați AllcountJS CLI invocând o comandă „npm install” și efectuați proiectul init:
npm install -g allcountjs-cli allcountjs init toptal-community-allcount cd toptal-community-allcount npm install
AllcountJS CLI vă va cere să introduceți câteva informații despre proiectul dvs. pentru a pre-completa package.json.
AllcountJS poate fi folosit ca server autonom sau ca dependență. În primul nostru exemplu, nu vom extinde AllcountJS, așa că un server independent ar trebui să funcționeze pentru noi.
În acest director nou creat de configurare a aplicației, vom înlocui conținutul fișierului JavaScript main.js cu următorul fragment de cod:
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") } } } } } });
Deși AllcountJS funcționează cu depozite Git, de dragul simplității nu îl vom folosi în acest tutorial. Pentru a rula aplicația Toptal Community, tot ce trebuie să facem este să invocăm comanda AllcountJS CLI run din directorul toptal-community-allcount.
allcountjs run
Este de remarcat faptul că MongoDB ar trebui să ruleze atunci când această comandă este executată. Dacă totul merge bine, aplicația ar trebui să funcționeze la http://localhost:9080.
Pentru a vă autentifica vă rugăm să utilizați numele de utilizator „admin” și parola „admin”.
Mai puțin de 100 de linii
Poate ați observat că aplicația definită în main.js a luat doar 91 de linii de cod. Aceste linii includ declararea tuturor comportamentelor pe care le puteți observa când navigați la http://localhost:9080. Deci, ce se întâmplă exact, sub capotă? Să aruncăm o privire mai atentă la fiecare aspect al aplicației și să vedem cum se leagă codul cu ele.
Conecteaza-ta inscrie-te
Prima pagină pe care o vedeți după deschiderea aplicației este o conectare. Aceasta se dublează ca o pagină de înscriere, presupunând că caseta de selectare - etichetată „Înscrieți-vă” - este bifată înainte de a trimite formularul.
Această pagină este afișată deoarece fișierul main.js declară că numai utilizatorii autentificați pot folosi această aplicație. În plus, permite utilizatorilor posibilitatea de a se înscrie de pe această pagină. Următoarele două rânduri sunt tot ceea ce a fost necesar pentru aceasta:
A.app({ ..., onlyAuthenticated: true, allowSignUp: true, ... })
Pagina de bun venit
După conectare, veți fi redirecționat către o pagină de bun venit cu un meniu pentru aplicații. Această porțiune a aplicației este generată automat, pe baza elementelor de meniu definite sub tasta „menuItems”.
Împreună cu alte câteva configurații relevante, meniul este definit în fișierul main.js după cum urmează:
A.app({ ..., appName: "Toptal Community", appIcon: "rocket", menuItems: [{ name: "Events", entityTypeId: "Event", icon: "calendar" }, { name: "My Events", entityTypeId: "MyEvent", icon: "calendar" }], ... });
AllcountJS utilizează pictograme Font Awesome, astfel încât toate numele pictogramelor la care se face referire în configurație sunt mapate cu numele pictogramelor Font Awesome.
Navigarea și editarea evenimentelor
După ce faceți clic pe „Evenimente” din meniu, veți fi dus la vizualizarea Evenimente afișată în captura de ecran de mai jos. Este o vizualizare standard AllcountJS care oferă unele funcționalități CRUD generice pentru entitățile corespunzătoare. Aici, puteți căuta evenimente, puteți crea evenimente noi și puteți edita sau șterge cele existente. Există două moduri ale acestei interfețe CRUD: listă și formular. Această porțiune a aplicației este configurată prin următoarele câteva rânduri de cod 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]], ... } } } });
Acest exemplu arată cum sunt configurate descrierile entităților în AllcountJS. Observați cum folosim o funcție pentru a defini entitățile; fiecare proprietate a configurației AllcountJS poate fi o funcție. Aceste funcții pot solicita ca dependențe să fie rezolvate prin numele argumentelor sale. Înainte ca funcția să fie apelată, sunt injectate dependențe adecvate. Aici, „Fields” este unul dintre API-urile de configurare AllcountJS utilizate pentru a descrie câmpurile de entitate. Proprietatea „Entități” conține perechi nume-valoare în care numele este un identificator de tip de entitate și valoarea este descrierea acestuia. Un tip de entitate pentru evenimente este descris, în acest exemplu, unde titlul este „Evenimente”. Alte configurații, cum ar fi ordonarea implicită, numele de referință și altele asemenea, pot fi, de asemenea, definite aici. Ordinea de sortare implicită este definită printr-o serie de nume de câmpuri și direcții, în timp ce numele de referință este definit printr-un șir (citiți mai multe aici).
Acest tip de entitate special a fost definit ca având patru câmpuri: „eventName”, „date”, „time” și „appliedUsers”, primele trei dintre ele fiind menținute în baza de date. Aceste câmpuri sunt obligatorii, așa cum este indicat de utilizarea „required().” Valorile din aceste câmpuri cu astfel de reguli sunt validate înainte ca formularul să fie trimis pe front-end, așa cum se arată în captura de ecran de mai jos. AllcountJS combină atât validările pe partea client, cât și pe partea serverului pentru a oferi cea mai bună experiență de utilizare. Al patrulea câmp este o relație care poartă o listă de utilizatori care au aplicat pentru a participa la eveniment. Desigur, acest câmp nu este menținut în baza de date și este populat selectând doar acele entități AppliedUser relevante pentru eveniment.

Aplicarea pentru a participa la evenimente
Când un utilizator selectează un anumit eveniment, bara de instrumente afișează un buton etichetat „Aplicați”. Făcând clic pe el, evenimentul se adaugă în programul utilizatorului. În AllcountJS, acțiuni similare cu aceasta pot fi configurate pur și simplu declarându-le în configurație:
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") }); } }); }) } }]
Proprietatea „acțiuni” a oricărui tip de entitate preia o serie de obiecte care descriu comportamentul fiecărei acțiuni personalizate. Fiecare obiect are o proprietate „id” care definește un identificator unic pentru acțiune, proprietatea „name” definește numele afișat și proprietatea „actionTarget” este folosită pentru a defini contextul acțiunii. Setarea „actionTarget” la „single-element” indică faptul că acțiunea trebuie efectuată cu un anumit eveniment. O funcție definită sub proprietatea „perform” este logica executată atunci când această acțiune este efectuată, de obicei atunci când utilizatorul face clic pe butonul corespunzător.
Dependențe pot fi solicitate de această funcție. De exemplu, în acest exemplu, funcția depinde de „Utilizator”, „Acțiuni” și „Crud”. Când are loc o acțiune, o referință la utilizator, invocând această acțiune, poate fi obținută prin solicitarea dependenței „Utilizator”. Dependența „Crud”, care permite manipularea stării bazei de date pentru aceste entități, este, de asemenea, solicitată aici. Cele două metode care returnează o instanță a obiectului Crud sunt: Metoda „actionContextCrud()” - returnează CRUD pentru tipul de entitate „Eveniment”, deoarece acțiunea „Aplicare” îi aparține, în timp ce metoda „crudForEntityType()” - returnează CRUD pentru orice tip de entitate identificat prin ID-ul tipului său.
Implementarea acțiunii începe prin a verifica dacă acest eveniment este deja programat pentru utilizator, iar dacă nu, creează unul. Dacă este deja programat, se afișează o casetă de dialog prin returnarea valorii de la apelarea „Actions.modalResult()”. Pe lângă afișarea unui mod, o acțiune poate efectua diferite tipuri de operații într-un mod similar, cum ar fi „navigați la vizualizare”, „reîmprospătați vizualizarea”, „afișați dialogul” și așa mai departe.
Programul utilizatorului de evenimente aplicate
După aplicarea cu succes la un eveniment, browserul este redirecționat către vizualizarea „Evenimentele mele”, care arată o listă de evenimente la care utilizatorul a aplicat. Vederea este definită de următoarea configurație:
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'] } } },
În acest caz, folosim o nouă proprietate de configurare, „filtrare”. Ca și în exemplul nostru anterior, această funcție se bazează și pe dependența „Utilizator”. Dacă funcția returnează un obiect, acesta este tratat ca o interogare MongoDB; interogarea filtrează colecția pentru evenimente care aparțin numai utilizatorului curent.
O altă proprietate interesantă este „Views”. „Vizualizare” este un tip de entitate obișnuit, dar colecția MongoDB este aceeași ca pentru tipul de entitate părinte. Acest lucru face posibilă crearea vizualizărilor diferite pentru aceleași date din baza de date. De fapt, am folosit această funcție pentru a crea două vizualizări diferite pentru „UserEvent:” „MyEvent” și „AppliedUser”. Deoarece prototipul sub-viziunilor este setat la tipul de entitate părinte, proprietățile care nu sunt suprascrise sunt „moștenite” de la tipul părinte.
Listarea participanților la eveniment
După ce se aplică la un eveniment, alți utilizatori pot vedea o listă cu toți utilizatorii care intenționează să participe. Acesta este generat ca urmare a următoarelor elemente de configurare în main.js:
AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } // ... appliedUsers: Fields.relation("Applied users", "AppliedUser", "event")
„AppliedUser” este o vizualizare numai în citire pentru un tip de entitate „MyEvent”. Această permisiune numai pentru citire este impusă prin setarea unei matrice goale la proprietatea „Scrie” a obiectului de permisiuni. De asemenea, deoarece permisiunea „Citire” nu este definită, în mod implicit, citirea este permisă pentru toți utilizatorii.
Extinderea implementărilor implicite
Reducerea tipică a cadrelor RAD este lipsa de flexibilitate. Odată ce ați creat aplicația și trebuie să o personalizați, este posibil să întâmpinați obstacole semnificative. AllcountJS este dezvoltat având în vedere extensibilitatea și permite înlocuirea fiecărui bloc de construcție din interior.
Pentru a realiza acest lucru, AllcountJS utilizează propria sa implementare Dependency Injection (DI). DI permite dezvoltatorului să suprascrie comportamentele implicite ale cadrului prin puncte de extensie și, în același timp, îi permite prin reutilizarea implementărilor existente. Multe aspecte ale extensiei cadrului RAD sunt descrise în documentații. În această secțiune, vom explora modul în care putem extinde două dintre numeroasele componente ale cadrului, logica și vizualizările pe partea serverului.
Continuând cu exemplul nostru Toptal Community, permiteți-ne să integrăm o sursă de date externă pentru a agrega datele despre evenimente. Să ne imaginăm că există postări Toptal Blog care discută planuri pentru evenimente cu o zi înainte de fiecare eveniment. Cu Node.js, ar trebui să fie posibilă analizarea fluxului RSS al blogului și extragerea acestor date. Pentru a face acest lucru, vom avea nevoie de câteva dependențe suplimentare npm, cum ar fi „cerere”, „xml2js” (pentru a încărca fluxul RSS Toptal Blog), „q” (pentru a implementa promisiunile) și „moment” (pentru a analiza datele). Aceste dependențe pot fi instalate invocând următorul set de comenzi:
npm install xml2js npm install request npm install q npm install moment
Să creăm un alt fișier JavaScript, să-l denumim „toptal-community.js” în directorul toptal-community-allcount și să-l populam cu următoarele:
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')); } });
În acest fișier, definim o dependență numită „DiscussionEventsImport”, pe care o putem folosi în fișierul nostru main.js adăugând o acțiune de import pe tipul de entitate „Event”.
{ id: "import-blog-events", name: "Import Blog Events", actionTarget: "all-items", perform: function (DiscussionEventsImport, Actions) { return DiscussionEventsImport.importEvents().then(function () { return Actions.refreshResult() }); } }
Deoarece este important să reporniți serverul după ce ați făcut unele modificări la fișierele JavaScript, puteți să omorâți instanța anterioară și să o porniți din nou executând aceeași comandă ca înainte:
node toptal-community.js
Dacă totul merge bine, veți vedea ceva de genul capturii de ecran de mai jos după rularea acțiunii „Importați evenimente pe blog”.
Până aici, bine, dar să nu ne oprim aici. Vizualizările implicite funcționează, dar pot fi plictisitoare uneori. Haideți să le personalizăm puțin.
Îți plac cărțile? Tuturor le plac cardurile! Pentru a face o vizualizare a cardului, puneți următoarele într-un fișier numit events.jade în directorul 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()
După aceea, trimiteți-o pur și simplu din entitatea „Eveniment” din main.js ca „customView: „evenimente”.” Rulați aplicația și ar trebui să vedeți o interfață bazată pe card în loc de cea tabelară implicită.
Concluzie
În zilele noastre, fluxul de dezvoltare al aplicațiilor web este similar în multe tehnologii web, unde unele operațiuni sunt repetate iar și iar. Chiar merită? Poate că este timpul să regândiți modul în care sunt dezvoltate aplicațiile dvs. web?
AllcountJS oferă o abordare alternativă a cadrelor de dezvoltare rapidă a aplicațiilor; începeți prin a crea un schelet pentru aplicație prin definirea descrierilor entităților și apoi adăugați vizualizări și personalizări de comportament în jurul acesteia. După cum puteți vedea, cu AllcountJS, am creat o aplicație simplă, dar complet funcțională, în mai puțin de o sută de linii de cod. Poate că nu îndeplinește toate cerințele de producție, dar este personalizabil. Toate acestea fac din AllcountJS un instrument bun pentru pornirea rapidă a aplicațiilor web.