2013-03-24 14 views
5

Na http://mindbat.com/2013/03/clojurewest-2013-day-one-notes/ znajduje się uwaga, że ​​brzmi:Jak pozbyć się danych globalnych w aplikacji Compojure

  • def'ing bibl i węgla na najwyższym poziomie jest w zasadzie globalny stan zmienny poprzez singletons należy unikać
  • zalecamy używania funkcji konstruktora zamian zmiennych stanu, których chcesz użyć, a następnie przekazać ten stan wzdłuż każdej funkcji

Myślę, że to dobra rada, ale nie jestem całkowicie pewien, jak zaimplementuj to w aplikacji Ring/Compojure. Czy ktokolwiek może podać konkretny przykład tego, jak to zadziała?

Jestem szczególnie zainteresowany tym, jak połączyć defroutes, init i app razem w ten sposób i pozbyć się zmiennych globalnych w tym zakresie.

Odpowiedz

2

Istnieje wiele przypadków, gdzie trzeba stan globalnej stąd nie można go uniknąć, co można zrobić, to zarządzać go poprawnie i myślę, że to, co 2 punkty mówić o:

Nie jest to dobry sposób:

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

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

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

dobry sposób:

(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. 

Zasadniczo cały dostęp do wszelkiego rodzaju państwa powinna być oderwana od innych części aplikacji. Cała twoja funkcja logiki biznesowej powinna przyjmować stan jako parametr i zwracać nowy stan zamiast wybierać sam stan i aktualizować go bezpośrednio.

+0

y! to dobre podejście. – hsestupin

+0

@ hsestupin: Czy to jest pytanie LUB afirmacja :) – Ankur

+0

Afirmacja ofc. Chodzi mi o to, że całkowicie zgadzam się z takim podejściem, kiedy nie można zmienić stanu przez bezpośrednie odniesienie. – hsestupin

5

Co rozumiem z rozmowy Stuart jest coś takiego:

(ns state.core) 

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

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

(defn get-users [module] 
    @module) 

Teraz nie ma globalny stan w „core” jako funkcji, które manipulują stan spodziewać się go jako parametr. Umożliwia to łatwe testowanie, ponieważ można utworzyć nowe wystąpienie "modułu użytkownika" dla każdego testu. Ponadto klienci tego modułu nie powinni przejmować się tym, co otrzymują w funkcji create-user-module, powinni po prostu je przekazać bez sprawdzania go, w ten sposób można zmienić implementację modułu użytkownika w dowolnym momencie. Stuart mówi również o tworzeniu protokołów dla tych modułów, jeśli zamierzasz wdrożyć więcej niż jedną implementację.

Próbując odpowiedzieć na to pytanie, adapter pierścień jest tylko funkcją 1 param i compojure tylko biblioteki routingu, więc można utworzyć internetową aplikację za pomocą zamknięć, takich jak:

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

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

teraz ty może wywołać moduł webowy w celu utworzenia aplikacji webapp, przekazując jako parametr wymagane zależności. Oczywiście trzeba jeszcze kogoś do tworzenia aplikacji internetowych z prawidłowymi użytkowników modułów, więc wystarczy do „main” funkcję przewody wszystko razem:

(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 będzie wywoływana z metody app main. Oznacza to tylko, że musisz przejść do wtyczki lein-run.

Teraz, biorąc pod uwagę, że pytasz o init (z wtyczki Lein Ring, zakładam), myślę, że planujesz wdrożyć swoją aplikację webapp w pojemniku.Jako plugin pierścień Lein musi pracować w ramach ograniczeń Aplet java fw, a osoba zajmująca kończy się skompilowany do apletu java, najlepsze, co prawdopodobnie można zrobić coś takiego jak:

(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)))) 

to nadal oznacza, że ​​rdzeń przestrzeń nazw jest łatwa do przetestowania, ale wciąż masz globalny stan w przestrzeni nazw, ale myślę, że jest "właściwie" obudowana i prawdopodobnie jest wystarczająco dobra, jeśli musisz użyć kontenera java.

A to tylko kolejny argument, dlaczego biblioteki są "lepsze" od frameworków :)

Powiązane problemy