Υπηρεσία Express για παράλληλη επίκληση SOAP σε λιγότερες από 25 γραμμές κώδικα

ΣΦΑΙΡΙΚΗ ΕΙΚΟΝΑ

Ας υποθέσουμε ότι υπάρχει μια υπηρεσία που έχει τις ακόλουθες δυνατότητες:

  1. Εκθέτει ένα REST τελικό σημείο που λαμβάνει μια λίστα αιτημάτων.
  2. Παράλληλα καλεί μια υπηρεσία SOAP, μία φορά ανά στοιχείο στη λίστα αιτημάτων.
  3. Επιστρέφει το αποτέλεσμα που έχει μετατραπεί από XML σε JSON.

Ο πηγαίος κώδικας αυτής της υπηρεσίας θα μπορούσε να μοιάζει με αυτό χρησιμοποιώντας το Node.js, Express και τον Οδηγό στυλ JavaScript Airbnb:

'use strict'; const { soap } = require('strong-soap'); const expressApp = require('express')(); const bodyParser = require('body-parser'); const url = '//www.dneonline.com/calculator.asmx?WSDL'; const clientPromise = new Promise((resolve, reject) => ( soap.createClient(url, {}, (err, client) => err ? reject(err) : resolve(client)) )); expressApp.use(bodyParser.json()) .post('/parallel-soap-invoke', (req, res) => (clientPromise.then(client => ({ client, requests: req.body })) .then(invokeOperations) .then(results => res.status(200).send(results)) .catch(({ message: error }) => res.status(500).send({ error })) )) .listen(3000, () => console.log('Waiting for incoming requests.')); const invokeOperations = ({ client, requests }) => (Promise.all(requests.map(request => ( new Promise((resolve, reject) => client.Add(request, (err, result) => ( err ? reject(err) : resolve(result)) )) ))));

Αίτημα δείγματος:

POST /parallel-soap-invoke [ { "intA": 1, "intB": 2 }, { "intA": 3, "intB": 4 }, { "intA": 5, "intB": 6 } ]

Δείγμα απάντησης:

HTTP/1.1 200 [ { "AddResult": 3 }, { "AddResult": 7 }, { "AddResult": 11 } ]

Οι δοκιμές δείχνουν ότι ένα μεμονωμένο άμεσο αίτημα για την υπηρεσία SOAP χρησιμοποιώντας το SOAPUI διαρκεί ~ 430 ms (από το σημείο όπου βρίσκομαι, στη Χιλή). Η αποστολή τριών αιτημάτων (όπως φαίνεται παραπάνω) διαρκεί ~ 400 ms για κλήσεις στην υπηρεσία Express (εκτός από την πρώτη, η οποία λαμβάνει το WSDL και δημιουργεί τον πελάτη).

Γιατί τα περισσότερα αιτήματα χρειάζονται λιγότερο χρόνο; Κυρίως επειδή το XML δεν επικυρώνεται σε μεγάλο βαθμό όπως είναι στο κανονικό σαπούνι, οπότε αν αυτή η απαλή επικύρωση δεν ταιριάζει με τις προσδοκίες σας, θα πρέπει να λάβετε υπόψη πρόσθετα χαρακτηριστικά ή λύσεις.

Αναρωτιέστε πώς θα φαίνεται async/await; Ορίστε (τα αποτελέσματα είναι τα ίδια):

'use strict'; const { soap } = require('strong-soap'); const expressApp = require('express')(); const bodyParser = require('body-parser'); const url = '//www.dneonline.com/calculator.asmx?WSDL'; const clientPromise = new Promise((resolve, reject) => ( soap.createClient(url, {}, (err, client) => err ? reject(err) : resolve(client)) )); expressApp.use(bodyParser.json()) .post('/parallel-soap-invoke', async (req, res) => { try { res.status(200).send(await invokeOperations(await clientPromise, req.body)); } catch ({message: error}) { res.status(500).send({ error }); } }) .listen(3000, () => console.log('Waiting for incoming requests.')); const invokeOperations = (client, requests) => (Promise.all(requests.map(request => ( new Promise((resolve, reject) => client.Add(request, (err, result) => ( err ? reject(err) : resolve(result)) )) ))));

Η ακόλουθη εικόνα παρέχει μια ιδέα για το πώς λειτουργεί ο κώδικας:

