2
votes

Comment appliquer l'architecture en couches de Java / Spring dans NodeJs?

J'essaie d'apprendre NodeJS depuis un certain temps maintenant. Tous les livres et didacticiels semblent suivre un modèle similaire de structuration de leur code. Exemple -

const express = require('express');

const app = express();
app.set('view engine','hbs');

app.get('/getUserDetails', (req, res) =>{
    // Call DB and get users whose status is True
    res.send(userdetails);
});

app.get('/getUserDetailsTop10', (req, res) =>{
    // Call DB and get users whose status is True
    // Filter the returned results and limit the result to 10 values only
    res.send(userdetails);
});


app.listen(3000, () => {
    console.log("Started server on port : 3000");
});

Comme vous pouvez le voir ci-dessus, le contrôleur / getName exécute un appel de base de données et renvoie la vue. Donc, la logique métier ainsi que l'opération CRUD se font au même endroit.

Je viens du monde de JAVA et là nous le faisons légèrement différemment. Par exemple, une application Spring Boot contiendrait la structure suivante -

  • DTO
  • Référentiel
  • Service
  • Contrôleur

Ainsi, les classes controller sont les points de terminaison réels qui n'effectuent aucune logique métier mais appellent la classe service sous-jacente pour gérer tout cela. Les classes service implémentent la logique métier et conservent / récupèrent les données nécessaires pour cela à l'aide des classes repository . Le référentiel, quant à lui, gère les opérations CRUD .

Cela semblait être une manière sensée de développer un logiciel. Étant donné que chaque classe a ses rôles définis, il devient vraiment facile de gérer tout changement.

Je comprends que les NodeJs sont dynamiques mais -

1. Existe-t-il un moyen de séparer les fonctionnalités comme nous le faisons dans Spring ? Sinon,

2. Comment structurer de grands projets ayant plusieurs points de terminaison et transactions DB.

Cordialement


MODIFIER -

Considérez le scénario suivant -

J'ai une exigence, où je dois récupérer une liste d'utilisateurs de la base de données dont le statut est Vrai (supposons que le statut est un champ booléen dans le modèle). em>

En Java -

@GetMapping("/getUserDetailsTop10")
            public ArrayList<UserDetail> getUserDetail(){

            List<UserDetails> users = UserService.getUserDetail(true);
            // Filter the list to send top 10
            // return users .
    }

Controller.java -

@GetMapping("/getUserDetails")
        public ArrayList<UserDetail> getUserDetail(){

        return UserService.getUserDetail(true);
}

Maintenant , si l'exigence change et qu'il doit y avoir un nouveau point de terminaison qui ne renvoie que les 10 premiers détails de l'utilisateur dont l'état est vrai. Dans ce cas, nous pouvons ajouter un nouveau contrôleur et simplement limiter les résultats renvoyés à 10. Nous pouvons utiliser la même logique métier / classe de service.

Controller.java

@Service
public class UserService {

    @Autowired
    UserDetailRepository userDetailRepository;

