2012-11-07 1 views
3

J'écris une application Node.js en utilisant un émetteur d'événement global. En d'autres termes, mon application est entièrement construite autour des événements. Je trouve que ce type d'architecture fonctionne très bien pour moi, à l'exception d'un cas latéral que je décrirai ici.Comment éviter le besoin de retarder l'émission de l'événement au prochain tick de la boucle d'événement?

Notez que je ne pense pas que la connaissance de Node.js est nécessaire pour répondre à cette question. Par conséquent, je vais essayer de le garder abstrait.

Imaginez la situation suivante:

  • Un émetteur d'événement mondial (appelé mediator) permet aux modules individuels pour écouter les événements à l'échelle application.
  • Un serveur HTTP est créé, acceptant les demandes entrantes.
  • Pour chaque requête entrante, un émetteur d'événement est créé pour faire face à des événements spécifiques à cette demande

Un exemple (uniquement pour illustrer cette question) d'une requête entrante:

mediator.on('http.request', request, response, emitter) { 

    //deal with the new request here, e.g.: 
    response.send("Hello World."); 

}); 

Jusqu'à présent , tellement bon. On peut désormais étendre cette application en identifiant l'URL demandée et d'émettre des événements appropriés:

mediator.on('http.request', request, response, emitter) { 

    //identify the requested URL 
    if (request.url === '/') { 
     emitter.emit('root'); 
    } 
    else { 
     emitter.emit('404'); 
    } 

}); 

Après cela on peut écrire un module qui traitera une demande de racine.

mediator.on('http.request', function(request, response, emitter) { 

    //when root is requested 
    emitter.once('root', function() { 

     response.send('Welcome to the frontpage.'); 

    }); 

}); 

Cela semble bien, non? En fait, c'est code potentiellement cassé. La raison en est que la ligne emitter.emit('root') peut être exécutée avant la ligne emitter.once('root', ...). Le résultat est que l'auditeur n'est jamais exécuté.

On pourrait faire face à cette situation particulière en retardant l'émission de l'événement root à la fin de la boucle d'événement:

mediator.on('http.request', request, response, emitter) { 

    //identify the requested URL 
    if (request.url === '/') { 
     process.nextTick(function() { 
      emitter.emit('root'); 
     }); 
    } 
    else { 
     process.nextTick(function() { 
      emitter.emit('404'); 
     }); 
    } 

}); 

La raison pour laquelle cela fonctionne est parce que l'émission est maintenant retardée jusqu'à ce que la boucle d'événements en cours a terminé, et donc tous les auditeurs ont été enregistrés.

Cependant, il y a beaucoup de problèmes avec cette approche: architecture basée

  1. l'un des avantages d'un tel événement est que des modules d'émission ne ont pas besoin de savoir qui est à l'écoute de leurs événements. Par conséquent, il ne devrait pas être nécessaire de décider si l'émission de l'événement doit être retardée, car on ne peut pas savoir ce qui va écouter l'événement et s'il a besoin d'être retardé ou non.
  2. ce code de manière significative les encombre et complexifie (comparer les deux exemples)
  3. il aggrave probablement la performance

En conséquence, ma question est: comment peut-on éviter la nécessité de retarder l'émission d'événements à la prochaine tique de la boucle d'événement, comme dans la situation décrite?

Mise à jour 19-01-2013

Un exemple illustrant pourquoi ce comportement est utile: pour permettre à une requête HTTP à traiter en parallèle.

mediator.on('http.request', function(req, res) { 

    req.onceall('json.parsed', 'validated', 'methodoverridden', 'authenticated', function() { 
     //the request has now been validated, parsed as JSON, the kind of HTTP method has been overridden when requested to and it has been authenticated 
    }); 

}); 

Si chaque événement comme json.parsed émettrait la demande initiale, alors ce qui précède n'est pas possible, car chaque événement est lié à une autre demande et vous ne pouvez pas écouter une combinaison d'actions exécutées en parallèle pour une demande spécifique.

+0

Veuillez indiquer si quelque chose doit être développé plus avant. – Tom

+1

Configurez tous les événements au démarrage. N'attachez pas d'événements (ceux du contrôle de flux) après le démarrage de l'application. Ne joignez pas d'événements lors de l'exécution pour chaque requête. – Raynos

Répondre

0

Oi, cela semble être une architecture très brisée pour quelques raisons:

  1. Comment passez-vous autour request et response? Il semble que vous ayez des références mondiales à leur sujet.
  2. Si je réponds à votre question, vous transformerez votre serveur en une pure fonction synchrone et vous perdrez la puissance du noeud asynchrone.js. (Les demandes seraient mises en attente efficacement et ne pouvaient commencer à exécuter une fois que la dernière demande est 100% terminée.)

