OK, minęło trochę czasu i jest to popularne pytanie, więc stworzyłem repozytorium github rusztowania z kodem JavaScript i długim README o tym, jak lubię tworzyć struktury średniej aplikacji express.js.
focusaurus / express_code_structure to repozytorium z najnowszym kodem do tego. Witamy w zaproszeniach do ściągania.
Oto migawka README, ponieważ przepełnienie stosu nie lubi odpowiedzi typu „tylko łącze”. Zrobię kilka aktualizacji, ponieważ jest to nowy projekt, który będę nadal aktualizować, ale ostatecznie repozytorium github będzie aktualnym miejscem dla tych informacji.
Struktura ekspresowego kodu
Ten projekt jest przykładem organizacji średniej aplikacji sieci web express.js.
Aktualne co najmniej ekspresowe v4.14 grudnia 2016 r
Jak duża jest twoja aplikacja?
Aplikacje internetowe nie są takie same i moim zdaniem nie ma jednej struktury kodu, którą należy zastosować do wszystkich aplikacji express.js.
Jeśli twoja aplikacja jest niewielka, nie potrzebujesz tak głębokiej struktury katalogów, jak pokazano tutaj. Po prostu zachowaj prostotę i wrzuć garść .js
plików do katalogu głównego repozytorium i gotowe. Voilà.
Jeśli twoja aplikacja jest ogromna, w pewnym momencie musisz podzielić ją na odrębne pakiety npm. Zasadniczo podejście node.js wydaje się faworyzować wiele małych pakietów, przynajmniej dla bibliotek, i powinieneś zbudować swoją aplikację, używając kilku pakietów npm, ponieważ zaczyna to mieć sens i uzasadniać narzut. Tak więc, gdy Twoja aplikacja rośnie, a część kodu staje się jednoznacznie wielokrotnego użytku poza aplikacją lub stanowi przejrzysty podsystem, przenieś ją do własnego repozytorium git i przekształć w samodzielny pakiet npm.
Dlatego celem tego projektu jest zilustrowanie praktycznej struktury dla średnich aplikacji.
Jaka jest twoja ogólna architektura
Istnieje wiele podejść do tworzenia aplikacji internetowych, takich jak
- Po stronie serwera MVC a la Ruby on Rails
- Styl aplikacji jednostronicowej a la MongoDB / Express / Angular / Node (MEAN)
- Podstawowa strona internetowa z niektórymi formularzami
- Modele / Operacje / Widoki / Wydarzenia a la MVC nie żyje, czas na MOVE
- i wiele innych, zarówno obecnych, jak i historycznych
Każdy z nich ładnie pasuje do innej struktury katalogów. Na potrzeby tego przykładu jest to tylko rusztowanie, a nie w pełni działająca aplikacja, ale zakładam następujące kluczowe punkty architektury:
- Witryna ma kilka tradycyjnych statycznych stron / szablonów
- Część witryny dotycząca aplikacji została opracowana jako styl aplikacji pojedynczej strony
- Aplikacja wyświetla interfejs API stylu REST / JSON w przeglądarce
- Aplikacja modeluje prostą domenę biznesową, w tym przypadku jest to aplikacja dealera samochodowego
A co z Ruby on Rails?
Tematem całego projektu będzie to, że wiele pomysłów zawartych w Ruby on Rails i przyjęte przez nich decyzje „Konwencja w sprawie konfiguracji”, choć powszechnie akceptowane i stosowane, nie są w rzeczywistości bardzo pomocne i czasami są przeciwieństwem tego, co to repozytorium poleca.
Chodzi mi przede wszystkim o to, że istnieją podstawowe zasady organizowania kodu i na podstawie tych zasad konwencje Ruby on Rails mają sens (głównie) dla społeczności Ruby on Rails. Jednak bezmyślne aportowanie tych konwencji mija się z celem. Po zapoznaniu się z podstawowymi zasadami WSZYSTKIE projekty będą dobrze zorganizowane i przejrzyste: skrypty powłoki, gry, aplikacje mobilne, projekty korporacyjne, a nawet katalog domowy.
Społeczność Railsów chce mieć możliwość przełączenia jednego dewelopera Railsów z aplikacji na aplikację i za każdym razem dobrze się z tym czuje. Ma to sens, jeśli masz 37 sygnałów lub Pivotal Labs i ma swoje zalety. W świecie JavaScript po stronie serwera ogólny etos jest o wiele bardziej dziki i zachodni, a tak naprawdę nie mamy z tym problemu. Tak się toczymy. Jesteśmy do tego przyzwyczajeni. Nawet w Express.js, jest to bliski krew Sinatry, a nie Railsów, a przyjmowanie konwencji z Railsów zwykle nic nie pomaga. Powiedziałbym nawet, że zasady są ważniejsze niż konwencja .
Podstawowe zasady i motywacje
- Bądź zarządzalny psychicznie
- Mózg może poradzić sobie i pomyśleć o niewielkiej liczbie powiązanych rzeczy na raz. Dlatego korzystamy z katalogów. Pomaga nam radzić sobie ze złożonością, koncentrując się na małych porcjach.
- Dopasuj rozmiar
- Nie twórz „katalogów rezydencji”, w których jest tylko 1 plik sam, 3 katalogi w dół. Możesz to zobaczyć w Ansible Best Practices, która zawstydza małe projekty do tworzenia ponad 10 katalogów do przechowywania ponad 10 plików, gdy 1 katalog z 3 plikami byłby o wiele bardziej odpowiedni. Nie jeździsz autobusem do pracy (chyba że jesteś kierowcą autobusu, ale nawet wtedy, gdy jedziesz autobusem AT nie działa TO), więc nie twórz struktur systemu plików, które nie są uzasadnione rzeczywistymi plikami w nich zawartymi .
- Bądź modułowy, ale pragmatyczny
- Społeczność węzłów preferuje małe moduły. Wszystko, co można całkowicie oddzielić od aplikacji, należy wyodrębnić do modułu do użytku wewnętrznego lub opublikować publicznie na npm. Jednak w przypadku średnich aplikacji, które są tutaj objęte, narzut ten może dodać nudę do przepływu pracy bez współmiernej wartości. Tak więc na czas, gdy masz jakiś wyodrębniony kod, ale niewystarczający, aby uzasadnić całkowicie oddzielny moduł npm, po prostu uważaj go za „ proto-moduł ” z oczekiwaniem, że gdy przekroczy on pewien próg wielkości, zostanie wyodrębniony.
- Niektóre osoby, takie jak @ hij1nx, nawet zawierają
app/node_modules
katalog i mają package.json
pliki w katalogach modułów, aby ułatwić to przejście i działają jako przypomnienie.
- Łatwo zlokalizować kod
- Biorąc pod uwagę funkcję do zbudowania lub błąd do naprawienia, naszym celem jest, aby programista nie miał trudności z lokalizacją zaangażowanych plików źródłowych.
- Nazwy są znaczące i dokładne
- kod crufty jest całkowicie usunięty, nie pozostawia się go w pliku osieroconym lub po prostu komentuje
- Bądź przyjazny dla wyszukiwania
- cały własny kod źródłowy znajduje się w
app
katalogu, dzięki czemu można cd
uruchomić find / grep / xargs / ag / ack / etc i nie rozpraszać się dopasowaniami innych firm
- Używaj prostych i oczywistych nazw
- Npm wydaje się teraz wymagać, aby nazwy pakietów zawierały małe litery. Uważam to za najbardziej okropne, ale muszę śledzić stado, dlatego nazwy plików powinny być używane,
kebab-case
mimo że nazwa zmiennej w JavaScript musi być taka, camelCase
ponieważ-
jest znakiem minus w JavaScript.
- nazwa zmiennej pasuje do nazwy bazowej ścieżki modułu, ale z
kebab-case
transformacją nacamelCase
- Grupuj według sprzężenia, a nie według funkcji
- To jest głównym odejście od Ruby on Rails konwencji
app/views
, app/controllers
, app/models
, etc
- Funkcje są dodawane do pełnego stosu, więc chcę skupić się na pełnym stosie plików, które są odpowiednie dla mojej funkcji. Kiedy dodam pole numeru telefonu do modelu użytkownika, nie dbam o żaden kontroler inny niż kontroler użytkownika i nie dbam o żaden inny model niż model użytkownika.
- Zamiast więc edytować 6 plików, z których każdy znajduje się w swoim własnym katalogu i ignorować mnóstwo innych plików w tych katalogach, to repozytorium jest zorganizowane w taki sposób, że wszystkie pliki potrzebne do zbudowania funkcji są kolokowane
- Z natury MVC widok użytkownika jest sprzężony z kontrolerem użytkownika, który jest połączony z modelem użytkownika. Kiedy więc zmieniam model użytkownika, te 3 pliki często się zmieniają razem, ale kontroler transakcji lub kontroler klienta są oddzielone i dlatego nie są zaangażowane. To samo dotyczy zwykle projektów innych niż MVC.
- Oddzielenie stylu MVC lub MOVE pod względem tego, który kod wchodzi w który moduł jest nadal wspierany, ale rozkładanie plików MVC do katalogów rodzeństwa jest po prostu denerwujące.
- Tak więc każdy z moich plików tras ma część tras, którą posiada. Plik w stylu szyn
routes.rb
jest przydatny, jeśli chcesz przeglądać wszystkie trasy w aplikacji, ale podczas faktycznego budowania funkcji i naprawiania błędów dbasz tylko o trasy odpowiednie dla zmienianego elementu.
- Przechowuj testy obok kodu
- To tylko przykład „grupuj przez sprzężenie”, ale chciałem to konkretnie nazwać. Napisałem wiele projektów, w których testy działają w równoległym systemie plików zwanym „testami”, a teraz, kiedy zacząłem umieszczać swoje testy w tym samym katalogu co odpowiadający im kod, nigdy nie wracam. Jest to bardziej modułowe i znacznie łatwiejsze do pracy w edytorach tekstowych i łagodzi wiele nonsensów ścieżki „../../ ..”. W razie wątpliwości wypróbuj kilka projektów i sam zdecyduj. Nie zamierzam robić nic poza tym, aby cię przekonać, że jest lepiej.
- Zmniejsz sprzężenie poprzeczne dzięki Eventom
- Łatwo jest pomyśleć „OK, za każdym razem, gdy tworzona jest nowa Umowa, chcę wysłać wiadomość e-mail do wszystkich sprzedawców”, a następnie po prostu umieścić kod, aby wysłać te wiadomości e-mail na trasę, która tworzy oferty.
- Jednak to połączenie ostatecznie przekształci Twoją aplikację w gigantyczną kulę błota.
- Zamiast tego DealModel powinien po prostu wywołać zdarzenie „create” i być całkowicie nieświadomy tego, co jeszcze system może zrobić w odpowiedzi na to.
- Kiedy kodujesz w ten sposób, znacznie łatwiej jest umieścić cały kod związany z użytkownikiem,
app/users
ponieważ nie ma gniazda szczurów połączonej logiki biznesowej w całym miejscu, która zanieczyszczałaby czystość bazy kodu użytkownika.
- Przepływ kodu jest możliwy
- Nie rób magicznych rzeczy. Nie ładuj automatycznie plików z magicznych katalogów w systemie plików. Nie bądźcie szynami. Aplikacja uruchamia się o godzinie
app/server.js:1
i możesz zobaczyć wszystko, co ładuje i wykonuje, postępując zgodnie z kodem.
- Nie twórz DSL dla swoich tras. Nie rób głupiego metaprogramowania, gdy nie jest wymagane.
- Jeśli aplikacja jest tak duża, że robi
magicRESTRouter.route(somecontroller, {except: 'POST'})
to dla ciebie wielka wygrana na 3 podstawowe app.get
, app.put
, app.del
, rozmowy, jesteś prawdopodobnie budowę monolityczną aplikację, która jest zbyt duża, aby efektywnie pracować. Przygotuj się na DUŻE zwycięstwa, a nie na zamianę 3 prostych linii na 1 linię złożoną.
Używaj nazw plików zawierających małe litery kebab
- Ten format pozwala uniknąć problemów z rozróżnianiem wielkości liter w systemie plików na różnych platformach
- npm zabrania pisania dużymi literami w nazwach nowych pakietów, i to dobrze z tym działa
specyfikacja express.js
Nie używać app.configure
. Jest prawie całkowicie bezużyteczny i po prostu go nie potrzebujesz. Jest w wielu płytkach kuchennych z powodu bezmyślnego copypasta.
- ZAMÓWIENIE ŚRODKOWEGO OPROGRAMOWANIA I DROGI W SPRAWACH EKSPRESOWYCH !!!
- Niemal każdy problem z routingiem, jaki widzę podczas przepełnienia stosu, to ekspresowe oprogramowanie pośrednie poza kolejnością
- Ogólnie rzecz biorąc, chcesz, aby Twoje trasy były rozdzielone i nie polegały na tak dużym porządku
- Nie używaj
app.use
do całej aplikacji, jeśli naprawdę potrzebujesz tego oprogramowania pośredniego tylko na 2 trasy (patrzę na ciebie, body-parser
)
- Upewnij się, że kiedy wszystko zostanie powiedziane i zrobione, masz DOKŁADNIE następujące zamówienie:
- Wszelkie bardzo ważne oprogramowanie pośrednie dla całej aplikacji
- Wszystkie Twoje trasy i różne pośrednie trasy
- TO programy obsługi błędów
- Niestety, będąc inspirowanym sinatrą, express.js głównie zakłada, że wszystkie twoje trasy będą na miejscu
server.js
i będzie jasne, w jaki sposób są one uporządkowane. W przypadku aplikacji średniej wielkości rozdzielanie modułów na osobne trasy jest przyjemne, ale wprowadza niebezpieczeństwo niedziałającego oprogramowania pośredniego
Aplikacja dowiązanie symboliczne
Istnieje wiele podejść przedstawione i omówione obszernie przez społeczność w wielkiej GIST Lepszy lokalny require () ścieżki dla node.js . Mogę wkrótce zdecydować, że wolę „po prostu poradzić sobie z dużą ilością ../../../ ..” lub skorzystać z modlue requFrom. Jednak w tej chwili stosuję sztuczkę z dowiązaniem symbolicznym opisaną poniżej.
Tak więc jednym ze sposobów uniknięcia irytujących ścieżek względnych require("../../../config")
jest użycie następującej sztuczki:
- utwórz dowiązanie symboliczne w module node_modules dla swojej aplikacji
- cd node_modules && ln -nsf ../app
- dodaj do git tylko samo dowiązanie symboliczne node_modules / app , a nie cały folder node_modules
- git dodaj -f moduły_węzła / aplikację
- Tak, nadal powinieneś mieć „node_modules” w swoim
.gitignore
pliku
- Nie, nie powinieneś umieszczać „node_modules” w swoim repozytorium git. Niektóre osoby zalecają to zrobić. Są nieprawidłowe.
- Teraz możesz wymagać modułów wewnętrznych za pomocą tego prefiksu
var config = require("app/config");
var DealModel = require("app/deals/deal-model")
;
- Zasadniczo sprawia to, że wewnątrz projektu wymaga pracy bardzo podobnie do wymagań dla zewnętrznych modułów npm.
- Niestety, użytkownicy systemu Windows muszą trzymać się ścieżek względnych katalogu nadrzędnego.
Konfiguracja
Generalnie koduj moduły i klasy, aby oczekiwać, że tylko podstawowy options
obiekt JavaScript zostanie przekazany. Tylko moduł app/server.js
powinien zostać załadowany app/config.js
. Stamtąd może syntezować małe options
obiekty w celu konfigurowania podsystemów w razie potrzeby, ale łączenie każdego podsystemu z dużym globalnym modułem konfiguracji pełnym dodatkowych informacji jest złym sprzężeniem.
Spróbuj scentralizować tworzenie połączeń DB i przekazywać je do podsystemów, w przeciwieństwie do przekazywania parametrów połączeń i samodzielnego wykonywania połączeń wychodzących przez podsystemy.
NODE_ENV
To kolejny kuszący, ale okropny pomysł przeniesiony z Rails. W aplikacji powinno znajdować się dokładnie 1 miejsce, app/config.js
które wygląda na NODE_ENV
zmienną środowiskową. Cała reszta powinna przyjąć jawną opcję jako argument konstruktora klasy lub parametr konfiguracyjny modułu.
Jeśli moduł e-mail ma opcję dostarczania wiadomości e-mail (SMTP, logowanie do standardowego wejścia, umieszczanie w kolejce itp.), Powinien wybrać taką opcję, {deliver: 'stdout'}
ale absolutnie nie powinien tego sprawdzać NODE_ENV
.
Testy
Teraz trzymam moje pliki testowe w tym samym katalogu co odpowiadający im kod i używam konwencji nazewnictwa rozszerzeń nazw plików, aby odróżnić testy od kodu produkcyjnego.
foo.js
ma kod modułu „foo”
foo.tape.js
ma testy węzłów dla foo i mieszka w tym samym katalogu
foo.btape.js
może być używany do testów, które należy wykonać w środowisku przeglądarki
Używam globów systemu plików i find . -name '*.tape.js'
polecenia, aby uzyskać dostęp do wszystkich moich testów w razie potrzeby.
Jak zorganizować kod w każdym .js
pliku modułu
Zakres tego projektu dotyczy głównie tego, gdzie idą pliki i katalogi, i nie chcę dodawać wiele innych zakresów, ale wspomnę tylko, że mój kod dzielę na 3 odrębne sekcje.
- Blok otwierający CommonJS wymaga wywołań zależności stanu
- Główny blok kodu czystego JavaScript. Nie ma tutaj zanieczyszczenia CommonJS. Nie odwołuj się do eksportów, modułów ani wymagań.
- Blok zamykający CommonJS w celu skonfigurowania eksportu