Ideen om Rapid Application Development (RAD) ble født som svar på tradisjonelle fossefallutviklingsmodeller. Det finnes mange varianter av RAD; for eksempel Agile utvikling og Rational Unified Process. Imidlertid har alle slike modeller en ting til felles: De tar sikte på å gi maksimal forretningsverdi med minimal utviklingstid gjennom prototyping og iterativ utvikling. For å oppnå dette er Rapid Application Development-modellen avhengig av verktøy som letter prosessen. I denne artikkelen skal vi utforske ett slikt verktøy, og hvordan det kan brukes til å fokusere på forretningsverdi og optimalisering av utviklingsprosessen.
AllcountJS er et framvoksende open source-rammeverk bygget med tanke på rask applikasjonsutvikling. Den er basert på ideen om deklarativ applikasjonsutvikling ved hjelp av JSON-lignende konfigurasjonskode som beskriver applikasjonens struktur og oppførsel. Rammeverket er bygget på toppen av Node.js, Express, MongoDB og er sterkt avhengig av AngularJS og Twitter Bootstrap. Selv om det er avhengig av deklarative mønstre, tillater rammeverket fortsatt videre tilpasning gjennom direkte tilgang til API der det er nødvendig.
I følge Wikipedia , det er minst hundre verktøy som lover rask applikasjonsutvikling, men dette reiser spørsmålet: hvor rask er 'rask'. Tillater disse verktøyene å utvikle et bestemt datasentrisk program på få timer? Eller kanskje det er 'raskt' hvis applikasjonen kan utvikles om noen få dager eller noen få uker. Noen av disse verktøyene hevder til og med at noen minutter er alt som trengs for å produsere en fungerende applikasjon. Det er imidlertid lite sannsynlig at du kan bygge et nyttig program på under fem minutter og fremdeles hevder å ha oppfylt ethvert forretningsbehov. AllcountJS hevder ikke å være et slikt verktøy; det AllcountJS tilbyr er en måte å prototype en idé på kort tid.
Med AllcountJS-rammeverket er det mulig å bygge en applikasjon med et tema automatisk generert brukergrensesnitt, brukeradministrasjonsfunksjoner, RESTful API og en håndfull andre funksjoner med minimal innsats og tid. Det er mulig å bruke AllcountJS til et bredt utvalg av brukstilfeller, men det passer best for applikasjoner der du har forskjellige samlinger av objekter med forskjellige visninger for dem. Forretningsapplikasjoner passer vanligvis godt for denne modellen.
AllcountJS har blitt brukt til å bygge allcountjs.com , pluss en prosjektsporing for det. Det er verdt å merke seg at allcountjs.com er et tilpasset AllcountJS-program, og at AllcountJS gjør at både statiske og dynamiske visninger kan kombineres med lite bry. Det gjør det også mulig å sette inn dynamisk lastede deler i statisk innhold. For eksempel administrerer AllcountJS en samling demo-applikasjonsmaler. Det er en demo-widget på hovedsiden til allcountjs.com som laster inn en tilfeldig applikasjonsmal fra den samlingen. En håndfull andre eksempler på applikasjoner er tilgjengelig i galleri på allcountjs.com .
For å demonstrere noen av funksjonene til RAD framework AllcountJS, vil vi lage en enkel applikasjon for ApeeScape, som vi vil kalle ApeeScape Community. Hvis du følger bloggen vår, vet du kanskje allerede at en lignende applikasjon ble laget med Hoodie som en del av et av våre tidligere blogginnlegg . Denne applikasjonen lar medlemmene i fellesskapet registrere seg, opprette arrangementer og søke om å delta på dem.
For å sette opp miljøet, bør du installere Node.js , MongoDB og Gå . Installer deretter AllcountJS CLI ved å påkalle en 'npm install' -kommando og utfør prosjektinit:
npm install -g allcountjs-cli allcountjs init toptal-community-allcount cd toptal-community-allcount npm install
AllcountJS CLI vil be deg om å legge inn litt informasjon om prosjektet ditt for å forhåndsutfylle package.json.
AllcountJS kan brukes som frittstående server eller som en avhengighet. I vårt første eksempel skal vi ikke utvide AllcountJS, så en frittstående server bør bare fungere for oss.
Inne i denne nyopprettede app-config-katalogen, vil vi erstatte innholdet i main.js JavaScript-filen med følgende kodebit:
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') } } } } } });
Selv om AllcountJS fungerer med Git-arkiver, vil vi for enkelhets skyld ikke bruke det i denne opplæringen. For å kjøre applikasjonen ApeeScape Community, er alt vi trenger å gjøre å påkalle AllcountJS CLI-kjørekommandoen i katalogen toptal-community-allcount.
allcountjs run
Det er verdt å merke seg at MongoDB skal kjøre når denne kommandoen utføres. Hvis alt går bra, bør applikasjonen være i gang på http: // localhost: 9080.
For å logge inn, bruk brukernavnet “admin” og passordet “admin”.
Du har kanskje lagt merke til at applikasjonen definert i main.js bare tok 91 linjer med kode. Disse linjene inkluderer erklæringen om all atferd du kan observere når du navigerer til http: // localhost: 9080. Så hva skjer egentlig under panseret? La oss se nærmere på hvert aspekt av applikasjonen, og se hvordan koden forholder seg til dem.
Den første siden du ser etter at du har åpnet applikasjonen, er en pålogging. Dette fungerer også som en registreringsside, forutsatt at avkrysningsruten - merket “Registrer deg” - er merket av før du sender inn skjemaet.
Denne siden vises fordi main.js-filen erklærer at bare godkjente brukere kan bruke dette programmet. Videre gjør det det mulig for brukere å registrere seg fra denne siden. Følgende to linjer er alt som var nødvendig for dette:
A.app({ ..., onlyAuthenticated: true, allowSignUp: true, ... })
Etter pålogging blir du omdirigert til en velkomstside med en applikasjonsmeny. Denne delen av applikasjonen genereres automatisk, basert på menyelementene som er definert under 'menuItems' -tasten.
Sammen med et par andre relevante konfigurasjoner er menyen definert i main.js-filen som følger:
A.app({ ..., appName: 'ApeeScape Community', appIcon: 'rocket', menuItems: [{ name: 'Events', entityTypeId: 'Event', icon: 'calendar' }, { name: 'My Events', entityTypeId: 'MyEvent', icon: 'calendar' }], ... });
AllcountJS bruker Font Awesome ikoner, slik at alle ikonnavn som det er referert til i konfigurasjonen, blir tilordnet Font Awesome ikonnavn.
Etter å ha klikket på 'Hendelser' fra menyen, blir du ført til hendelsesvisningen som vises i skjermbildet nedenfor. Det er en standard AllcountJS-visning som gir noen generiske CRUD-funksjoner på de tilsvarende enhetene. Her kan du søke etter hendelser, opprette nye hendelser og redigere eller slette eksisterende. Det er to moduser for dette CRUD-grensesnittet: liste og skjema. Denne delen av applikasjonen er konfigurert gjennom følgende få linjer med JavaScript-kode.
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]], ... } } } });
Dette eksemplet viser hvordan enhetsbeskrivelser er konfigurert i AllcountJS. Legg merke til hvordan vi bruker en funksjon til å definere enhetene; hver eiendom med AllcountJS-konfigurasjon kan være en funksjon. Disse funksjonene kan be om at avhengigheter løses gjennom argumentnavnet. Før funksjonen blir kalt, injiseres passende avhengigheter. Her er “Fields” en av APIene for AllcountJS-konfigurasjon som brukes til å beskrive enhetsfelt. Eiendommen 'Enheter' inneholder navn-verdipar der navnet er en identitetskode for enhetstypen og verdien er beskrivelsen. En enhetstype for hendelser er beskrevet i dette eksemplet, der tittelen er 'Hendelser.' Andre konfigurasjoner, som standard sorteringsrekkefølge, referansenavn og lignende, kan også defineres her. Standard sorteringsrekkefølge er definert gjennom en rekke feltnavn og retninger, mens referansenavnet er definert gjennom en streng ( les mer her ).
Denne spesifikke enhetstypen er definert som å ha fire felt: 'eventName', 'date', 'time' og 'appliedUsers', hvorav de tre første er i databasen. Disse feltene er obligatoriske, som angitt ved bruk av 'obligatorisk ().' Verdier i disse feltene med slike regler valideres før skjemaet sendes i frontend som vist på skjermbildet nedenfor. AllcountJS kombinerer validering av klientsiden og serversiden for å gi den beste brukeropplevelsen. Det fjerde feltet er et forhold som inneholder en liste over brukere som har søkt om å delta på arrangementet. Naturligvis er dette feltet ikke vedvarende i databasen, og fylles ut ved å velge bare de AppliedUser-enhetene som er relevante for hendelsen.
Når en bruker velger en bestemt hendelse, viser verktøylinjen en knapp merket 'Bruk'. Ved å klikke på den legges hendelsen til brukerens tidsplan. I AllcountJS kan lignende handlinger konfigureres ved å kunngjøre dem i konfigurasjonen:
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') }); } }); }) } }]
Egenskapen 'handlinger' av en hvilken som helst enhetstype tar en rekke objekter som beskriver oppførselen til hver tilpasset handling. Hvert objekt har en 'id' -egenskap som definerer en unik identifikator for handlingen, egenskapen 'navn' definerer visningsnavnet og egenskapen 'actionTarget' brukes til å definere handlingskonteksten. Å sette “actionTarget” til “single-item” indikerer at handlingen skal utføres med en bestemt hendelse. En funksjon definert under egenskapen 'utfør' er logikken som utføres når denne handlingen utføres, vanligvis når brukeren klikker på den tilsvarende knappen.
Avhengigheter kan bli bedt om av denne funksjonen. I dette eksemplet er for eksempel funksjonen avhengig av 'Bruker', 'Handlinger' og 'Crud.' Når en handling skjer, kan en henvisning til brukeren, som påkaller denne handlingen, oppnås ved å kreve 'bruker' -avhengighet. 'Crud' -avhengigheten, som tillater manipulering av databasetilstand for disse enhetene, blir også bedt om her. De to metodene som returnerer en forekomst av Crud-objekt er: Metoden 'actionContextCrud ()' - returnerer CRUD for 'Event' -enhetstype siden handlingen 'Apply' tilhører den, mens metoden 'crudForEntityType ()' - returnerer CRUD for enhver enhetstype som er identifisert av typen ID.
Implementeringen av handlingen begynner med å sjekke om denne hendelsen allerede er planlagt for brukeren, og hvis ikke, oppretter den en. Hvis den allerede er planlagt, vises en dialogboks ved å returnere verdien fra å ringe 'Actions.modalResult ()'. Foruten å vise et modal, kan en handling utføre forskjellige typer operasjoner på en lignende måte, for eksempel 'navigere til visning', 'oppdater visning', 'vis dialog' og så videre.
Etter vellykket bruk av en hendelse, blir nettleseren omdirigert til 'Mine hendelser' -visningen, som viser en liste over hendelser brukeren har søkt på. Visningen er definert av følgende konfigurasjon:
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'] } } },
I dette tilfellet bruker vi en ny konfigurasjonsegenskap, 'filtrering'. Som med vårt tidligere eksempel, er denne funksjonen også avhengig av 'bruker' -avhengighet. Hvis funksjonen returnerer et objekt, blir det behandlet som et MongoDB-spørsmål; spørringen filtrerer samlingen for hendelser som bare tilhører den nåværende brukeren.
En annen interessant egenskap er 'Views'. 'Visning' er en vanlig enhetstype, men MongoDB-samlingen er den samme som for overordnet enhetstype. Dette gjør det mulig å lage visuelt forskjellige visninger for de samme dataene i databasen. Faktisk brukte vi denne funksjonen til å lage to forskjellige visninger for 'UserEvent:' 'MyEvent' og 'AppliedUser.' Siden prototypen til undervisningene er satt til den overordnede enhetstypen, 'arves' egenskaper fra den overordnede typen.
Etter å ha søkt på et arrangement, kan andre brukere se en liste over alle brukerne som planlegger å delta. Dette genereres som et resultat av følgende konfigurasjonselementer i main.js:
AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } // ... appliedUsers: Fields.relation('Applied users', 'AppliedUser', 'event')
'AppliedUser' er en skrivebeskyttet visning for en 'MyEvent' enhetstype. Denne skrivebeskyttede tillatelsen håndheves ved å sette en tom matrise til 'Skriv' -egenskapen til tillatelsesobjektet. Ettersom 'Lesetillatelsen' ikke er definert, er lesing også tillatt for alle brukere.
Den typiske bakgrunnen for RAD-rammer er mangelen på fleksibilitet. Når du har bygget appen din, og du må tilpasse den, kan du støte på betydelige hindringer. AllcountJS er utviklet med tanke på utvidelse og tillater utskifting av hver byggestein inni.
For å oppnå at AllcountJS bruker sin egen Dependency Injection (DI) implementering. DI tillater utvikleren å overstyre standardoppførselen til rammeverket gjennom utvidelsespunkter, og samtidig tillate det gjennom gjenbruk av eksisterende implementeringer. Mange aspekter av RAD rammeverk er beskrevet i dokumentasjoner . I denne delen vil vi undersøke hvordan vi kan utvide to av de mange komponentene i rammeverket, serverlogikk og visninger.
Fortsett med vårt ApeeScape Community-eksempel, la oss integrere en ekstern datakilde for å samle hendelsesdata. La oss forestille oss at det er ApeeScape-blogginnlegg som diskuterer planer for arrangementer dagen før hver hendelse. Med Node.js skal det være mulig å analysere bloggens RSS-feed og trekke ut slike data. For å gjøre dette, trenger vi noen ekstra npm-avhengigheter, for eksempel “forespørsel”, “xml2js” (for å laste ApeeScape Blog RSS-feed), “q” (for å implementere løfter) og “øyeblikk” (for å analysere datoer). Disse avhengighetene kan installeres ved å påkalle følgende sett med kommandoer:
npm install xml2js npm install request npm install q npm install moment
La oss lage en annen JavaScript-fil, gi den navnet 'toptal-community.js' i katalogen toptal-community-allcount og fylle den ut med følgende:
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('
')); } });
I denne filen definerer vi en avhengighet som kalles 'DiscussionEventsImport', som vi kan bruke i main.js-filen vår ved å legge til en importhandling på enhetstypen 'Event'.
{ id: 'import-blog-events', name: 'Import Blog Events', actionTarget: 'all-items', perform: function (DiscussionEventsImport, Actions) { return DiscussionEventsImport.importEvents().then(function () { return Actions.refreshResult() }); } }
Siden det er viktig å starte serveren på nytt etter å ha gjort noen endringer i JavaScript-filene, kan du drepe forrige forekomst og starte den på nytt ved å utføre den samme kommandoen som før:
node toptal-community.js
Hvis alt går bra, vil du se noe som skjermbildet nedenfor etter å ha kjørt 'Importer blogghendelser' -handlingen.
Så langt så bra, men la oss ikke stoppe her. Standardvisninger fungerer, men de kan være kjedelige til tider. La oss tilpasse dem litt.
Liker du kort? Alle liker kort! For å lage en kortvisning, legg følgende i en fil som heter events.jade i app-config-katalogen:
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()
Deretter refererer du det bare fra 'Event' -enheten i main.js som 'customView:' events '.' Kjør appen din, og du bør se et kortbasert grensesnitt i stedet for standardtabellen.
I dag er utviklingsflyten for webapplikasjoner lik på tvers av mange webteknologier, der noen operasjoner gjentas igjen og igjen. Er det virkelig verdt det? Kanskje det er på tide å revurdere måten webapplikasjonene dine er utviklet på?
AllcountJS gir en alternativ tilnærming til raske rammer for applikasjonsutvikling; du begynner med å lage et skjelett for applikasjonen ved å definere enhetsbeskrivelser, og deretter legge til visninger og tilpasning av atferd rundt det. Som du kan se, med AllcountJS opprettet vi en enkel, men fullt funksjonell applikasjon, på under hundre linjer med kode. Kanskje oppfyller den ikke alle produksjonskrav, men den kan tilpasses. Alt dette gjør AllcountJS til et godt verktøy for rask oppstart av webapplikasjoner.