Αυτό το άρθρο στοχεύει να δείξει την απλότητα της χρήσης JavaScript για εργασίες στον επιχειρηματικό κόσμο, όπως η χρήση υπηρεσιών SOAP. Εάν είστε εξοικειωμένοι με το JavaScript, αυτό είναι βασικά μόνο Promise.allστην κορυφή μερικών υποσχέσεων επιστροφής κλήσεων κάτω από ένα τελικό σημείο Express. Μπορείτε να μεταβείτε απευθείας στην ενότητα 4 ( κομμάτι μπόνους ) αν νομίζετε ότι θα μπορούσε να σας φανεί χρήσιμο.

Εάν είστε εκτός του κόσμου της JavaScript, νομίζω ότι 24 γραμμές κώδικα για τις τρεις δυνατότητες που ανέφερα στην αρχή είναι πολύ καλή. Τώρα θα αναφερθώ στις λεπτομέρειες.

1. Το τμήμα Express

Ας ξεκινήσουμε με τον κωδικό που σχετίζεται με το Express, ένα ελάχιστο και ευέλικτο πλαίσιο εφαρμογής διαδικτύου Node.js. Είναι αρκετά απλό και μπορείτε να το βρείτε οπουδήποτε, οπότε θα σας δώσω μια συνοπτική περιγραφή.

'use strict'; // Express framework. const express = require('express'); // Creates an Express application. const app = express(); /** * Creates a GET (which is defined by the method invoked on 'app') endpoint, * having 'parallel-soap-invoke' as entry point. * Each time a GET request arrives at '/parallel-soap-invoke', the function passed * as the second parameter from app.get will be invoked. * The signature is fixed: the request and response objects. */ app.get('/parallel-soap-invoke', (_, res) => { // HTTP status of the response is set first and then the result to be sent. res.status(200).send('Hello!'); }); // Starts 'app' and sends a message when it's ready. app.listen(3000, () => console.log('Waiting for incoming requests.'));

Αποτέλεσμα:

GET /parallel-soap-invoke HTTP/1.1 200 Hello!

Τώρα θα πρέπει να χειριστούμε ένα αντικείμενο που αποστέλλεται μέσω POST. Το Express body-parserεπιτρέπει την εύκολη πρόσβαση στο κύριο μέρος του αιτήματος:

 'use strict'; const expressApp = require('express')(); // Compressing two lines into one. const bodyParser = require('body-parser'); // Several parsers for HTTP requests. expressApp.use(bodyParser.json()) // States that 'expressApp' will use JSON parser. // Since each Express method returns the updated object, methods can be chained. .post('/parallel-soap-invoke', (req, res) => { /** * As an example, the same request body will be sent as response with * a different HTTP status code. */ res.status(202).send(req.body); // req.body will have the parsed object }) .listen(3000, () => console.log('Waiting for incoming requests.'));
POST /parallel-soap-invoke content-type: application/json [ { "intA": 1, "intB": 2 }, { "intA": 3, "intB": 4 }, { "intA": 5, "intB": 6 } ] HTTP/1.1 202 [ { "intA": 1, "intB": 2 }, { "intA": 3, "intB": 4 }, { "intA": 5, "intB": 6 } ] 

Λοιπόν, σύντομη ιστορία: ρυθμίστε την εφαρμογή Express και μόλις έχετε το αποτέλεσμα, στείλτε την μέσω resκαι voilà.

2. Το τμήμα SOAP

Αυτό θα έχει μερικά περισσότερα βήματα από την προηγούμενη ενότητα. Η κύρια ιδέα είναι ότι, για παράλληλη επίκληση των σαπουνιών, θα χρησιμοποιήσω Promise.all. Για να μπορέσω να το χρησιμοποιήσω Promise.all, η επίκληση στις υπηρεσίες SOAP πρέπει να αντιμετωπιστεί μέσα σε μια υπόσχεση, κάτι που δεν ισχύει strong-soap. Αυτή η ενότητα θα δείξει πώς να μετατρέψετε τις κανονικές επιστροφές κλήσεων από strong-soapσε Υποσχέσεις και, στη συνέχεια, να τοποθετήσετε ένα Promise.allπάνω από αυτό.

