OK, cela fait un moment et c'est une question populaire, alors j'ai continué et créé un référentiel github d'échafaudage avec du code JavaScript et un long README sur la façon dont j'aime structurer une application express.js de taille moyenne.
focusaurus / express_code_structure est le dépôt avec le dernier code pour cela. Les demandes de tirage sont les bienvenues.
Voici un instantané du fichier README puisque stackoverflow n'aime pas les réponses d'un simple lien. Je ferai quelques mises à jour car c'est un nouveau projet que je continuerai à mettre à jour, mais finalement le repo github sera le lieu à jour pour cette information.
Structure de code express
Ce projet est un exemple de la façon d'organiser une application Web express.js de taille moyenne.
Actuel au moins express v4.14 décembre 2016
Quelle est la taille de votre application?
Les applications Web ne sont pas toutes les mêmes, et il n'y a pas, à mon avis, une structure de code unique qui devrait être appliquée à toutes les applications express.js.
Si votre application est petite, vous n'avez pas besoin d'une structure de répertoire aussi profonde que celle illustrée ici. Restez simple et collez une poignée de .js
fichiers à la racine de votre référentiel et vous avez terminé. Voilà.
Si votre application est énorme, vous devez à un moment donné la diviser en packages npm distincts. En général, l'approche node.js semble favoriser de nombreux petits packages, au moins pour les bibliothèques, et vous devez créer votre application en utilisant plusieurs packages npm car cela commence à avoir un sens et à justifier la surcharge. Donc, à mesure que votre application se développe et qu'une partie du code devient clairement réutilisable en dehors de votre application ou est un sous-système clair, déplacez-le vers son propre référentiel git et transformez-le en un package npm autonome.
L' objectif de ce projet est donc d'illustrer une structure viable pour une application de taille moyenne.
Quelle est votre architecture globale
Il existe de nombreuses approches pour créer une application Web, telles que
- Côté serveur MVC à la Ruby on Rails
- Style d'application de page unique à la MongoDB / Express / Angular / Node (MEAN)
- Site Web de base avec quelques formulaires
- Modèles / Opérations / Vues / Événements, le style à la MVC est mort, il est temps de bouger
- et bien d'autres à la fois actuels et historiques
Chacun d'entre eux s'intègre parfaitement dans une structure de répertoires différente. Pour les besoins de cet exemple, il s'agit simplement d'un échafaudage et non d'une application pleinement fonctionnelle, mais j'assume les points d'architecture clés suivants:
- Le site a des pages / modèles statiques traditionnels
- La partie "application" du site est développée comme un style d'application à page unique
- L'application expose une API de style REST / JSON au navigateur
- L'application modélise un domaine commercial simple, dans ce cas, c'est une application de concessionnaire automobile
Et qu'en est-il de Ruby on Rails?
Ce sera un thème tout au long de ce projet que de nombreuses idées incarnées dans Ruby on Rails et les décisions "Convention sur la configuration" qu'ils ont adoptées, bien qu'elles soient largement acceptées et utilisées, ne sont en fait pas très utiles et sont parfois à l'opposé de ce que ce référentiel recommande.
Mon point principal ici est qu'il existe des principes sous-jacents à l'organisation du code, et sur la base de ces principes, les conventions Ruby on Rails ont un sens (principalement) pour la communauté Ruby on Rails. Cependant, le simple fait de réfléchir inconsciemment à ces conventions passe à côté. Une fois que vous aurez maîtrisé les principes de base, TOUS vos projets seront bien organisés et clairs: scripts shell, jeux, applications mobiles, projets d'entreprise, même votre répertoire personnel.
Pour la communauté Rails, ils veulent pouvoir faire passer un seul développeur Rails d'une application à une autre et être familier et à l'aise avec chaque fois. Cela a beaucoup de sens si vous êtes 37 signaux ou Pivotal Labs, et présente des avantages. Dans le monde JavaScript côté serveur, la philosophie globale est bien plus farfelue, et nous n'avons pas vraiment de problème avec cela. Voilà comment nous roulons. Nous y sommes habitués. Même dans express.js, c'est un proche parent de Sinatra, pas Rails, et prendre des conventions de Rails n'aide généralement rien. Je dirais même principes sur convention sur configuration .
Principes et motivations sous-jacents
- Soyez gérable mentalement
- Le cerveau ne peut traiter et penser qu'à un petit nombre de choses liées à la fois. C'est pourquoi nous utilisons des répertoires. Il nous aide à gérer la complexité en nous concentrant sur de petites portions.
- Soyez approprié à la taille
- Ne créez pas de «répertoires de manoirs» où il n'y a qu'un seul fichier, 3 répertoires en moins. Vous pouvez voir cela se produire dans les meilleures pratiques Ansible qui fait honte aux petits projets en créant plus de 10 répertoires pour contenir plus de 10 fichiers alors qu'un répertoire avec 3 fichiers serait beaucoup plus approprié. Vous ne conduisez pas un bus pour travailler (sauf si vous êtes un conducteur de bus, mais même si vous conduisez un bus AT ne fonctionne PAS), alors ne créez pas de structures de système de fichiers qui ne sont pas justifiées par les fichiers réels à l'intérieur .
- Soyez modulaire mais pragmatique
- La communauté de nœuds privilégie globalement les petits modules. Tout ce qui peut être complètement séparé de votre application doit être extrait dans un module pour un usage interne ou publié publiquement sur npm. Cependant, pour les applications de taille moyenne qui sont concernées ici, le surcoût peut ajouter de l'ennui à votre flux de travail sans valeur proportionnelle. Donc, pour le moment où vous avez du code qui est factorisé mais pas suffisant pour justifier un module npm complètement séparé, considérez-le simplement comme un " proto-module " avec l'espoir que quand il franchit un certain seuil de taille, il sera extrait.
- Certaines personnes comme @ hij1nx incluent même un
app/node_modules
répertoire et ont des package.json
fichiers dans les répertoires du proto-module pour faciliter cette transition et servir de rappel.
- Soyez facile à localiser le code
- Étant donné une fonctionnalité à créer ou un bogue à corriger, notre objectif est qu'un développeur n'ait aucune difficulté à localiser les fichiers source impliqués.
- Les noms sont significatifs et précis
- le code crufty est entièrement supprimé, pas laissé dans un fichier orphelin ou simplement commenté
- Soyez facile à rechercher
- tout le code source d'origine est dans le
app
répertoire, vous pouvez donc cd
y exécuter run / grep / xargs / ag / ack / etc et ne pas être distrait par des correspondances tierces
- Utilisez un nom simple et évident
- npm semble désormais exiger des noms de packages tout en minuscules. Je trouve cela surtout terrible mais je dois suivre le troupeau, donc les noms de fichiers doivent utiliser
kebab-case
même si le nom de variable pour cela en JavaScript doit être camelCase
parce que -
c'est un signe moins en JavaScript.
- le nom de la variable correspond au nom de base du chemin du module, mais avec
kebab-case
transformé encamelCase
- Grouper par couplage, pas par fonction
- Ceci est un changement majeur de la convention Ruby on Rails
app/views
, app/controllers
, app/models
, etc.
- Les fonctionnalités sont ajoutées à une pile complète, je souhaite donc me concentrer sur une pile complète de fichiers pertinents pour ma fonctionnalité. Lorsque j'ajoute un champ de numéro de téléphone au modèle utilisateur, je ne me soucie d'aucun contrôleur autre que le contrôleur utilisateur et je ne me soucie d'aucun modèle autre que le modèle utilisateur.
- Donc, au lieu de modifier 6 fichiers qui se trouvent chacun dans leur propre répertoire et d'ignorer des tonnes d'autres fichiers dans ces répertoires, ce référentiel est organisé de telle sorte que tous les fichiers dont j'ai besoin pour créer une fonctionnalité sont colocalisés
- De par la nature de MVC, la vue utilisateur est couplée au contrôleur utilisateur qui est couplé au modèle utilisateur. Ainsi, lorsque je change de modèle d'utilisateur, ces 3 fichiers changent souvent ensemble, mais le contrôleur des transactions ou le contrôleur client sont découplés et donc pas impliqués. La même chose s'applique également aux conceptions non MVC.
- Le découplage de style MVC ou MOVE en termes de code qui va dans quel module est toujours encouragé, mais la propagation des fichiers MVC dans les répertoires frères est juste ennuyeuse.
- Ainsi, chacun de mes fichiers d'itinéraires a la partie des itinéraires qu'il possède. Un
routes.rb
fichier de style rails est pratique si vous voulez un aperçu de tous les itinéraires dans l'application, mais lorsque vous créez des fonctionnalités et corrigez des bugs, vous ne vous souciez que des itinéraires pertinents pour la pièce que vous modifiez.
- Stocker les tests à côté du code
- Ceci est juste un exemple de "groupe par couplage", mais je voulais l'appeler spécifiquement. J'ai écrit de nombreux projets où les tests vivent sous un système de fichiers parallèle appelé "tests" et maintenant que j'ai commencé à mettre mes tests dans le même répertoire que leur code correspondant, je ne reviendrai jamais. Ceci est plus modulaire et beaucoup plus facile à utiliser dans les éditeurs de texte et atténue une grande partie du non-sens du chemin "../../ ..". En cas de doute, essayez-le sur quelques projets et décidez par vous-même. Je ne vais rien faire au-delà de cela pour vous convaincre que c'est mieux.
- Réduisez le couplage transversal avec les événements
- Il est facile de penser «OK, chaque fois qu'une nouvelle offre est créée, je veux envoyer un e-mail à tous les vendeurs», puis il suffit de mettre le code pour envoyer ces e-mails dans l'itinéraire qui crée les offres.
- Cependant, ce couplage finira par transformer votre application en une boule de boue géante.
- Au lieu de cela, le DealModel devrait simplement déclencher un événement "create" et ignorer complètement ce que le système pourrait faire d'autre en réponse à cela.
- Lorsque vous codez de cette façon, il devient beaucoup plus possible de mettre tout le code associé à l'utilisateur
app/users
car il n'y a pas un nid de logique métier couplée partout polluant la pureté de la base de code utilisateur.
- Le flux de code est suivable
- Ne fais pas de choses magiques. Ne chargez pas automatiquement les fichiers à partir de répertoires magiques dans le système de fichiers. Ne soyez pas Rails. L'application démarre à
app/server.js:1
et vous pouvez voir tout ce qu'elle charge et exécute en suivant le code.
- Ne faites pas de DSL pour vos itinéraires. Ne faites pas de métaprogrammation idiote quand ce n'est pas nécessaire.
- Si votre application est si grand que cela
magicRESTRouter.route(somecontroller, {except: 'POST'})
est une grande victoire pour vous sur 3 de base app.get
, app.put
, app.del
, les appels, vous êtes probablement construire une application monolithique qui est trop grand pour travailler efficacement. Obtenez fantaisie pour de GRANDES victoires, pas pour convertir 3 lignes simples en 1 ligne complexe.
Utiliser des noms de fichiers en minuscules
- Ce format évite les problèmes de sensibilité à la casse du système de fichiers sur toutes les plateformes
- npm interdit les majuscules dans les nouveaux noms de paquets, et cela fonctionne bien avec cela
Spécificités express.js
Ne pas utiliser app.configure
. C'est presque entièrement inutile et vous n'en avez tout simplement pas besoin. C'est dans beaucoup de passe-partout en raison de copypasta stupide.
- L'ORDRE DU MIDWARE ET DES ITINÉRAIRES EN MATIÈRE EXPRESSE !!!
- Presque tous les problèmes de routage que je vois sur stackoverflow sont des middlewares express hors service
- En général, vous voulez que vos itinéraires soient découplés et ne dépendent pas beaucoup de l'ordre
- Ne pas utiliser
app.use
pour l'ensemble de votre application si vous n'avez vraiment besoin que de ce middleware pour 2 routes (je vous regarde, body-parser
)
- Assurez-vous que tout est dit et fait, vous avez EXACTEMENT cette commande:
- Tout middleware très important à l'échelle de l'application
- Tous vos itinéraires et assortiments de middlewares
- ALORS gestionnaires d'erreur
- Malheureusement, étant inspiré par sinatra, express.js suppose principalement que tous vos itinéraires seront entrés
server.js
et il sera clair comment ils sont commandés. Pour une application de taille moyenne, décomposer les choses en modules de routes séparés est bien, mais cela introduit un péril de middleware hors service
L'astuce du lien symbolique de l'application
Il existe de nombreuses approches décrites et discutées longuement par la communauté dans le grand fond mieux chemins exigent locale () pour Node.js . Je vais peut-être bientôt décider de préférer soit "traiter avec beaucoup de ../../../ .." ou utiliser le module requireFrom. Cependant, pour le moment, j'utilise l'astuce de lien symbolique détaillée ci-dessous.
Donc, une façon d'éviter l'intra-projet nécessite des chemins relatifs ennuyeux comme require("../../../config")
d'utiliser l'astuce suivante:
- créer un lien symbolique sous node_modules pour votre application
- cd node_modules && ln -nsf ../app
- ajoutez juste le lien symbolique node_modules / app lui - même , pas tout le dossier node_modules, pour git
- git add -f node_modules / app
- Oui, vous devriez toujours avoir "node_modules" dans votre
.gitignore
fichier
- Non, vous ne devez pas mettre "node_modules" dans votre dépôt git. Certaines personnes vous recommanderont de le faire. Ils sont incorrects.
- Maintenant, vous pouvez exiger des modules intra-projet en utilisant ce préfixe
var config = require("app/config");
var DealModel = require("app/deals/deal-model")
;
- Fondamentalement, cela fait que l'intra-projet nécessite un travail très similaire à celui des modules npm externes.
- Désolé, utilisateurs Windows, vous devez vous en tenir aux chemins relatifs du répertoire parent.
Configuration
Généralement, codez les modules et les classes pour qu'ils n'attendent qu'un options
objet JavaScript de base transmis. Seul app/server.js
doit charger le app/config.js
module. À partir de là, il peut synthétiser de petits options
objets pour configurer les sous-systèmes selon les besoins, mais coupler chaque sous-système à un grand module de configuration global plein d'informations supplémentaires est un mauvais couplage.
Essayez de centraliser la création de connexions de base de données et de les transmettre aux sous-systèmes plutôt que de passer des paramètres de connexion et de demander aux sous-systèmes d'établir eux-mêmes les connexions sortantes.
NODE_ENV
Ceci est une autre idée séduisante mais terrible transmise par Rails. Il devrait y avoir exactement 1 place dans votre application, app/config.js
qui examine la NODE_ENV
variable d'environnement. Tout le reste doit prendre une option explicite comme argument de constructeur de classe ou paramètre de configuration de module.
Si le module de messagerie a une option sur la façon de livrer des e-mails (SMTP, connectez-vous à stdout, mettez en file d'attente, etc.), il devrait prendre une option comme {deliver: 'stdout'}
mais il ne devrait absolument pas vérifier NODE_ENV
.
Les tests
Je garde maintenant mes fichiers de test dans le même répertoire que leur code correspondant et j'utilise les conventions de dénomination des extensions de nom de fichier pour distinguer les tests du code de production.
foo.js
a le code du module "foo"
foo.tape.js
a les tests basés sur les nœuds pour foo et vit dans le même dir
foo.btape.js
peut être utilisé pour les tests qui doivent s'exécuter dans un environnement de navigateur
J'utilise des globes de système de fichiers et la find . -name '*.tape.js'
commande pour accéder à tous mes tests si nécessaire.
Comment organiser le code dans chaque .js
fichier de module
La portée de ce projet concerne principalement la destination des fichiers et des répertoires, et je ne veux pas ajouter beaucoup d'autre portée, mais je mentionnerai simplement que j'organise mon code en 3 sections distinctes.
- Le bloc d'ouverture de CommonJS nécessite des appels aux dépendances d'état
- Bloc de code principal de JavaScript pur. Pas de pollution CommonJS ici. Ne référencez pas les exportations, les modules ou les exigences.
- Bloc de fermeture de CommonJS pour configurer les exportations