    @Override
    public UserDetail getUserDetail (boolean status) {
        UserDetail userDetail  = UserDetailRepository .findUserDetailByStatus(status);
        return userDetail  ;
    }

Si je dois implémenter le même cas d'utilisation dans NodeJS, je devrai écrire la logique métier pour récupérer l'utilisateur deux fois -

const express = require('express');

const app = express();
app.set('view engine','hbs');

app.get('/', (req, res) =>{
    res.render('index');
});

app.get('/getName', (req, res) =>{
    // Mock DB call to fetch Name
    res.render('displayName');
});


app.listen(3000, () => {
    console.log("Started server on port : 3000");
});

Au mieux, je peux résumer cette logique dans une fonction, qui renverra une liste d'utilisateurs avec le statut Vrai, mais là encore, cette approche n'est pas très évolutive. Il doit y avoir une séparation complète entre la logique métier et le contrôleur.


23 commentaires

Veuillez participer à la visite (vous obtenez un badge!), Regardez autour de vous et lisez le centre d'aide , en particulier Comment poser une bonne question? , Quels types de questions dois-je éviter de poser? et < i> Quels sujets puis-je poser ici? Cette question est loin trop large et ouverte pour le format de questions-réponses de SO.


Salut @ T.J.Crowder. Je peux reformater la question si nécessaire. Mais je crois que ma question est assez précise. Je demande comment puis-je refactoriser le code ci-dessus en utilisant une architecture en couches. Si ce n'est pas possible, c'est très bien mais si c'est le cas, j'aimerais savoir comment.


Le format de la question n'est pas le problème, le contenu de la question l'est. Il existe des centaines de réponses différentes possibles tout aussi valables. L'écosystème Node.js est riche et varié, il n'y a pas qu'une seule façon de le faire. Veuillez consulter les liens ci-dessus, en particulier celui-ci . Ce n'est pas que ce ne soit pas une question parfaitement raisonnable à poser, mais simplement pas adaptée au format de ce site.


Bien que j'aimerais personnellement voir des discussions sur ce sujet, cette question est en effet assez large et pourrait nécessiter une série de blogs pour y répondre correctement. Le paradigme introduit par un framework comme Spring Boot est plus ou moins indépendant du langage. Vous pourriez aussi bien demander, comment Spring Boot est-il conçu pour rendre possible une telle architecture en couches, en java, sans même mentionner js. Connaissant cette réponse, je pourrais probablement écrire un framework similaire à partir de zéro dans js alors.


@hackape a du sens. Mais dans ce cas, comment gérez-vous les grands projets NodeJS? Ou NodeJs pas utilisé à la même échelle que celui de Java?


Pas comme si c'était une mauvaise question. @TJCrowder Juste curieux, où suggérez-vous d'être un endroit approprié pour soulever une question comme celle-ci?


une question similaire est abordée dans une certaine mesure ici stackoverflow.com/questions/5178334/…


Salut @ naga-elixir-jar, Il s'agit davantage d'une organisation de code que d'une séparation logique. Le commentaire supérieur suggère de déplacer nos modèles ORM vers un dossier séparé et d'exprimer les points de terminaison vers le contrôleur. Mais le problème initial persiste. La logique métier pour gérer l'appel de base de données est toujours en cours d'exécution dans le contrôleur.


Hmm, je ne suis pas le meilleur pour répondre à votre question. Pas si expérimenté dans le backend js, mais je vais essayer. La communauté Js embrasse des choses légères. Nous avons un runtime qui prend en charge nativement les fonctions de première classe, les boucles d'événements, etc. De cette façon, nous avons tendance à chercher naturellement une solution dans des modèles tels que le chaînage de pipelines, le flux observable, pub / sub. Nous avons aussi la séparation des préoccupations, mais pas comme le style Java.


Prenons par exemple l'application express.js. Vous pouvez avoir plusieurs couches de middlewares traitant une demande. Vous pouvez déléguer à un service externe dans le middleware. On pourrait dire que notre style est plus fonctionnel que celui orienté objet.


@hackape. Je me suis familiarisé avec ces modèles, mais je ne comprends toujours pas comment Node gère la réutilisation du code. Pour les ex- en Java, disons que j'ai une classe de service qui renvoie les utilisateurs dont l'état est Vrai. Et dans une classe de contrôleur, je reviendrai cela. Maintenant, si le besoin se fait sentir d'inclure une autre classe de contrôleur qui doit renvoyer des utilisateurs dont l'état est Vrai mais qui limite uniquement les valeurs renvoyées aux 5 premiers, alors ce contrôleur peut réutiliser le service existant. Je ne sais pas comment cela peut être fait dans Node !! Le seul moyen est de créer une fonction qui peut être référencée par plusieurs points de terminaison.


Trouvé une discussion similaire sur SO - stackoverflow.com/ questions / 41875617 /…


^ vous savez quoi, c'est une question concrète à poser. Pourquoi ne pas reformuler votre question et ajouter un exemple de code java, puis nous discuterons de la manière traditionnelle de résoudre le problème de réutilisation du code.


@hackape édité. S'il vous plaît, jetez un oeil.


" Le seul moyen est de créer une fonction qui peut être référencée par plusieurs points de terminaison. " - oui, c'est précisément ce que vous feriez. (En cas de besoin, sinon YAGNI). Vous pouvez mettre ces fonctions dans leurs propres modules, fichiers ou même les écrire sous forme de classes si vous préférez cela. Je ne comprends pas pourquoi vous pensez que ce n'est pas suffisant ou évolutif, ou pourquoi vous ne le considérez pas comme séparant les préoccupations.


@Bergi La raison est simple. Soit j'en aurai tout, soit aucun. Je ne veux pas choisir à la main des fonctions pour des éléments de logique qui, à mon avis, pourraient être réutilisés à l'avenir. Soit cela devrait être la norme, c'est-à-dire écrire la logique métier dans les fonctions et les appeler à partir des contrôleurs, soit nous ne les avons pas du tout et continuer avec le modèle existant. Sélectionner des morceaux de code ici et là et les encapsuler dans des fonctions ne semble en fait pas très agréable. Il doit y avoir une manière standard de faire cela.


@BouhayanDev l'une des choses qui me dérange en java est que vous êtes obligé de créer une classe avant d'utiliser une fonction. Je ne comprends pas pourquoi les fonctions de regroupement au sein d'une classe la rendent soudainement plus réutilisable. Je suis satisfait des fonctions dispersées regroupées de manière lâche par des fichiers bien nommés, c'est-à-dire des modules dans js. Ne vous méprenez pas, c'est juste que je ne vois vraiment pas la différence. Quel est l'avantage fondamental d'utiliser une classe comme "replieur" contre les modules.


Cependant, je vois les avantages de cette approche polyvalente: je peux les composer / les composer librement sans me soucier de leur propre classe. Cela me semble beaucoup plus réutilisable.


@hackape Cela dérive simplement du concept OOPS où le raisonnement va quelque chose comme ça - les fonctions sont analogues aux comportements alors que les classes sont analogues aux entités réelles (ex TV, projecteur, etc.). Maintenant, est-il judicieux d'autoriser la fonctionnalité sans définir le conteneur / propriétaire de la fonctionnalité? Ce n'est pas le cas. D'où ce regroupement de fonctions en classe. Cependant, je suis d'accord avec vous, du point de vue de la fonctionnalité, il ne semble pas y avoir de problème si j'utilise un module / une classe pour contenir mes fonctions


@BoudhayanDev mon travail quotidien est de créer une application Web à grande échelle, comme WebIDE. Je trouve également qu'il est plus naturel dans de nombreux scénarios d'utiliser la classe, en particulier lors de l'exposition des API à d'autres parties. Cet avantage a deux raisons, a) la persistance des états, b) les classes sont indexées / symbolisées / interrogeables dans l'EDI, alors que les fichiers ne le sont pas.