Ο παρακάτω κώδικας θα χρησιμοποιήσει το πιο βασικό παράδειγμα από strong-soapτην τεκμηρίωση. Θα το απλοποιήσω λίγο και θα χρησιμοποιήσω το ίδιο WSDL που βλέπουμε (δεν χρησιμοποίησα το ίδιο WSDL που αναφέρεται στην strong-soapτεκμηρίωση, καθώς αυτό το WSDL δεν λειτουργεί πια):

'use strict'; // The SOAP client library. var { soap } = require('strong-soap'); // WSDL we'll be using through the article. var url = '//www.dneonline.com/calculator.asmx?WSDL'; // Hardcoded request var requestArgs = { "intA": 1, "intB": 2, }; // Creates the client which is returned in the callback. soap.createClient(url, {}, (_, client) => ( // Callback delivers the result of the SOAP invokation. client.Add(requestArgs, (_, result) => ( console.log(`Result: ${"\n" + JSON.stringify(result)}`) )) ));
$ node index.js Result: {"AddResult":3}

Θα το μετατρέψω σε Υποσχέσεις και θα περάσω από όλες τις επιστροφές, ένα προς ένα, για χάρη του παραδείγματος. Με αυτόν τον τρόπο η διαδικασία μετάφρασης θα είναι ξεκάθαρη για εσάς:

'use strict'; var { soap } = require('strong-soap'); var url = '//www.dneonline.com/calculator.asmx?WSDL'; var requestArgs = { "intA": 1, "intB": 2, }; /** * A function that will return a Promise which will return the SOAP client. * The Promise receives as parameter a function having two functions as parameters: * resolve & reject. * So, as soon as you got a result, call resolve with the result, * or call reject with some error otherwise. */ const createClient = () => (new Promise((resolve, reject) => ( // Same call as before, but I'm naming the error parameter since I'll use it. soap.createClient(url, {}, (err, client) => ( /** * Did any error happen? Let's call reject and send the error. * No? OK, let's call resolve sending the result. */ err ? reject(err) : resolve(client) )))) ); /** * The above function is invoked. * The Promise could have been inlined here, but it's more understandable this way. */ createClient().then( /** * If at runtime resolve is invoked, the value sent through resolve * will be passed as parameter for this function. */ client => (client.Add(requestArgs, (_, result) => ( console.log(`Result: ${"\n" + JSON.stringify(result)}`) ))), // Same as above, but in this case reject was called at runtime. err => console.log(err), );

Η κλήση node index.jsέχει το ίδιο αποτέλεσμα με πριν. Επόμενη επανάκληση:

'use strict'; var { soap } = require('strong-soap'); var url = '//www.dneonline.com/calculator.asmx?WSDL'; var requestArgs = { "intA": 1, "intB": 2, }; const createClient = () => (new Promise((resolve, reject) => ( soap.createClient(url, {}, (err, client) => ( err ? reject(err) : resolve(client) )))) ); /** * Same as before: do everything you need to do; once you have a result, * resolve it, or reject some error otherwise. * invokeOperation will replace the first function of .then from the former example, * so the signatures must match. */ const invokeOperation = client => (new Promise((resolve, reject) => ( client.Add(requestArgs, (err, result) => ( err ? reject(err) : resolve(result) )) ))); /** * .then also returns a Promise, having as result the value resolved or rejected * by the functions that were passed as parameters to it. In this case, the second .then * will receive the value resolved/rejected by invokeOperation. */ createClient().then( invokeOperation, err => console.log(err), ).then( result => console.log(`Result: ${"\n" + JSON.stringify(result)}`), err => console.log(err), );

node index.js; Ακόμα το ίδιο. Ας τυλίξουμε αυτές τις υποσχέσεις σε μια συνάρτηση, προκειμένου να προετοιμάσουμε τον κωδικό για την κλήση του στο τελικό σημείο Express. Απλοποιεί επίσης λίγο τον χειρισμό σφαλμάτων:

'use strict'; var { soap } = require('strong-soap'); var url = '//www.dneonline.com/calculator.asmx?WSDL'; var requestArgs = { "intA": 1, "intB": 2, }; const createClient = () => (new Promise((resolve, reject) => ( soap.createClient(url, {}, (err, client) => ( err ? reject(err) : resolve(client) )))) ); const invokeOperation = client => (new Promise((resolve, reject) => ( client.Add(requestArgs, (err, result) => ( err ? reject(err) : resolve(result) )) ))); const processRequest = () => createClient().then(invokeOperation); /** * .catch() will handle any reject not handled by a .then. In this case, * it will handle any reject called by createClient or invokeOperation. */ processRequest().then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`)) .catch(({ message }) => console.log(message));

Σίγουρα μπορείτε να μαντέψετε το αποτέλεσμα node index.js.

Τι θα συμβεί εάν πραγματοποιηθούν πολλές επόμενες κλήσεις; Θα μάθουμε με τον ακόλουθο κωδικό:

'use strict'; var { soap } = require('strong-soap'); var url = '//www.dneonline.com/calculator.asmx?WSDL'; var requestArgs = { "intA": 1, "intB": 2, }; const createClient = () => (new Promise((resolve, reject) => ( soap.createClient(url, {}, (err, client) => { if (err) { reject(err); } else { // A message is displayed each time a client is created. console.log('A new client is being created.'); resolve(client); } }))) ); const invokeOperation = client => (new Promise((resolve, reject) => ( client.Add(requestArgs, (err, result) => ( err ? reject(err) : resolve(result) )) ))); const processRequest = () => createClient().then(invokeOperation) processRequest().then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`)) .catch(({ message }) => console.log(message)); processRequest().then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`)) .catch(({ message }) => console.log(message)); processRequest().then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`)) .catch(({ message }) => console.log(message));
$ node index.js A new client is being created. A new client is being created. Result: {"AddResult":3} A new client is being created. Result: {"AddResult":3} Result: {"AddResult":3}