Pour résoudre ce problème:

  1. passe request & response à l'appel emit() en tant que paramètres. Maintenant, vous n'avez plus besoin de tout forcer à s'exécuter de façon synchrone, car lorsque le composant suivant gère l'événement, il aura une référence à la bonne requête & objets de réponse.
  2. Découvrez d'autres solutions courantes qui n'ont pas besoin d'un médiateur global. Regardez le modèle que Connect a été basé sur de nombreuses il y a Internet années: http://howtonode.org/connect-it < - décrit middleware/oignon routage
+0

Je n'ai pas de références globales à la requête et à la réponse, elles sont émises sur chaque requête du serveur en utilisant le médiateur. Je ne vois pas comment transformer le serveur en une fonction synchrone résoudrait le problème. Peut-être que nous ne parlons pas de la même chose ici? – Tom

1

On dirait que vous commencez à courir dans certains des inconvénients du modèle d'observateur (comme mentionné dans beaucoup de livres/articles qui décrivent ce modèle). Ma solution est pas idéale - en supposant un idéal existe - mais:

Si vous pouvez faire une hypothèse simplificatrice que l'événement est émis seulement 1 fois par émetteur (c.-à-emitter.emit('root'); est appelée une seule fois pour toute instance emitter), alors peut-être vous pouvez écrire quelque chose qui fonctionne comme l'événement $.ready() de jQuery.

Dans ce cas, l'abonnement à emitter.once('root', function() { ... }) vérifie si 'root' a déjà été émis et, dans ce cas, appelle le gestionnaire de toute façon. Et si 'root' n'a pas encore été émis, il s'en remettra à la fonctionnalité normale existante.

C'est tout ce que j'ai.

1

Je pense que cette architecture est en difficulté, car vous faites du travail séquentiel (E/S) qui nécessite un ordre d'action précis mais qui projette de construire une application sur des composants qui permettent naturellement un ordre d'exécution non-déterministe.

Qu'est-ce que vous pouvez faire

Inclure sélecteur de contexte dans la fonction mediator.on par exempleDe cette façon

mediator.on('http.request > root', function(..) { }) 

Ou définir comme submediator

var submediator = mediator.yield('http.request > root'); 
submediator.on(function(...) { 
    emitter.once('root', ...) 
}); 

Cela déclencherait le rappel que si la racine a été émise par gestionnaire http.request.

Une autre manière plus délicate est de rendre l'ordonnancement en arrière-plan, mais ce n'est pas faisable avec votre médiateur actuel qui les régit toutes. Implémentez le code de sorte que chaque appel .emit n'envoie pas réellement l'événement, mais place l'événement produit dans la liste. Chaque .once place l'enregistrement d'événement de consommation dans la même liste. Lorsque tous les rappels de mediator.on ont été exécutés, parcourez la liste, triez-la par ordre de dépendance (par exemple si la liste a d'abord consommé 'root' et ensuite produit 'root' échangez-les). Ensuite, exécutez consommer les gestionnaires dans l'ordre. Si vous manquez d'événements, arrêtez l'exécution.

3

Avoir à la fois un mediator qui écoute des événements et un emitter qui écoute et déclenche également des événements semble trop compliqué. Je suis sûr qu'il y a une raison légitime mais ma suggestion est de simplifier. Nous utilisons un eventBus global dans notre service nodejs qui fait quelque chose de similaire. Pour cette situation, j'émettrais un nouvel événement.

bus.on('http:request', function(req, res) { 
    if (req.url === '/') 
    bus.emit('ns:root', req, res); 
    else 
    bus.emit('404'); 
}); 

// note the use of namespace here to target specific subsystem 
bus.once('ns:root', function(req, res) { 
    res.send('Welcome to the frontpage.'); 
}); 
+0

J'ai utilisé cette approche jusqu'à maintenant. La raison pour laquelle je veux commencer à utiliser un émetteur interne pour chaque requête est parce que cela rend beaucoup plus facile de prendre soin des événements pour chaque demande unique. Par exemple, tout le code qui traite les requêtes peut effectuer un nettoyage en faisant 'emitter.once ('timeout', cleanup)' plutôt que 'mediator.on ('http.request.timeout', cleanupRequest)'. Cela peut ne pas sembler une grande différence, mais c'est parce que dans le second cas, la portée locale à demander en question est perdue. Je pense que j'aurais besoin d'un autre poste énorme avec du code pour l'expliquer correctement. – Tom

+0

s'il vous plaît voir ma question originale mise à jour pour un exemple sur la raison pour laquelle votre approche ne s'applique pas dans ce cas. – Tom

Questions connexes