Je pense que vous ressentez un malaise conceptuel face à la bonté multi-paradigme dans js land. Java a une certaine charge de programmation fonctionnelle, mais pas en js. FP ou OOP, je vais simplement avec ce qui convient, selon le cas d'utilisation.


Oui, c'est le cas ici. Je pensais répliquer une application Spring existante à l'aide de Node. Mais il semble que Node couplait toutes les couches en un seul fichier. Mais je vais essayer de le déstructurer.


@BoudhayanDev " Soit j'aurai tout ou rien. " - eh bien si vous le souhaitez, vous pouvez bien sûr toujours tout mettre dans des fonctions séparées. Ce n'est qu'une convention et vous êtes encouragé à faire ce avec quoi vous vous sentez à l'aise. L'avantage d'un style lâche (au lieu d'être forcé par un cadre à suivre un modèle particulier) est qu'il évolue beaucoup mieux du simple et intégré au complexe et séparé. Vous n'utilisez que ce dont vous avez besoin.


3 Réponses :


1
votes

UserService.js

import * as express from 'express';
import { userController } from './controllers/user';

const app = express();
app.set('view engine','hbs');

app.use(userController);

app.listen(3000, () => {
    console.log("Started server on port : 3000");
});

UserController.js

import { userService } from '../services/user';