Δεν είναι καλό, καθώς δημιουργούνται αρκετοί πελάτες. Στην ιδανική περίπτωση, ο πελάτης πρέπει να αποθηκευτεί προσωρινά και να επαναχρησιμοποιηθεί. Υπάρχουν δύο βασικοί τρόποι για να επιτευχθεί αυτό:

  1. Μπορείτε να δημιουργήσετε μια μεταβλητή εκτός της υπόσχεσης και να αποθηκεύσετε προσωρινά στον πελάτη μόλις την έχετε (λίγο πριν την επιλύσετε). Ας το πούμε αυτό cachedClient. Ωστόσο, σε αυτήν την περίπτωση, θα πρέπει να χειριστείτε χειροκίνητα τις κλήσεις που createClient()πραγματοποιούνται από την πρώτη φορά που καλείται και πριν επιλυθεί ο πρώτος πελάτης. Θα πρέπει να ελέγξετε εάν cachedClientείναι η αναμενόμενη τιμή ή θα πρέπει να ελέγξετε αν η υπόσχεση έχει επιλυθεί ή όχι, ή θα πρέπει να βάλετε κάποιο είδος εκδότη συμβάντων για να ξέρετε πότε cachedClientείναι έτοιμο. Την πρώτη φορά που έγραψα κώδικα για αυτό, χρησιμοποίησα αυτήν την προσέγγιση και κατέληξα να ζω με το γεγονός ότι κάθε κλήση έγινε πριν από την πρώτη createClient().resolveαντικατάσταση cachedClient. Εάν το πρόβλημα δεν είναι τόσο ξεκάθαρο, ενημερώστε με και θα γράψω τον κωδικό και τα παραδείγματα.
  2. Οι υποσχέσεις έχουν μια πολύ δροσερή λειτουργία (βλ. Τεκμηρίωση MDN, ενότητα "Επιστροφή τιμής"): εάν καλέσετε .then()μια υπόσχεση που έχει επιλυθεί / απορριφθεί, θα επιστρέψει την ίδια τιμή που επιλύθηκε / απορρίφθηκε, χωρίς επεξεργασία ξανά. Στην πραγματικότητα, πολύ τεχνικά, θα είναι το ίδιο αντικείμενο αναφοράς.

Η δεύτερη προσέγγιση είναι πολύ πιο απλή στην εφαρμογή, οπότε ο σχετικός κώδικας είναι ο ακόλουθος:

'use strict'; var { soap } = require('strong-soap'); var url = '//www.dneonline.com/calculator.asmx?WSDL'; var requestArgs = { "intA": 1, "intB": 2, }; // createClient function is removed. const clientPromise = (new Promise((resolve, reject) => ( soap.createClient(url, {}, (err, client) => { if (err) { reject(err); } else { console.log('A new client is being created.'); resolve(client); } }))) ); const invokeOperation = client => (new Promise((resolve, reject) => ( client.Add(requestArgs, (err, result) => ( err ? reject(err) : resolve(result) )) ))); // clientPromise is called instead getClient(). clientPromise.then(invokeOperation) .then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`)) .catch(({ message }) => console.log(message)); clientPromise.then(invokeOperation) .then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`)) .catch(({ message }) => console.log(message)); clientPromise.then(invokeOperation) .then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`)) .catch(({ message }) => console.log(message));
$ node index.js A new client is being created. Result: {"AddResult":3} Result: {"AddResult":3} Result: {"AddResult":3}

Τέλος, για αυτήν την ενότητα, ας κάνουμε τον κώδικα να χειριστεί πολλές παράλληλες κλήσεις. Αυτό θα είναι εύκολο:

  1. Για τον χειρισμό πολλών παράλληλων κλήσεων, θα χρειαστούμε Promise.all.
  2. Promise.allέχει μία μόνο παράμετρο: μια σειρά υποσχέσεων. Έτσι θα μετατρέψουμε τη λίστα των αιτημάτων σε μια λίστα υποσχέσεων. Ο κώδικας αυτή τη στιγμή μετατρέπει ένα μόνο αίτημα σε μία υπόσχεση ( invokeOperation), οπότε ο κώδικας χρειάζεται απλώς ένα .mapγια να το επιτύχει.
'use strict'; var { soap } = require('strong-soap'); var url = '//www.dneonline.com/calculator.asmx?WSDL'; // Hardcoded list of requests. var requestsArgs = [ { "intA": 1, "intB": 2, }, { "intA": 3, "intB": 4, }, { "intA": 5, "intB": 6, }, ]; const clientPromise = (new Promise((resolve, reject) => ( soap.createClient(url, {}, (err, client) => err ? reject(error) : resolve(client)) ))); // Promise.all on top of everything. const invokeOperation = client => (Promise.all( // For each request, a Promise is returned. requestsArgs.map(requestArgs => new Promise((resolve, reject) => ( // Everything remains the same here. client.Add(requestArgs, (err, result) => ( err ? reject(err) : resolve(result) )) ))) )); clientPromise.then(invokeOperation) .then(result => console.log(`Result: ${"\n" + JSON.stringify(result)}`)) .catch(({ message }) => console.log(message));
$ node index.js Result: [{"AddResult":3},{"AddResult":7},{"AddResult":11}]

3. Συνδυάζοντας τα όλα μαζί

This is fairly easy — it’s just assembling the last code from each previous section:

'use strict'; const { soap } = require('strong-soap'); const expressApp = require('express')(); const bodyParser = require('body-parser'); const url = '//www.dneonline.com/calculator.asmx?WSDL'; const clientPromise = new Promise((resolve, reject) => ( soap.createClient(url, {}, (err, client) => err ? reject(err) : resolve(client)) )); expressApp.use(bodyParser.json()) .post('/parallel-soap-invoke', (req, res) => (clientPromise.then(invokeOperations) .then(results => res.status(200).send(results)) .catch(({ message: error }) => res.status(500).send({ error })) )) .listen(3000, () => console.log('Waiting for incoming requests.')); // Adding req.body instead of hardcoded requests. const invokeOperations = client => Promise.all(req.body.map(request => ( new Promise((resolve, reject) => client.Add(request, (err, result) => ( err ? reject(err) : resolve(result)) )) )));
POST /parallel-soap-invoke [ { "intA": 1, "intB": 2 }, { "intA": 3, "intB": 4 }, { "intA": 5, "intB": 6 } ] HTTP/1.1 500 { "error": "req is not defined" }

Hmmm… Not a good result, since I did not expect an error at all. The problem is that invokeOperations doesn’t have req in its scope. The first thought could be “Just add it to the signature.” But that’s not possible, as that signature matches the result from the previous Promise, and that promise doesn’t return req, it only returns client. But, what if we add an intermediate Promise whose only purpose is injecting this value?

