Init.js: una guida al perché e al come di JavaScript full-stack
Pubblicato: 2022-03-11La storia
Quindi, tu e il tuo co-fondatore avete questa grande idea per un business, giusto?
Hai aggiunto funzionalità nella tua mente.
Spesso chiedi ai potenziali clienti le loro opinioni e tutti lo adorano.
Ok, quindi la gente lo vuole. Ci sono anche dei soldi da fare. E l'unico motivo per cui non possono averlo è perché non l'hai ancora implementato.
Quindi, alla fine, un giorno ti siedi e dici: "Facciamolo!" Presto cercherai di capire come implementare la logica di business della tua app, la caratteristica killer che farà avanzare il prodotto: hai un'idea di come farlo e sai che puoi farlo.
"Fatto! Funziona!" tu dici. Il tuo proof of concept è un successo! Non resta che confezionarlo in un'app Web.
“Ok, creiamo il sito”, dici.
E poi ti rendi conto della verità: devi scegliere un linguaggio di programmazione; devi scegliere una piattaforma (moderna); devi scegliere alcuni framework (moderni); è necessario configurare (e acquistare) storage, database e provider di hosting; hai bisogno di un'interfaccia di amministrazione; hai bisogno di un sistema di permessi; hai bisogno di un gestore di contenuti.
Hai decine e decine di decisioni architettoniche da prendere. E vuoi fare quelli giusti: vuoi utilizzare tecnologie che consentano uno sviluppo rapido, un'iterazione costante, la massima efficienza, velocità, robustezza e altro ancora. Vuoi essere snello, vuoi essere agile. Vuoi utilizzare tecnologie che ti aiutino ad avere successo a breve e lungo termine. E non sono sempre facili da individuare.
"Sono sopraffatto", dici, mentre ti senti sopraffatto. La tua energia non è più la stessa di una volta. Cerchi di mettere insieme le cose, ma è troppo lavoro.
Il tuo proof of concept lentamente appassisce e muore.
La proposta
Dopo aver abbandonato io stesso tonnellate di idee in questo modo, ho deciso di progettare una soluzione. Lo chiamo il progetto 'Init' (o, init.js).
Il fulcro dell'idea è avere un unico progetto per avviarli tutti, per consentire allo sviluppatore o al fondatore tecnico di prendere tutte queste decisioni essenziali contemporaneamente e ricevere un modello di partenza appropriato basato su tali decisioni. So cosa diranno i detrattori: "Una soluzione non può essere applicata a ogni problema" (gli odiatori odieranno). E potrebbero avere ragione. Ma possiamo fare del nostro meglio per creare una soluzione approssimativa e penso che Init si avvicini molto.
Per raggiungere al meglio questo obiettivo, dobbiamo tenere a mente alcune idee chiave. Durante lo sviluppo di Init, ho considerato:
Componenti
La componentizzazione è una caratteristica chiave di qualsiasi sistema in quanto consente di riutilizzare i componenti software in diversi progetti, che è l'obiettivo principale di Init. Ma la componentizzazione arriva anche con un sottoprodotto, la "sostituibilità", che sarà il nostro miglior alleato per affrontare diversi problemi con "quasi" la stessa soluzione.
Facilità di sviluppo
Qualche problema, da qualche parte ha una soluzione meglio scritta in Brainf*ck. Ma implementare quella soluzione (in Brainfuck) sarà quasi impossibile da scrivere, per non parlare di leggere. Ti costerà tempo e un'enorme quantità di sforzi. In generale, dovresti usare linguaggi e piattaforme che rendano lo sviluppo più facile, non più difficile per te (o per chiunque possa lavorarci in seguito).
Comunità
Qualunque sia la piattaforma che scegli, assicurati che abbia una grande comunità e una che possa aiutarti con i problemi più comuni e non comuni. Ricorda: jQuery potrebbe non essere la libreria più veloce, più pulita o più elegante, ma è vincente solo per la sua comunità.
Tenendo presenti questi obiettivi, ti mostrerò in seguito come ho preso le mie decisioni nella creazione di Init.
In sostanza, Init sfrutta il paradigma "JavaScript full-stack" (alcune persone si riferiscono ad esso, oa un suo sottoinsieme, come MEAN Stack). Lavorando con tale stack, Init è in grado di utilizzare un solo linguaggio creando un ambiente incredibilmente flessibile e completo per lo sviluppo di app Web. In breve, Init ti consente di utilizzare JavaScript non solo per lo sviluppo di client e server, ma anche per creare, testare, creare modelli e altro ancora.
Ma rallentiamo un attimo e chiediamoci: è davvero una buona idea usare JavaScript?
Perché ho scelto JavaScript
Sono uno sviluppatore web dal 1998. Allora usavamo Perl per la maggior parte del nostro sviluppo lato server, ma anche da allora abbiamo avuto JavaScript sul lato client. Le tecnologie dei server Web sono cambiate immensamente da allora: abbiamo attraversato ondate di linguaggi e tecnologie come PHP, AP, JSP, .NET, Ruby, Python, solo per citarne alcuni. Gli sviluppatori hanno iniziato a rendersi conto che l'utilizzo di due linguaggi diversi per gli ambienti client e server stava complicando le cose. I tentativi iniziali di unificazione in un unico linguaggio hanno cercato di creare componenti client sul server e di compilarli in JavaScript. Questo non ha funzionato come previsto e la maggior parte di questi progetti ha fallito (ad esempio: ASP MVC che ha sostituito i moduli Web ASP.NET e GWT probabilmente sarà sostituito nel prossimo futuro da Polymer). Ma è stata una grande idea, in sostanza: un unico linguaggio sul client e sul server, che ci ha permesso di riutilizzare componenti e risorse (questa è la parola chiave: risorse ).
La risposta è stata semplice: mettere JavaScript sul server.
JavaScript in realtà è nato con JavaScript Server Side in Netscape Enterprise Server, ma il linguaggio semplicemente non era pronto in quel momento. Dopo anni di tentativi ed errori, finalmente è emerso Node.js che non solo ha messo JavaScript sul server, ma ha anche promosso l'idea di una programmazione non bloccante, cambiando per sempre il modo in cui scriviamo un "paura" (I/O) (leggi qui per più).
Ma quelle idee non erano nuove, quindi perché sono diventate così popolari con Node.js? È possibile ottenere una programmazione semplice e non bloccante in diversi modi. Forse il modo più semplice è usare callback e un ciclo di eventi. Nella maggior parte delle lingue, non è un compito facile: mentre i "callback" sono una caratteristica comune in alcuni altri linguaggi, un ciclo di eventi non lo è e spesso ti ritrovi alle prese con librerie esterne (ad esempio: Python, con Tornado). Ma in JavaScript, i callback sono incorporati nel linguaggio, così come il ciclo degli eventi, e quasi tutti i programmatori che si sono anche dilettati in JavaScript li conoscono (o almeno li hanno usati, anche se non capiscono bene quale sia l'evento ciclo è). Improvvisamente, ogni startup sulla Terra potrebbe riutilizzare gli sviluppatori (cioè le risorse) sia sul lato client che sul lato server, risolvendo il problema del post di lavoro "Python Guru Needed".
Quindi ora abbiamo una piattaforma incredibilmente veloce (grazie alla programmazione non bloccante) con un linguaggio di programmazione incredibilmente facile da usare (grazie a JavaScript). Ma basta? Durerà? Sono sicuro che JavaScript avrà un posto importante in futuro. Lascia che ti spieghi perché:
Programmazione Funzionale
JavaScript è stato il primo linguaggio di programmazione a portare il paradigma funzionale alle masse (ovviamente, Lisp è arrivato prima, ma la maggior parte dei programmatori non ha mai creato un'applicazione pronta per la produzione utilizzando Lisp). Lisp e Self, le principali influenze di Javascript, sono piene di idee innovative. Queste idee potrebbero liberare le nostre menti per esplorare nuove tecniche, modelli e paradigmi. E tutti vengono trasferiti in JavaScript. Dai un'occhiata alle monadi, ai numeri della Chiesa o anche (per un esempio più pratico) alle funzioni di raccolta di Underscore.js, che possono farti risparmiare righe e righe di codice.
Oggetti dinamici ed eredità prototipale
La programmazione orientata agli oggetti senza classi (e senza infinite gerarchie di classi) consente uno sviluppo rapido (crea oggetti, aggiungi metodi e usali) ma, soprattutto, riduce i tempi di refactoring durante le attività di manutenzione consentendo al programmatore di modificare invece istanze di oggetti di classi. Questa velocità e flessibilità aprono la strada a un rapido sviluppo.
JavaScript è Internet
JavaScript è stato progettato per Internet, è presente dall'inizio e non scomparirà. Tutti i tentativi di distruggerlo sono falliti: vedi, ad esempio, la caduta delle applet Java, la sostituzione di VBScript con TypeScript di Microsoft (che compila in JavaScript) e la scomparsa di Flash per mano del mercato mobile e di HTML5. È impossibile sostituire Javascript senza rompere milioni di pagine Web, quindi il nostro obiettivo per il futuro dovrebbe essere quello di migliorarlo. E non c'è nessuno migliore per il lavoro del Technical Committee 39 dell'ECMA.
Ok, ogni giorno nascono alternative a JavaScript, come CoffeeScript, TypeScript e i milioni di linguaggi che compilano in JavaScript. Queste alternative potrebbero essere utili per le fasi di sviluppo (tramite mappe dei sorgenti), ma a lungo termine non riusciranno a soppiantare JavaScript per due motivi: le loro comunità non saranno mai più grandi e le loro migliori caratteristiche saranno adottate dallo script ECMA (ad esempio, JavaScript ). JavaScript non è un linguaggio assembly: è un linguaggio di programmazione di alto livello con codice sorgente che puoi capire, quindi dovresti capirlo.
JavaScript end-to-end: Node.js e MongoDB
Quindi, questi sono i motivi per usare JavaScript. Ora userò JavaScript come motivo per usare Node.js e MongoDB.
Node.js
Node.js è una piattaforma per la creazione di applicazioni di rete veloci e scalabili, questo è più o meno ciò che dice il sito Node.js. Ma Node.js è più di questo: è l'ambiente di runtime preferito per qualsiasi applicazione JavaScript con accesso I/O. Anche se non prevedi di scrivere la tua applicazione del server principale con Node.js, puoi utilizzare strumenti basati su Node.js per migliorare il tuo processo di sviluppo. Ad esempio: Mocha.js per unit test, Grunt.js per attività di compilazione automatizzate o anche Brackets per la modifica del codice full-text.
Quindi, se hai intenzione di scrivere applicazioni JavaScript per il server o il client, dovresti dare un'occhiata ad alcuni esempi di Node.js, perché ne avrai bisogno e lo utilizzerai quotidianamente. Ci sono alcune alternative interessanti, ma nessuna è nemmeno al 10% della comunità di Node.js.
MongoDB
MongoDB è un database basato su documenti NoSQL che utilizza JavaScript come linguaggio di query, consentendomi di completare la piattaforma JavaScript end-to-end. Ma questo non è nemmeno il motivo principale per scegliere questo database.
MongoDB è un database senza schema che ti consente di mantenere i tuoi oggetti in modo flessibile e quindi adattarti più velocemente ai cambiamenti dei requisiti. Inoltre, è altamente scalabile e basato sulla riduzione della mappa, il che lo rende adatto per applicazioni di big data. MongoDB è così flessibile che può essere utilizzato come database di documenti senza schema, un datastore relazionale (sebbene manchi di transazioni) o anche come archivio di valori-chiave per la memorizzazione nella cache delle risposte.
Componentizzazione del server con Express.js
La componentizzazione lato server non è mai facile. Ma con Express.js (e Connect.js) è nata l'idea di "middleware". A mio parere, il middleware è il modo migliore per definire i componenti sul server. Se vuoi confrontarlo con un modello noto, è abbastanza vicino a tubi e filtri.
L'idea di base è che il tuo componente faccia parte di una pipeline. La pipeline elabora una richiesta (input) e genera una risposta (output), ma il tuo componente non è responsabile dell'intera risposta. Al contrario, modifica solo ciò di cui ha bisogno e quindi delega al pezzo successivo della pipeline. Al termine dell'elaborazione dell'ultimo pezzo della pipeline, la risposta viene restituita al client.
Ci riferiamo a questi "pezzi della pipeline" come "middleware". Chiaramente, possiamo creare due tipi di middleware:
Intermedi : quelli che elaborano la richiesta e la risposta, ma non sono completamente responsabili della risposta stessa, quindi delegano al middleware successivo.
Finali : quelli con piena responsabilità sulla risposta finale. Elaborano e modificano la richiesta e la risposta, ma non devono delegare al middleware successivo. In pratica, si consiglia di delegare comunque a un middleware successivo per consentire la flessibilità dell'architettura (ad esempio, aggiungere più middleware in un secondo momento), anche se quel middleware non esiste (nel qual caso la risposta andrà direttamente al client).
Come esempio concreto, si consideri un componente 'user manager' sul server. In termini di middleware, avremmo sia finali che intermedi. Per le nostre finali, avremmo caratteristiche come la creazione di un utente e l'elenco degli utenti. Ma prima di poter eseguire queste azioni, abbiamo bisogno dei nostri intermedi per l'autenticazione (poiché non vogliamo che le richieste non autenticate arrivino e creino utenti). Dopo aver creato questi intermedi di autenticazione, possiamo semplicemente collegarli ovunque desideriamo trasformare una funzionalità precedentemente non autenticata in una funzionalità autenticata.
Applicazioni a pagina singola
Il progetto Init si concentra sulla creazione di applicazioni a pagina singola (SPA). La maggior parte degli sviluppatori web è stata tentata più di una volta di cimentarsi con le SPA. Ne ho costruiti diversi (per lo più proprietari) e posso dire con sicurezza che sono semplicemente il futuro delle applicazioni web. Hai mai confrontato una SPA con una normale app web su una connessione mobile? La differenza di reattività è dell'ordine di decine di secondi.
Le SPA sono il futuro del Web, quindi perché dovresti costruire il tuo prodotto in una forma legacy? Un argomento comune che sento è che le persone sono preoccupate per la SEO. Ma se gestisci le cose correttamente, questo non dovrebbe essere un problema: Google stesso ha un ottimo tutorial su come farlo e ci sono anche alcuni buoni commenti qui.
Client Side MV* con Backbone.js, Marionette.js e Twitter Bootstrap
Molto è stato detto sui framework MVC* per le SPA. È una scelta difficile, ma direi che i primi tre sono Backbone.js, Ember.js e Angular.js.
Tutti e tre sono molto ben considerati. Ma qual è il migliore per te?
Sfortunatamente, devo ammettere che ho un'esperienza molto limitata con Angular.js, quindi la lascerò fuori da questa discussione (per ulteriori informazioni, fare riferimento al tutorial di Angular.js). Ora, Ember.js e Backbone.js rappresentano due modi diversi di affrontare lo stesso problema.
Backbone.js è minimale, semplicistico e ti offre quanto basta per creare una semplice SPA. Ember.js, invece, è un framework completo e professionale per la creazione di SPA. Ha più campane e fischietti, ma anche una curva di apprendimento più ampia.
A seconda delle dimensioni della tua applicazione, la decisione può essere facile come guardare il rapporto featuresUsed/featuresAvailable, che ti darà un grande suggerimento.
Nel caso di Init, volevo coprire la maggior parte degli scenari, quindi ho scelto Backbone.js per una facile creazione di SPA, con Backbone.Marionette.View per la componentizzazione. In questo modo, ogni componente è una semplice applicazione e l'app finale può essere complessa quanto vogliamo.
Anche lo styling è una sfida, ma possiamo contare ancora una volta su strutture per salvarci. Per CSS, non c'è niente di meglio di Twitter Bootstrap, che offre un set completo di stili pronti per l'uso e facili da personalizzare.
Bootstrap è stato creato utilizzando il linguaggio LESS ed è open source, quindi possiamo modificarlo se necessario. Viene fornito con un sacco di controlli UX che sono ben documentati sul sito Bootstrap. Inoltre, c'è un modello di personalizzazione che ti consente di crearne uno tuo. È sicuramente l'uomo per il lavoro.
Migliori pratiche: Grunt.js, Mocha.js, Chai.js, RequireJS e CoverJS
Infine, dovremmo definire alcune delle nostre migliori pratiche e guardare come Init può aiutarti a implementarle e mantenerle. La nostra soluzione è incentrata su diversi strumenti, che si basano sugli stessi Node.js.
Mocha.js e Chai.js :
Questi strumenti ti consentono di migliorare il tuo processo di sviluppo applicando TDD o BDD, fornendo l'infrastruttura per organizzare i tuoi unit test e un corridore per eseguirli automaticamente.
Esistono migliaia di framework di unit test per JavaScript. Allora perché usare Mocha.js? La risposta breve: è flessibile e completo.
La risposta lunga: ha due caratteristiche importanti (interfacce, giornalisti) e un'assenza significativa (affermazioni). Lasciatemi spiegare.
Interfacce : forse sei abituato a concetti TDD di suite e unit test, o forse preferisci idee BDD di specifiche comportamentali con "descrivi" e "dovrebbe". Mocha.js ti consente di utilizzare entrambi gli approcci.
Reporter : l'esecuzione del test genererà report sui risultati e puoi formattare questi risultati utilizzando vari reporter. Ad esempio, se devi alimentare un server di integrazione continua, puoi trovare un giornalista che faccia proprio questo.
Mancanza di una libreria di asserzioni : lungi dall'essere un problema, Mocha.js è stato progettato per consentirti di utilizzare la libreria di asserzioni di tua scelta, offrendoti ancora più flessibilità. Ci sono molte opzioni, ma è qui che entra in gioco Chai.js.
Chai.js è una libreria di asserzioni flessibile che ti consente di utilizzare uno dei tre principali stili di asserzione:
Affermazione : stile classico di affermazione della vecchia scuola TDD. Per esempio:
assert.equal(variable, ”value”);
Aspettatevi : stile di asserzione concatenabile, più comunemente usato in BDD. Per esempio:
expect(variable).to.equal(“value”);
Dovrebbe : usato anche in BDD, ma preferisco Expect perché dovrebbe suonare ripetitivo con la specifica del comportamento "it ("dovrebbe fare qualcosa..")". Per esempio:
variable.should.equal(“value”);
Chai.js si combina perfettamente con Mocha.js. Usando solo queste due librerie, puoi scrivere i tuoi test in TDD, BDD o qualsiasi stile immaginabile.
Grunt.js :
Grunt.js ti consente di automatizzare le attività di compilazione, che vanno dal semplice copia-incolla e concatenazione di file, alla precompilazione di modelli, alla compilazione del linguaggio di stile (ad esempio, SASS e LESS), unit test (con mocha.js), linting e minimizzazione del codice (ad es. con UglifyJS o Closure Compiler). Puoi aggiungere la tua attività automatizzata a Grunt o cercare nel registro di Grunt, dove sono disponibili centinaia e centinaia di plugin (di nuovo, l'utilizzo di strumenti con grandi comunità dietro di loro ripaga). Grunt può anche monitorare i tuoi file e attivare azioni quando vengono modificati.
RequireJS :
RequireJS può sembrare solo un altro modo per caricare moduli con AMD, ma posso assicurarti che è molto di più. Per capire perché, dobbiamo prima menzionare l'idea del namespace dei moduli (ad es. demo.views.hello), che evita di inquinare lo spazio dei nomi globale avvolgendo ogni modulo nel proprio spazio dei nomi. Il problema è che questi moduli non sono riutilizzabili: se modifichi lo spazio dei nomi di una "istanza", stai modificando lo spazio dei nomi di tutte le "istanze". Al contrario, RequireJS ti consente di definire moduli riutilizzabili fin dall'inizio. (Inoltre, ti aiuterà ad abbracciare l'inserimento delle dipendenze per evitare che i tuoi moduli accedano a variabili globali.)
CoverJS :
La copertura del codice è una metrica per valutare i test. Come suggerisce il nome, ti dice quanto del tuo codice è coperto dalla tua attuale suite di test. CoverJS misura la copertura del codice dei tuoi test strumentando istruzioni (invece di righe di codice come JSCoverage) nel tuo codice e generando una versione strumentata del tuo codice. Può anche generare report per alimentare il tuo server di integrazione continua.
Utilizzo dei rami per attivare/disattivare le funzioni
Quando ho avviato Init, avevo bisogno di un modo per consentire agli utenti di attivare e disattivare varie funzionalità che potrebbero desiderare nel loro progetto. Ho deciso di adottare un approccio radicale al sistema di branch di git per implementare questa funzionalità.
In sostanza, ogni ramo rappresenta una caratteristica o funzionalità che un utente potrebbe voler includere. Se stai iniziando un progetto da zero, inizia dal ramo minimo di cui avresti bisogno, quindi aggiungi altre tecnologie unendole con i rami desiderati. Ad esempio, supponiamo che tu voglia iniziare il tuo progetto con Backbone.js e Marionette.js. Bene, puoi iniziare sul ramo Backbone.js e unirlo al ramo Marionette, continuando per ogni funzionalità che desideri aggiungere.
Per ora, questa idea di unione per aggiungere funzionalità può essere utilizzata solo per i modelli tecnologici (ad esempio, Backbone, Node, Express). Ma in futuro sarai in grado di passare dalle implementazioni di back-end (ad es. da MongoDB a Postgres) e client.
Avvia un progetto con Init e distribuiscilo a Heroku oggi
Non c'è mai stato un modo più semplice per avviare un progetto. Vai al repository GitHub, controlla il ramo con gli ultimi commit (in questo momento è usermanager, anche se questo potrebbe cambiare in futuro) e poi:
- Crea la directory per il tuo progetto (o usane una esistente).
- Crea il tuo repository con "git init" (o usa il repository esistente).
Aggiungi un telecomando con init
git remote add init git://github.com/picanteverde/init.git
Prendi la filiale che desideri
git pull init usermanager
Ottieni il file di processo di Heroku
git pull init heroku-webprocess
Con Heroku Toolbelt installato, crea un'app Heroku
heroku create
Spingi il tuo ramo principale su Heroku
git push heroku master
- Visita la tua app, attiva e funzionante su Heroku!
Ora puoi iniziare a sviluppare la tua funzione killer con poche righe di codice. Non solo, svilupperai con le tecnologie più recenti ed efficienti in una suite di sviluppo il più automatizzata possibile.
Spero che tu possa usare Init per dare il via alla tua prossima grande idea. Ricordati di controllare il repository Init per nuove correzioni e funzionalità: il suo sviluppo è molto attivo e non vedo l'ora di sentire il tuo feedback.