@RouterAdapter
class UserController {
  @GetMapping("/getUserDetails")
  getUserDetail() {
    return userService.getUserDetail(true);
  }
  @GetMapping("/getUserDetailsTop10")
  getUserDetailsTop10() {
    return userService.getUserDetail(true).slice(10);
  }
}

export const userController = new UserController();

app.js

import { userDetailRepository } from '../models/user';

class UserService {
    getUserDetail (status) {
        const userDetail  = UserDetailRepository.findUserDetailByStatus(status);
        return userDetail;
    }
}

export const userService = new UserService();

J'ai intentionnellement "déformé" mon code js pour qu'il corresponde au style java afin que vous vous sentiez peut-être chez vous. Personnellement, je n'écris pas js de cette façon, je préfère utiliser la fonction bien plus que la classe.

Je n'ai pas inclus l'implémentation de deux décorateurs ( @RouterAdapter, @GetMapping ) I utilisé, c'est un détail non lié à la discussion. Je peux vous assurer que c'est faisable dans js. La seule chose qui manque est que js n'a pas de surcharge de méthode d'exécution, donc je dois nommer 2 méthodes de contrôleur de diff.

Le point que je veux dire ici est que les modèles de conception sont pour la plupart indépendants du langage. Si vous êtes familier avec le langage, vous pouvez toujours trouver un moyen de bien gérer la séparation des préoccupations.

Maintenant, j'utilise intentionnellement la classe dans l'exemple ci-dessus, mais une fonction agissant comme un UserService ne le fait pas moins réutilisable. Je ne vois pas pourquoi vous pensez que "ça ne s'adapte pas".


10 commentaires

Cela fonctionnerait certainement. Mais oui, je suis d'accord avec vous ici que l'utilisation de classes pour structurer le code dans Node semble une version laide de Java: p Ce que j'aimerais savoir, est-ce vraiment la manière dont les grands projets dans Node sont gérés? Ou ai-je tort d'assumer l'importance de l'architecture en couches. De plus, par `` ça ne monte pas '' dans mon commentaire précédent, je voulais dire que, pour que je sépare la logique du contrôleur, je devrais écrire toute cette logique dans une classe / fonction de service. Mais cette abstraction doit être faite pour tous les cas où il existe une logique métier. Nous ne pouvons pas choisir la fonction que nous voulons


Aussi, que pensez-vous des opérations liées à la DB? Comme si je dois persister / récupérer quelque chose de DB, j'appellerai le connecteur client sql et exécuterai la requête dans NodeJs. Ce morceau de logique peut aller directement dans la classe de service. Existe-t-il également un moyen d'abstraire cela? Donc, je suppose que pour simuler le comportement du référentiel, nous pouvons créer un autre module qui contient essentiellement toutes les opérations CRUD définies pour une entité particulière sous la forme de fonctions. Et ces fonctions sont ensuite appelées dans la couche de service. Ensuite, il y aurait une séparation claire de - Contrôleur -> Service -> Repo?


Enfin, pourquoi les didacticiels / ressources NodeJs n'en parlent-ils pas? Est-ce parce que Node n'est pas utilisé pour des projets à grande échelle où de tels problèmes seraient rencontrés ou qu'il existe une stratégie alternative pour gérer ces problèmes?


@BoudhayanDev 1. J'espère que vous avez répondu dans les commentaires sous la question, ne voyez pas pourquoi le cherry-pick fn est inacceptable, peut-être que vous voulez juste passer un tas de fonctions connexes en même temps, dans ce cas, vous pouvez les envelopper dans un objet simple .


@BoudhayanDev 2. Personnellement, je suis d'accord avec / votre "hypothèse", mais encore une fois, je suis plus un gars du frontend, pas le meilleur gars pour répondre.