'use strict'; const { soap } = require('strong-soap'); const expressApp = require('express')(); const bodyParser = require('body-parser'); const url = '//www.dneonline.com/calculator.asmx?WSDL'; const clientPromise = new Promise((resolve, reject) => ( soap.createClient(url, {}, (err, client) => err ? reject(err) : resolve(client)) )); expressApp.use(bodyParser.json()) .post('/parallel-soap-invoke', (req, res) => ( /** * After clientPromise.then, where client is received, a new Promise is * created, and that Promise will resolve an object having two properties: * client and requests. */ clientPromise.then(client => ({ client, requests: req.body })) .then(invokeOperations) .then(results => res.status(200).send(results)) .catch(({ message: error }) => res.status(500).send({ error })) )) .listen(3000, () => console.log('Waiting for incoming requests.')); /** * Since the shape of the object passed to invokeOperations changed, the signature has * to change to reflect the shape of the new object. */ const invokeOperations = ({ client, requests }) => Promise.all(requests.map(request => ( new Promise((resolve, reject) => client.Add(request, (err, result) => ( err ? reject(err) : resolve(result)) )) )));

The results are exactly the same as the ones at the summary.

4. Bonus track

A generic SOAP to JSON converter for parallel SOAP invoking. The code is familiar, based on what you saw in the former sections. How about that?

'use strict'; const { soap } = require('strong-soap'); const expressApp = require('express')(); const bodyParser = require('body-parser'); const clientPromises = new Map(); expressApp.use(bodyParser.json()) .post('/parallel-soap-invoke', ({ body: { wsdlUrl, operation, requests } }, res) => ( getClient(wsdlUrl).then(client => ({ client, operation, requests })) .then(invokeOperations) .then(results => res.status(200).send(results)) .catch(({ message: error }) => res.status(500).send({ error })) )) .listen(3000, () => console.log('Waiting for incoming requests.')); const getClient = wsdlUrl => clientPromises.get(wsdlUrl) || (clientPromises.set(wsdlUrl, new Promise((resolve, reject) => ( soap.createClient(wsdlUrl, {}, (err, client) => err ? reject(err) : resolve(client)) ))).get(wsdlUrl)); const invokeOperations = ({ client, operation, requests }) => (Promise.all(requests.map(request => ( new Promise((resolve, reject) => client[operation](request, (err, result) => ( err ? reject(err) : resolve(result)) )) ))));

First use example:

POST /parallel-soap-invoke content-type: application/json { "wsdlUrl": "//www.dneonline.com/calculator.asmx?WSDL", "operation": "Add", "requests": [ { "intA": 1, "intB": 2 }, { "intA": 3, "intB": 4 }, { "intA": 5, "intB": 6 } ] } HTTP/1.1 200 [ { "AddResult": 3 }, { "AddResult": 7 }, { "AddResult": 11 } ] 

Second use example:

POST /parallel-soap-invoke content-type: application/json { "wsdlUrl": "//ws.cdyne.com/ip2geo/ip2geo.asmx?wsdl", "operation": "ResolveIP", "requests": [ { "ipAddress": "8.8.8.8", "licenseKey": "" }, { "ipAddress": "8.8.4.4", "licenseKey": "" } ] } HTTP/1.1 200 [ { "ResolveIPResult": { "Country": "United States", "Latitude": 37.75101, "Longitude": -97.822, "AreaCode": "0", "HasDaylightSavings": false, "Certainty": 90, "CountryCode": "US" } }, { "ResolveIPResult": { "Country": "United States", "Latitude": 37.75101, "Longitude": -97.822, "AreaCode": "0", "HasDaylightSavings": false, "Certainty": 90, "CountryCode": "US" } } ]

Περνάτε ψηφιακή αποσύνδεση; Σε μια αρχιτεκτονική πλήρους στοίβας JavaScript πάνω από τις παλιές υπηρεσίες, αυτό το τεχνούργημα θα μπορούσε να σας βοηθήσει να ενσωματώσετε όλες τις υπηρεσίες SOAP, να τις επεκτείνετε και να εκθέσετε μόνο το JSON. Θα μπορούσατε ακόμη και να τροποποιήσετε αυτόν τον κωδικό λίγο για να καλέσετε πολλές διαφορετικές υπηρεσίες SOAP ταυτόχρονα (αυτό θα πρέπει να είναι μόνο ένα πρόσθετο .mapκαι .reduce, όπως το βλέπω τώρα). Ή θα μπορούσατε να ενσωματώσετε τα WSDL της επιχείρησής σας σε μια βάση δεδομένων και να τα καλέσετε με βάση έναν κωδικό ή κάποιο αναγνωριστικό. Αυτό θα ήταν μόνο μία ή δύο επιπλέον υποσχέσεις για την αλυσίδα.