2013-03-24 9 views
5

Su http://mindbat.com/2013/03/clojurewest-2013-day-one-notes/ c'è una nota che recita:Come sbarazzarsi dei dati globali in un'applicazione Compojure

  • def'ing arbitri e gli atomi al livello più alto è fondamentalmente stato mutevole globale tramite single, si prega di evitare
  • consiglia di utilizzare funzioni di costruzione per ritorno le variabili di stato che si desidera utilizzare, quindi passare quello stato insieme a ciascuna funzione

penso che questo è un buon consiglio, ma io non sono del tutto sicuro di come implementalo in un'applicazione Ring/Compojure. Qualcuno può dare un esempio concreto di come funzionerebbe?

Sono particolarmente interessato a come combinare defroutes, init e app insieme in questo modo e sbarazzarsi di variabili globali in tale ambito.

risposta

2

Ci sono molti casi in cui è necessario stato globale quindi non si può evitare, quello che si può fare è gestire correttamente e credo che è quello che i 2 punti parlano:

Non

un buon modo:

(ns data) 
(def users (atom [])) 

(ns pages) 
(defn home [] 
    (do-something data/@users) 

(defn save [] 
    (let [u @users] 
     (swap! data/users ....) 

Buon modo:

(ns data) 
(def- users (atom [])) 
(defn get-users [] @users) 
(defn update-user [user] (swap! @users ...)) 

(ns pages) 
; use the functions exposed by data ns to interact with data rather then poking the atom directly. 

Fondamentalmente tutti l'accesso a qualsiasi tipo di stato dovrebbe essere astratto lontano da altri parti dell'applicazione Tutta la tua funzione di business logic dovrebbe assumere lo stato come parametro e restituire un nuovo stato piuttosto che selezionare lo stato e aggiornarlo direttamente.

+0

y! è un buon approccio. – hsestupin

+0

@hsestupin: è una domanda o affermazione :) – Ankur

+0

affermazione di c. Intendevo dire che ero totalmente d'accordo con quel tipo di approccio quando lo stato non poteva essere cambiato per riferimento diretto. – hsestupin

5

Quello che ho capito dal discorso di Stuart è qualcosa di simile:

(ns state.core) 

(defn create-user-module [] (atom [])) 

(defn add-user [module user] 
    (swap! module conj user)) 

(defn get-users [module] 
    @module) 

Ora non c'è stato globale nel vostro "core", come le funzioni che manipolano lo Stato si aspettano di ottenere come parametro. Ciò consente un facile test in quanto è possibile creare una nuova istanza del "modulo utente" per ogni test. Inoltre, i client di questo modulo non dovrebbero preoccuparsi di ciò che ottengono nella funzione create-user-module, dovrebbero semplicemente passarlo in giro senza controllarlo, in questo modo è possibile modificare l'implementazione del modulo utente ogni volta che lo si desidera. Stuart parla anche della creazione di protocolli per quei moduli se avete intenzione di avere più di un'implementazione.

Cercando di rispondere alla tua domanda, un anello adattatore è solo una funzione di 1 param, e compojure è solo una libreria di routing, così si potrebbe creare una web-app utilizzando chiusure come:

(ns state.web 
    (:use compojure.core) 
    (:require [state.core :as core])) 

(defn web-module [user-module] 
    (routes 
    (GET "/all" [] (core/get-users user-module)))) 

Ora può chiamare il modulo web per creare una webapp, passando come parametro le dipendenze necessarie. Naturalmente è ancora bisogno di qualcuno per creare la web app con l'utente-moduli corretti, in modo che solo bisogno di una funzione "principale" che fili tutto insieme:

(ns state.main 
    (:require state.core 
      state.web) 
    (:use ring.adapter.jetty)) 

(defn start [] 
    (let [user-module (state.core/create-user-module) 
     web-module (state.web/web-module user-module)] 
    (run-jetty web-module {:port 3000 :join? false}))) 

(defn stop [app] 
    (.stop app)) 

start saranno chiamati dal metodo di applicazione main. Questo vuol dire che devi passare al plugin lein-run.

Ora, dato che stai chiedendo di init (dal plugin lein ring presumo), suppongo che tu abbia intenzione di distribuire la tua webapp in un contenitore.Siccome il plugin anello Lein deve lavorare all'interno dei vincoli del Java Servlet fw e che il gestore finisce compilato per un servlet Java, il meglio che si può probabilmente fare è qualcosa di simile:

(ns state.web 
    (:use compojure.core) 
    (:require [state.core :as core])) 

(def module-deps (atom {}) 

(defn init-app [] (swap! module-deps conj [:user-module (core/create-user-module)])) 

(defroutes web-module [] 
    (GET "/all" [] (core/get-users (:user-module @module-deps)))) 

Ciò significa ancora che il vostro core lo spazio dei nomi è facile da testare, ma hai ancora uno stato globale nel namespace web, ma penso che sia "correttamente" incapsulato e probabilmente è abbastanza buono se devi usare un contenitore java.

E questo è solo un altro argomento del motivo per cui le librerie sono "migliori" dei quadri :)