@BoudhayanDev 3. 2 raisons, d'abord peut-être qu'ils en ont parlé, comme dans l'autre article SO que vous avez trouvé, mais vous ne les reconnaissez tout simplement pas parce qu'ils ne ressemblent pas à Spring Boot. La deuxième approche «rigide» et communément acceptée n'existe pas dans js, en java, c'est en grande partie parce que le démarrage à ressort est si dominant, et en plus sa batterie est incluse. Express.js est dominé mais pas de batterie incluse, donc chacun apporte sa propre batterie. Parce que la facilité du multi-paradigme dans js. Les gens ont tendance à utiliser tout ce qui résout leur problème. La configuration de démarrage de printemps pour un petit projet est une simple exagération.


Et au fait, vous vous trompez sur nodejs pas pour une grande échelle. taobao.com (amazon chinois) utilise de manière intensive nodejs. Imaginez leur PV et leur complexité.


Comme je l'ai dit, je n'ai pas beaucoup d'expérience dans Node, je ne peux donc pas dire si c'est le bon outil pour les grands projets, mais après avoir lu vos réponses, je suis d'accord que Node / Express a sa propre façon de gérer les choses. Cela rappelle un autre framework que j'ai essayé il y a quelques années - appelé Flask (en Python). Ce modèle de non-application de responsabilités strictes aux classes / fonctions semble être commun dans tous les langages à typage dynamique. Je suppose que je vais simplement m'adapter à la manière de faire de Node plutôt que d'essayer de la compliquer en suivant la structuration du code de Java.


@Boudhayan Dev ouais bien sûr. Il est toujours bon d'essayer de nouvelles choses, élargit votre horizon.


Oui, faites-le simplement de manière nodejs. En interpolant une autre couche de complexité avec les applications de nœud pour suivre un autre principe architectural n'est pas justifié à mon humble avis. Vous pouvez ofc abstraire les classes de couche de service par entité, mais en raison de la nature dynamique du langage et du pilote que vous utilisez pour DB, vous pouvez également obtenir une bonne quantité de métadonnées pour générer ces classes. Vous devriez jeter un oeil à knex.js et voir ce que cela peut faire pour vous;)



1
votes

Une idée:

async function userDetails({ limit, ...restOfTheOptions } = { limit: 0 }) {
  console.log({ limit, restOfTheOptions }) // TODO logic here.
  // const result = await db.find('users', {...})
  // return serialize(result)
}

// function serialize(result) {
//  return JSON.stringify(result)
// }

module.exports = {
  userDetails
}

Et puis vous écrivez votre logique comme function userDetails ({limit, ... rest} = {limit: 0}) .

Dans un autre fichier, à savoir usersBusinessLogic.js:

const express = require('express');

const app = express();
const { userDetails } = require('./usersBusinessLogic.js')
app.set('view engine','hbs');

app.get('/getUserDetails', async (req, res) =>{
  const limit = parseInt(req.query.limit || '0')
  if (IsNaN(limit)) {
    res.status(400)
    return res.end('limit expected to be a number')
  }
  const detailsResponse = await userDetails({ limit })
  res.send();
});

Après quoi vous pouvez appeler GET / userDetails code> ou GET /userDetails?limit=10.

Dites-moi ce que vous en pensez.


2 commentaires

Salut. Cette approche fonctionnerait. Au moins, nous avons séparé le BL du contrôleur. Mais que suggéreriez-vous pour diviser davantage le module de service? Donc, à mon avis, il est toujours préférable de conserver les requêtes liées à la base de données dans un module / classe séparé. Pour les ex - getUsers, saveUsers, getUserByID etc sont toutes des requêtes DB (ORM ou autre). Donc, si je sépare cela, est-il judicieux d'introduire un autre module pour résumer ces requêtes? Parce que je ne suis pas sûr que l'utilisation de modules soit la bonne façon de le faire principalement parce que je suis nouveau dans JS. Mais j'aimerais connaître vos pensées.


Ce n'est pas particulièrement lié aux modules que vous essayez de réaliser. C'est lié à vos décisions architecturales. Dans mon exemple, j'ai mis await db.find ('users', {...}) en commentaire, mais je n'ai pas dit d'où il vient. Certes, en haut de votre module, vous devrez require ('./ db') , et dans le module db.js, implémenter votre logique contre la base de données. Que ce soit l'implémentation d'ORM ou simplement l'utilisation directe du pilote, c'est à vous de décider. Mais vous auriez à exposer quelque chose pour BL. J'espère que cela a été clarifié. N'hésitez pas à en demander plus.



