Kiire rakenduse arendamise (RAD) idee sündis vastusena traditsioonilistele jugade arengumudelitele. RAD-i variatsioone on palju; näiteks vilgas arendus ja ratsionaalne ühtne protsess. Kõigil sellistel mudelitel on siiski üks ühine omadus: nende eesmärk on prototüüpide loomise ja iteratiivse arendamise kaudu saada maksimaalset äriväärtust minimaalse arendusaega. Selle saavutamiseks tugineb kiirrakenduse arendamise mudel protsessi hõlbustavatele tööriistadele. Selles artiklis uurime ühte sellist tööriista ja seda, kuidas seda saab kasutada ettevõtte väärtusele ja arendusprotsessi optimeerimisele keskendumiseks.
AllcountJS on tekkiv avatud lähtekoodiga raamistik, mis on loodud rakenduste kiiret arendamist silmas pidades. See põhineb deklaratiivse rakenduse arendamise ideel, kasutades JSON-i sarnast konfiguratsioonikoodi, mis kirjeldab rakenduse struktuuri ja käitumist. Raamistik on ehitatud Node.js, Expressi, MongoDB peale ja tugineb suuresti sellele NurgaJS ja Twitteri alglaadimine. Ehkki see tugineb deklaratiivsetele mustritele, võimaldab raamistik siiski vajaduse korral API-le otsese juurdepääsu kaudu täiendavat kohandamist.
Vikipeedia andmetel , on vähemalt sada tööriista, mis lubavad rakenduste kiiret arendamist, kuid see tekitab küsimuse: kui kiire on „kiire”. Kas need tööriistad võimaldavad mõne tunni jooksul välja töötada konkreetse andmekeskse rakenduse? Või võib-olla on see „kiire“, kui rakenduse saab välja töötada mõne päeva või mõne nädala jooksul. Mõni neist tööriistadest väidab isegi, et mõne rakenduse loomiseks kulub vaid mõni minut. Kuid on ebatõenäoline, et saaksite kasuliku rakenduse alla viie minutiga ehitada ja siiski väita, et olete rahuldanud kõik ettevõtte vajadused. AllcountJS ei pretendeeri sellisele tööriistale; see, mida AllcountJS pakub, on viis idee prototüüpimiseks lühikese aja jooksul.
AllcountJS-i raamistiku abil on minimaalse vaevaga ja ajaga võimalik luua rakendus, millel on automaatselt kujundatav kasutajaliides, kasutajahalduse funktsioonid, RESTful API ja käputäis muid funktsioone. AllcountJS-i on võimalik kasutada mitmesuguste kasutusjuhtumite jaoks, kuid see sobib kõige paremini rakendustele, kus teil on erinevaid objektide kogumeid, millel on erinevad vaated. Tavaliselt sobivad selle rakenduse jaoks ärirakendused.
Ehitamiseks on kasutatud AllcountJS-i allcountjs.com , millele lisandub ka projekti jälgija. Väärib märkimist, et allcountjs.com on kohandatud rakendus AllcountJS ja et AllcountJS võimaldab nii staatilisi kui ka dünaamilisi vaateid vähese vaevaga kombineerida. See võimaldab isegi dünaamiliselt koormatud osi staatilisse sisusse sisestada. Näiteks haldab AllcountJS demorakenduse mallide kogu. Allcountjs.com avalehel on demovidin, mis laadib sellest kollektsioonist juhusliku rakenduse malli. Programmis on saadaval veel käputäis muid näidisrakendusi galerii aadressil allcountjs.com .
RAD-i raamistiku AllcountJS mõnede võimaluste demonstreerimiseks loome ApeeScape'ile lihtsa rakenduse, mida nimetame ApeeScape-i kogukonnaks. Kui jälgite meie ajaveebi, võite juba teada, et sarnane rakendus on loodud osana Hoodie'st üks meie varasematest blogipostitustest . See rakendus võimaldab kogukonna liikmetel registreeruda, üritusi luua ja nendel osalemiseks kandideerida.
Keskkonna seadistamiseks peaksite installima Node.js , MongoDB ja Mine . Seejärel installige AllcountJS CLI, käivitades käsu „npm install” ja tehke projekti init:
npm install -g allcountjs-cli allcountjs init toptal-community-allcount cd toptal-community-allcount npm install
AllcountJS CLI palub teil sisestada projekti kohta natuke teavet, et paketiks package.json eeltäita.
AllcountJS-i saab kasutada iseseisva serverina või sõltuvusena. Esimeses näites ei kavatse me AllcountJSi laiendada, seega peaks eraldiseisev server lihtsalt meie jaoks töötama.
Selles vastloodud rakenduse-konfiguratsiooni kataloogis asendame JavaScripti faili main.js sisu järgmise koodilõiguga:
A.app({ appName: 'ApeeScape 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') } } } } } });
Kuigi AllcountJS töötab Giti hoidlatega, ei kasuta me selle õpetuse jaoks lihtsuse huvides seda. ApeeScape Community rakenduse käivitamiseks peame vaid kutsuma kataloogi toptal-community-allcount käsu AllcountJS CLI run.
allcountjs run
Väärib märkimist, et MongoDB peaks selle käsu käivitamisel töötama. Kui kõik hästi läheb, peaks rakendus olema töökorras aadressil http: // localhost: 9080.
Sisselogimiseks kasutage kasutajanime “admin” ja parooli “admin”.
Võib-olla olete märganud, et main.js-s määratletud rakendus võttis ainult 91 koodirida. Need read sisaldavad kõigi käitumismudelite deklareerimist, mida võite jälgida aadressile http: // localhost: 9080 navigeerimisel. Mis siis täpselt toimub, kapoti all? Vaatame rakenduse kõiki aspekte lähemalt ja vaatame, kuidas kood nendega seostub.
Esimene leht, mida näete pärast rakenduse avamist, on sisselogimine. See toimib ka registreerimislehena, eeldades, et enne vormi esitamist on märgitud ruut „Registreeru”.
Seda lehte näidatakse, kuna fail main.js deklareerib, et ainult autentitud kasutajad võivad seda rakendust kasutada. Veelgi enam, see võimaldab kasutajatel registreeruda sellelt lehelt. Selleks olid kõik järgmised kaks rida vajalikud:
A.app({ ..., onlyAuthenticated: true, allowSignUp: true, ... })
Pärast sisselogimist suunatakse teid rakenduse menüüga tervituslehele. See rakenduse osa genereeritakse automaatselt menüü üksuste põhjal, mis on määratletud klahvi “menuItems” all.
Koos paari muu asjakohase konfiguratsiooniga on menüü failis main.js määratletud järgmiselt:
A.app({ ..., appName: 'ApeeScape Community', appIcon: 'rocket', menuItems: [{ name: 'Events', entityTypeId: 'Event', icon: 'calendar' }, { name: 'My Events', entityTypeId: 'MyEvent', icon: 'calendar' }], ... });
AllcountJS kasutab Font Awesome ikoonid, seega kõik konfiguratsioonis viidatud ikooninimed vastendatakse Font Awesome ikooninimedega.
Pärast menüüst „Sündmused” klõpsamist suunatakse teid alloleval ekraanipildil kuvatud sündmuste vaatesse. See on tavaline AllcountJS-i vaade, mis pakub vastavatele üksustele mõningaid üldisi CRUD-funktsioone. Siin saate otsida sündmusi, luua uusi sündmusi ja olemasolevaid muuta või kustutada. CRUD-liidesel on kaks režiimi: loend ja vorm. Rakenduse see osa on konfigureeritud mõne järgmise JavaScripti koodi rida.
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]], ... } } } });
See näide näitab, kuidas üksuste kirjeldused seadistatakse rakenduses AllcountJS. Pange tähele, kuidas me kasutame funktsiooni üksuste määratlemiseks; kõik AllcountJS-i konfiguratsiooni atribuudid võivad olla funktsioonid. Need funktsioonid võivad taotleda sõltuvuste lahendamist argumentide nimede kaudu. Enne funktsiooni kutsumist süstitakse sobivad sõltuvused. Siin on väljad üks üksustest AllcountJS konfiguratsiooni API-d, mida kasutatakse üksuse väljade kirjeldamiseks. Atribuut „Entities” sisaldab nime-väärtuse paare, kus nimi on olemitüübi identifikaator ja väärtus on selle kirjeldus. Selles näites kirjeldatakse sündmuste olemitüüpi, kus pealkiri on „Sündmused”. Siinkohal võidakse määratleda ka muud konfiguratsioonid, näiteks vaikimisi sortimise järjestus, viitenimi jms. Vaikimisi sortimisjärjestus määratletakse väljade nimede ja suundade massiivi kaudu, viitenimi aga stringi ( loe lähemalt siit ).
Sellel konkreetsel olemitüübil on määratletud neli välja: 'eventName', 'date', 'time' ja 'appliedUsers', millest esimesed kolm jäävad andmebaasi. Need väljad on kohustuslikud, millele viitab „nõutav ()”. Nendel väljadel olevad väärtused koos selliste reeglitega kinnitatakse enne vormi esitamist kasutajaliideses, nagu on näidatud alloleval ekraanipildil. Parima kasutuskogemuse pakkumiseks ühendab AllcountJS nii kliendipoolseid kui ka serveripoolseid valideerimisi. Neljas väli on suhe, mis kannab nimekirja kasutajatest, kes on kandideerinud üritusele. Loomulikult ei püsi seda välja andmebaasis ja see täidetakse, valides ainult need sündmusega seotud AppliedUseri olendid.
Kui kasutaja valib konkreetse sündmuse, kuvatakse tööriistaribal nupp sildiga „Rakenda“. Sellel klõpsamine lisab sündmuse kasutaja ajakavasse. AllcountJS-is saab sarnaseid toiminguid konfigureerida, deklareerides need lihtsalt konfiguratsioonis:
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') }); } }); }) } }]
Mis tahes olemitüübi atribuudi „toimingud” jaoks on vaja massiivi objekte, mis kirjeldavad iga kohandatud toimingu käitumist. Igal objektil on omadus „id”, mis määratleb toimingu kordumatu identifikaatori, omadus „nimi” - kuvatava nime ja omadust „actionTarget” kasutatakse toimingu konteksti määratlemiseks. Kui määrate „actionTarget” olekuks „single-item”, näitab see, et toiming tuleks teha konkreetse sündmusega. Atribuudi „perform” all määratletud funktsioon on selle toimingu sooritamisel loogika, tavaliselt siis, kui kasutaja klõpsab vastaval nupul.
See funktsioon võib taotleda sõltuvusi. Näiteks sõltub see näide funktsioonidest „Kasutaja”, „Toimingud” ja „Jämedus”. Kui toiming toimub, saab viite kasutajale, viidates sellele toimingule, nõudes sõltuvust kasutajast. Siin küsitakse ka sõltuvust „Crud“, mis võimaldab nende üksuste jaoks andmebaasi olekut manipuleerida. Kaks meetodit, mis tagastavad Crudi objekti eksemplari, on: Meetod “actionContextCrud ()” - tagastab CRUD olemi tüübi “Event” jaoks, kuna toiming “Apply” kuulub selle juurde, meetod “crudForEntityType ()” aga tagastab CRUD iga üksuse tüübi jaoks, mis on identifitseeritud selle tüübi ID järgi.
Toimingu rakendamine algab kontrollides, kas see sündmus on kasutajale juba ajastatud, ja kui ei, siis loob selle. Kui see on juba ajastatud, kuvatakse dialoogiboks, tagastades väärtuse helistamisel „Actions.modalResult ()”. Lisaks modali kuvamisele võib toiming sarnasel viisil teha ka erinevat tüüpi toiminguid, näiteks „vaates navigeerimine”, „vaate värskendamine”, „kuva dialoog” ja nii edasi.
Pärast edukalt rakendamist sündmusele suunatakse brauser vaate „Minu sündmused“ juurde, kus kuvatakse loend sündmustest, millele kasutaja on rakendunud. Vaade on määratletud järgmise konfiguratsiooniga:
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'] } } },
Sel juhul kasutame uut konfigureerimise atribuuti “filtreerimine”. Nagu meie varasemas näites, tugineb see funktsioon ka kasutaja sõltuvusele. Kui funktsioon tagastab objekti, käsitletakse seda kui MongoDB päringut; päring filtreerib ainult praegusele kasutajale kuuluvate sündmuste kogu.
Teine huvitav vara on vaated. „Vaade” on tavaline olemitüüp, kuid see on MongoDB kogu, mis on sama kui emaüksuse tüüp. See võimaldab andmebaasis samade andmete jaoks visuaalselt erinevaid vaateid luua. Tegelikult kasutasime seda funktsiooni kahe erineva vaate loomiseks rakenduse „UserEvent“ jaoks: „MyEvent“ ja „AppliedUser“. Kuna alamvaadete prototüüp on seatud vanemüksuse tüübile, siis pärandatakse atribuudid, mida ei tühistata, vanematüübilt.
Pärast üritusele kandideerimist võivad teised kasutajad näha nimekirja kõigist kasutajatest, kes kavatsevad sellel osaleda. See genereeritakse järgmiste konfiguratsioonielementide tulemusena saidil main.js:
AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } // ... appliedUsers: Fields.relation('Applied users', 'AppliedUser', 'event')
„AppliedUser” on olemitüübi „MyEvent” kirjutuskaitstud vaade. Selle kirjutuskaitstud õiguse jõustamiseks määratakse õiguste objekti atribuudile „Kirjutamine” tühi massiiv. Kuna lugemisluba pole määratletud, on vaikimisi lubatud lugemine kõigile kasutajatele.
RAD-i raamistike tüüpiline tagapõhi on paindlikkuse puudumine. Kui olete oma rakenduse ehitanud ja peate seda kohandama, võib teil tekkida märkimisväärseid takistusi. AllcountJS on välja töötatud laiendatavust silmas pidades ja võimaldab iga sees oleva ehitusploki vahetamist.
Selle saavutamiseks kasutab AllcountJS oma Dependency Injection (DI) rakendust. DI võimaldab arendajal laienduspunktide kaudu alistada raamistiku vaikekäitumise ja võimaldab samal ajal olemasolevate rakenduste taaskasutust. RAD raamistiku laiendamise paljusid aspekte on kirjeldatud dokumendis dokumentatsiooni . Selles jaotises uurime, kuidas saaksime laiendada raamistiku paljudest komponentidest kahte, serveripoolset loogikat ja vaateid.
Jätkates meie ApeeScape'i kogukonna näitega, integreerime sündmuste andmete koondamiseks välise andmeallika. Kujutame ette, et päev enne igat sündmust on ApeeScape'i blogi postitusi, kus arutatakse ürituste plaane. Node.js-ga peaks olema võimalik blogi RSS-voogu sõeluda ja selliseid andmeid välja võtta. Selleks vajame mõningaid täiendavaid sõltuvusi npm-st, näiteks “request”, “xml2js” (ApeeScape Blogi RSS-voo laadimiseks), “q” (lubaduste elluviimiseks) ja “moment” (kuupäevade sõelumiseks). Neid sõltuvusi saab installida järgmiste käskude komplekti abil:
npm install xml2js npm install request npm install q npm install moment
Loome veel ühe JavaScripti faili, nimetage see kataloogis toptal-community-allcount nimeks “toptal-community.js” ja täitke see järgmisega:
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('
')); } });
Selles failis määratleme sõltuvust nimega “DiscussionEventsImport”, mida saame kasutada failis main.js, lisades olemi tüübile “Event” imporditoimingu.
{ id: 'import-blog-events', name: 'Import Blog Events', actionTarget: 'all-items', perform: function (DiscussionEventsImport, Actions) { return DiscussionEventsImport.importEvents().then(function () { return Actions.refreshResult() }); } }
Kuna pärast JavaScripti failide mõningaid muudatusi on oluline server taaskäivitada, võite eelmise eksemplari tappa ja uuesti käivitada, käivitades sama käsu nagu varem:
node toptal-community.js
Kui kõik läheb hästi, näete pärast toimingu „Blogi sündmuste importimine” käivitamist allpool kuvatavat pilti.
Siiani nii hästi, kuid ärgem piirdugem sellega. Vaikevaated töötavad, kuid need võivad mõnikord olla igavad. Kohandame neid veidi.
Kas teile meeldivad kaardid? Kõigile meeldivad kaardid! Kaardivaate loomiseks pange rakenduse config kataloogi faili events.jade järgmine:
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 } {{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()
Pärast seda viidake sellele lihtsalt main.js üksuses „Sündmus” kui „customView:„ sündmused ”.” Käivitage rakendus ja peaksite nägema tabelipõhise vaikeliidese asemel kaardipõhist liidest.
Tänapäeval on veebirakenduste arendusvoog sarnane paljude veebitehnoloogiatega, kus mõnda toimingut korratakse ikka ja jälle. Kas see on tõesti seda väärt? Võib-olla on aeg oma veebirakenduste arendamine uuesti läbi mõelda?
AllcountJS pakub alternatiivset lähenemist rakenduste kiirele arendusraamistikule; alustate rakenduse skeleti loomisega, määratledes üksuste kirjeldused ning lisades seejärel selle ümber vaated ja käitumise kohandused. Nagu näete, lõime AllcountJS-iga lihtsa, kuid siiski täiesti funktsionaalse rakenduse vähem kui sajas koodireas. Võib-olla ei vasta see kõigile tootmisnõuetele, kuid on kohandatav. See kõik teeb AllcountJSist hea tööriista veebirakenduste kiireks käivitamiseks.