3
votes

Pas vraiment une réponse ... mais quelques réflexions, car je viens de C # et j'ai commencé en tant que développeur NodeJs il y a 3 ans.

Injection de dépendances

Ceci est (malheureusement) rarement utilisé dans de nombreux projets NodeJs que je vois. Certains modules npm fournissent cette fonctionnalité, mais aucun d'entre eux ne m'a convaincu d'une approche et d'une API appropriées. L'injection de dépendances est toujours parfaitement possible, mais un peu plus moche à cause d'un code standard que vous devez écrire. Malheureusement, aucune approche @autowire facile. Donc oui, vous pouvez faire l'injection de dépendances comme avec Spring, mais pas aussi pratique. Mais que mon opinion ne vous empêche pas de rechercher des bibliothèques d'injection de dépendances pour node.

< gagnantArchitecture

Vous pouvez toujours utiliser les concepts de DTO, de référentiels, de services et de contrôleurs. Seulement pour une raison (étrange), la majorité des tutoriels et guides oublient l'architecture de bon sens et jettent tout dans un seul contrôleur. Ne les laissez pas vous séduire en oubliant les concepts que vous avez appris en Java. Non pas que la POO et Java n'aient pas de défauts, mais il y a une différence entre «écrire du code de style JavaScript» et «oublions l'architecture appropriée tous ensemble».

Notez que vous pourriez voir apparaître d'autres modèles de «programmation fonctionnelle» dans la communauté NodeJs, ce qui n'est pas mal (mais la POO non plus).

Comment structurer de grands projets ayant plusieurs points de terminaison et DB transactions.

Ignorez les mauvais exemples que vous voyez, suivez simplement votre instinct de votre expérience Java comment structurer votre code, avec les bonnes couches, responsabilités et modèles de conception. Gardez seulement à l'esprit que vous n'avez pas d'interfaces et qu'il n'y a pas d'alternative facile à cela. Vous devrez donc apprendre à contourner cela, mais vous pouvez toujours écrire du code élégant sans eux.

< gagnantMiddleware

Le middleware est un concept courant pour les applications NodeJs, ils sont similaires au style de programmation orienté proxy / aspect. Vous pourriez trouver des exemples de projets avec une quantité excessive de middleware, ne vous laissez pas non plus tenter par cela. L'intergiciel est bon pour l'authentification / la sérialisation / les en-têtes / la sécurité mais pas pour la logique métier. J'ai vu l'enfer du middleware et le développement piloté par le middleware, ce n'est pas joli.

De nombreuses personnes utilisent directement le client MongoDb / Mongoose / SQL sans utiliser le modèle d'adaptateur tel que Repository, ce qui fait fuir les requêtes MongoDb partout dans leur solution.

Mon conseil pour vous serait d'utiliser simplement la même structure et la même approche que vous connaissez, mais avec les outils que JavaScript vous donne. J'adore vraiment JavaScript, mais cela me fait grincer des dents quand je regarde le manque de conception et d'architecture que je vois dans les ressources grand public, bien que les petits projets et les PoC n'ont pas besoin d'une conception approfondie, mais souvent lorsque le projet se développe, ils ne le sont pas. nettoyer. La manière dont vous structurez un projet doit être indépendante du langage et du cadre dans lesquels vous écrivez.

Bon codage 😄


1 commentaires

Salut @Ian Segers, totalement d'accord avec vous. Je pourrais continuer et résumer la logique métier dans un module séparé, puis appeler des contrôleurs et cela fonctionnerait. Mais j'aimerais voir les didacticiels NodeJs appliquer cela. Cependant, je ne sais pas comment séparer les requêtes DB du module séparé (fonctions de service). J'adorerais voir un projet NodeJS bien structuré qui respecte ces normes de codage.