DEV Community

nikosst
nikosst

Posted on Edited on

SOAP με απλά λόγια

Καταλαβαίνουμε το SOAP βήμα βήμα, με ένα απλό παράδειγμα προϊόντος, XML και JSON, λίγο C# και μια καθαρή εικόνα της Clean Architecture.


Έχεις ένα ηλεκτρονικό κατάστημα και θέλεις να εμφανίσεις την τιμή ενός προϊόντος. Η τιμή βρίσκεται στο σύστημα του προμηθευτή σου. Η εφαρμογή σου πρέπει να τη ζητήσει, να πάρει μια απάντηση και να εμφανίσει το σωστό ποσό στον πελάτη.

Ας υποθέσουμε ότι ο προμηθευτής χρησιμοποιεί SOAP. Θα ακολουθήσουμε αυτή τη μία επικοινωνία από την αρχή μέχρι το τέλος: ζητάμε την τιμή του προϊόντος 123 και παίρνουμε την απάντηση 19.90 EUR.

Το SOAP είναι ένας συμφωνημένος τρόπος επικοινωνίας μεταξύ εφαρμογών. Η λέξη «πρωτόκολλο» σημαίνει εδώ ότι οι δύο πλευρές ακολουθούν κοινούς κανόνες για τα μηνύματα που ανταλλάσσουν. Το SOAP χρησιμοποιεί XML για τη μορφή αυτών των μηνυμάτων. Εισαγωγή του W3C στο SOAP.

Στο παράδειγμά μας, η επικοινωνία είναι αυτή:

Το κατάστημά μας                         Ο προμηθευτής

«Πόσο κοστίζει το προϊόν 123;»  ────────►
                              ◄────────  «Κοστίζει 19,90 ευρώ»
Enter fullscreen mode Exit fullscreen mode

Οι εφαρμογές μεταφέρουν αυτή την ερώτηση και την απάντηση σε μια συγκεκριμένη δομή. Πρώτα θα καταλάβουμε την XML και μετά θα τη βάλουμε μέσα σε ένα μήνυμα SOAP.

Η XML οργανώνει τις πληροφορίες με ετικέτες. Για παράδειγμα:

<Product>
  <Id>123</Id>
  <Price>19.90</Price>
</Product>
Enter fullscreen mode Exit fullscreen mode

Μπορούμε να το διαβάσουμε σαν πρόταση: «Υπάρχει ένα προϊόν. Ο κωδικός του είναι 123 και η τιμή του είναι 19.90».

Το <Product> ανοίγει το στοιχείο του προϊόντος και το </Product> το κλείνει. Μέσα του υπάρχουν δύο μικρότερα στοιχεία: το Id και το Price. Αυτή η σχέση δημιουργεί μια δομή σαν δέντρο, όπου κάθε στοιχείο μπορεί να περιέχει άλλα στοιχεία ή μια τιμή.

Τα ονόματα έχουν σημασία: το <Price> είναι διαφορετικό από το <price>, επειδή η XML ξεχωρίζει πεζά και κεφαλαία. Επίσης, κάθε στοιχείο πρέπει να κλείνει σωστά και τα στοιχεία να μπαίνουν το ένα μέσα στο άλλο με σωστή σειρά. Βασικοί κανόνες XML.

Η XML επιτρέπει και attributes, δηλαδή πρόσθετες πληροφορίες μέσα στο αρχικό tag:

<Price currency="EUR">19.90</Price>
Enter fullscreen mode Exit fullscreen mode

Εδώ το 19.90 είναι το περιεχόμενο του στοιχείου Price. Το currency="EUR" είναι ένα attribute που μας λέει ότι το ποσό είναι σε ευρώ.

Το SOAP βάζει την πληροφορία μέσα σε έναν τυποποιημένο φάκελο. Στο δικό μας αίτημα θέλουμε να καλέσουμε τη λειτουργία GetPrice, δηλαδή «βρες τιμή», για το προϊόν 123:

<soap:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:shop="urn:shop">
  <soap:Body>
    <shop:GetPrice>
      <shop:ProductId>123</shop:ProductId>
    </shop:GetPrice>
  </soap:Body>
</soap:Envelope>
Enter fullscreen mode Exit fullscreen mode

Το διαβάζουμε από έξω προς τα μέσα:

  1. Το Envelope είναι ο εξωτερικός φάκελος του μηνύματος.
  2. Το Body είναι το μέρος που περιέχει την πληροφορία που στέλνουμε.
  3. Το GetPrice είναι η λειτουργία που ζητάμε από την υπηρεσία.
  4. Το ProductId δίνει τον κωδικό του προϊόντος: 123.

Το SOAP ορίζει τα Envelope και Body. Ο προμηθευτής ορίζει τα GetPrice και ProductId, σύμφωνα με τη δική του υπηρεσία. Έτσι ξέρουμε ποιο κομμάτι αφορά τον γενικό τρόπο επικοινωνίας και ποιο αφορά τη συγκεκριμένη δουλειά που θέλουμε να γίνει. Δομή μηνύματος SOAP.

Μπορεί να βλέπεις και ένα Header πριν από το Body. Είναι προαιρετικό και μεταφέρει πρόσθετες πληροφορίες για την επεξεργασία του μηνύματος. Στο απλό μας παράδειγμα δεν το χρειαζόμαστε.

Τα soap: και shop: είναι μικρά αναγνωριστικά που ξεχωρίζουν δύο ομάδες στοιχείων. Το soap: συνδέεται με τους κανόνες του SOAP και το shop: με τα στοιχεία της υπηρεσίας μας. Οι δηλώσεις xmlns δείχνουν σε ποιο namespace, δηλαδή σε ποια ομάδα ονομάτων, ανήκει κάθε prefix.

Κράτησε αυτή τη λεπτομέρεια για την ανάγνωση του XML: κάποιος μπορεί να χρησιμοποιεί s: αντί για soap:. Εφόσον το συνδέει με το ίδιο namespace, η σημασία παραμένει ίδια. Ο κώδικάς μας θα αναγνωρίζει το namespace και το όνομα του στοιχείου. Namespaces στην XML.

Το αίτημα φτάνει στη διεύθυνση της υπηρεσίας. Αυτή η διεύθυνση ονομάζεται endpoint. Στο παράδειγμά μας, η εφαρμογή στέλνει το XML με HTTP POST. Η υπηρεσία διαβάζει τον κωδικό 123, αναζητά την τιμή και επιστρέφει ένα νέο XML μήνυμα.

Χρησιμοποιούμε τη μορφή SOAP 1.1 σε όλα τα παραδείγματα, ώστε να ακολουθούμε σταθερά τους ίδιους κανόνες. Για μια πραγματική υπηρεσία, ο πάροχος δίνει το endpoint και τις απαιτούμενες ρυθμίσεις αποστολής. Μεταφορά SOAP 1.1 μέσω HTTP.

Η απάντηση της υποθετικής υπηρεσίας μας είναι:

<soap:Envelope
    xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:shop="urn:shop">
  <soap:Body>
    <shop:GetPriceResponse>
      <shop:Price currency="EUR">19.90</shop:Price>
    </shop:GetPriceResponse>
  </soap:Body>
</soap:Envelope>
Enter fullscreen mode Exit fullscreen mode

Ο φάκελος παραμένει ίδιος. Το περιεχόμενο του Body έχει αλλάξει: τώρα περιέχει GetPriceResponse, δηλαδή την απάντηση στη λειτουργία που καλέσαμε.

Η πληροφορία που χρειαζόμαστε βρίσκεται εδώ:

<shop:Price currency="EUR">19.90</shop:Price>
Enter fullscreen mode Exit fullscreen mode

Άρα, η τιμή είναι 19.90 και το νόμισμα EUR. Στο XML η δεκαδική τιμή γράφεται με τελεία· στην οθόνη μπορούμε να την εμφανίσουμε ως 19,90 €. Η διαδρομή που ακολουθούμε για να τη βρούμε είναι:

Envelope → Body → GetPriceResponse → Price
Enter fullscreen mode Exit fullscreen mode

Αυτή είναι και η διαδρομή που θα ακολουθήσει ο κώδικάς μας.

Αν κάτι αποτύχει, η απάντηση μπορεί να περιέχει Fault. Για παράδειγμα, ο προμηθευτής μπορεί να επιστρέψει ένα SOAP σφάλμα όταν δεν μπορεί να επεξεργαστεί το αίτημα. Το Fault βρίσκεται μέσα στο Body και περιγράφει το πρόβλημα. Ένας κανονικός SOAP client ελέγχει και αυτή την περίπτωση. Παρακάτω θα διαβάσουμε την επιτυχημένη απάντηση του παραδείγματος. Σφάλματα SOAP.

Στο C# διαβάζουμε την απάντηση σαν δέντρο. Ας υποθέσουμε ότι η HTTP κλήση έχει ήδη ολοκληρωθεί και το XML της απάντησης βρίσκεται σε μια μεταβλητή responseXml, ως κείμενο.

using System;
using System.Xml;
using System.Xml.Linq;

var document = XDocument.Parse(responseXml);

XNamespace soap = "http://schemas.xmlsoap.org/soap/envelope/";
XNamespace shop = "urn:shop";

var priceElement = document.Root?
    .Element(soap + "Body")?
    .Element(shop + "GetPriceResponse")?
    .Element(shop + "Price");

if (priceElement is null)
    throw new InvalidOperationException("Δεν βρέθηκε τιμή στην απάντηση.");

decimal price = XmlConvert.ToDecimal(priceElement.Value);
string currency = priceElement.Attribute("currency")?.Value ?? "";

Console.WriteLine($"Τιμή: {price} {currency}");
Enter fullscreen mode Exit fullscreen mode

Ας το δούμε βήμα βήμα:

  • Το XDocument.Parse(responseXml) διαβάζει το κείμενο και δημιουργεί ένα δέντρο XML.
  • Τα δύο XNamespace αντιστοιχούν στα namespaces που είδαμε στην απάντηση.
  • Το document.Root μάς δίνει το εξωτερικό στοιχείο, το Envelope.
  • Με κάθε Element(...) κατεβαίνουμε ένα επίπεδο: στο Body, μετά στο GetPriceResponse και τέλος στο Price.
  • Το priceElement.Value διαβάζει το κείμενο 19.90. Το XmlConvert.ToDecimal το μετατρέπει σε αριθμό τύπου decimal.
  • Το Attribute("currency") διαβάζει το νόμισμα EUR.

Τα ? στην αλυσίδα επιτρέπουν στην αναζήτηση να σταματήσει αν λείπει κάποιο στοιχείο. Ο έλεγχος if μάς δίνει τότε ένα κατανοητό σφάλμα, αντί να συνεχίσουμε σαν να βρήκαμε τιμή.

Έτσι, από ένα μεγάλο XML μήνυμα καταλήγουμε σε δύο απλές τιμές της εφαρμογής: έναν αριθμό και ένα νόμισμα. Ανάγνωση XML με LINQ to XML, Μετατροπή σε decimal.

Το JSON μπορεί να περιγράψει την ίδια πληροφορία με διαφορετική γραφή. Η τιμή του προϊόντος μας θα μπορούσε να εμφανίζεται έτσι:

{
  "price": 19.90,
  "currency": "EUR"
}
Enter fullscreen mode Exit fullscreen mode

Το XML χρησιμοποιεί tags και attributes. Το JSON χρησιμοποιεί ονόματα πεδίων και τιμές. Για ένα τόσο απλό μήνυμα, το JSON είναι συνήθως πιο σύντομο και διαβάζεται εύκολα ως αντικείμενο.

Αυτό που θέλουμε να περιγράψουμε XML JSON
Τιμή προϊόντος Περιεχόμενο του στοιχείου Price Τιμή του πεδίου price
Νόμισμα Attribute currency="EUR" Πεδίο "currency": "EUR"
Οργάνωση Στοιχεία μέσα σε άλλα στοιχεία Αντικείμενα και πίνακες

Η βασική διάκριση είναι η εξής: XML και JSON είναι μορφές δεδομένων. Το SOAP είναι πρωτόκολλο επικοινωνίας που χρησιμοποιεί XML. Το JSON από μόνο του δεν ορίζει πού στέλνουμε ένα μήνυμα ή ποια λειτουργία θα εκτελέσει ο παραλήπτης. Προδιαγραφή JSON.

Το WSDL μάς λέει τι περιμένει η υπηρεσία. Επιστρέφοντας στον προμηθευτή μας, πρέπει κάπως να μάθουμε ότι η λειτουργία λέγεται GetPrice, ότι δέχεται ProductId και ποια μορφή έχει η απάντηση.

Αυτές τις πληροφορίες περιγράφει το WSDL, ένα αρχείο που λειτουργεί σαν τεχνικός οδηγός της υπηρεσίας. Περιλαμβάνει τις διαθέσιμες λειτουργίες, τα μηνύματα που ανταλλάσσονται και τις διευθύνσεις επικοινωνίας. Έτσι, ο προγραμματιστής μπορεί να υλοποιήσει τη σύνδεση βάσει συγκεκριμένης συμφωνίας. Περιγραφή υπηρεσιών με WSDL.

Συχνά το project περιέχει ήδη έναν client που έχει δημιουργηθεί από το WSDL. Τον αναγνωρίζεις από τις μεθόδους που αντιστοιχούν στις λειτουργίες της υπηρεσίας. Αυτός αναλαμβάνει την κατασκευή και την ανάγνωση των μηνυμάτων, ώστε ο υπόλοιπος κώδικας να καλεί συνηθισμένες μεθόδους C#. Δημιουργία SOAP client σε .NET.

Τα DTD και XSD αφορούν τους κανόνες των δεδομένων. Ένα XML μπορεί να είναι σωστά γραμμένο, αλλά να λείπει μια πληροφορία που απαιτεί η υπηρεσία. Για παράδειγμα, το αίτημα μπορεί να έχει σωστά tags αλλά να μην περιέχει κωδικό προϊόντος.

Το DTD είναι ένας τρόπος περιγραφής της επιτρεπόμενης δομής ενός XML εγγράφου. Μπορεί να ορίζει ποια στοιχεία πρέπει να υπάρχουν και με ποια σειρά. Στα μηνύματα SOAP, όμως, απαγορεύεται η δήλωση DOCTYPE, μέσω της οποίας δηλώνεται DTD. DTD στην XML, Περιορισμός στα μηνύματα SOAP.

Στα συμβόλαια SOAP συναντάμε συνήθως XSD. Το XSD μπορεί να ορίζει και τύπους δεδομένων: για παράδειγμα, ότι η τιμή είναι δεκαδικός αριθμός. Έτσι, όπου εφαρμόζεται επικύρωση με αυτό το schema, η τιμή 19.90 μπορεί να γίνει δεκτή, ενώ το κείμενο δεκαεννέα ευρώ απορρίπτεται από το ίδιο αριθμητικό πεδίο. Εισαγωγή στο XML Schema.

Οι τρεις έννοιες συνδέονται με το παράδειγμά μας ως εξής:

Έννοια Τι μας λέει για την υπηρεσία τιμών
SOAP Πώς οργανώνουμε το μήνυμα που ανταλλάσσουμε.
WSDL Ποια λειτουργία καλούμε και πώς επικοινωνούμε μαζί της.
XSD Ποια στοιχεία και ποιους τύπους δεδομένων περιμένει το συμβόλαιο.

Στην Clean Architecture δίνουμε στον κώδικα SOAP μια συγκεκριμένη θέση. Η εφαρμογή του καταστήματος χρειάζεται την τιμή του προϊόντος. Ο τρόπος με τον οποίο μιλάμε με τον προμηθευτή ανήκει στη σύνδεση με το εξωτερικό σύστημα.

Μπορούμε να οργανώσουμε αυτή τη μικρή διαδρομή ως εξής:

Μέρος της εφαρμογής Ευθύνη στο παράδειγμά μας
Application Ορίζει ότι χρειάζεται να μπορεί να ζητήσει την τιμή ενός προϊόντος.
Infrastructure Καλεί την υπηρεσία SOAP και μετατρέπει την απάντηση σε δεδομένα της εφαρμογής.
API Δέχεται το αίτημα του δικού μας χρήστη και επιστρέφει το αποτέλεσμα.

Στο Application μπορούμε να έχουμε ένα interface, δηλαδή μια συμφωνία για τη λειτουργία «βρες τιμή». Η κλάση που υλοποιεί τη σύνδεση SOAP βρίσκεται στο Infrastructure και ακολουθεί αυτή τη συμφωνία. Ο κώδικας ανάγνωσης XML που είδαμε ανήκει εκεί, μαζί με την υπόλοιπη επικοινωνία με τον προμηθευτή.

Η βασική αρχή είναι ότι ο κώδικας της επιχειρηματικής λειτουργίας εξαρτάται από αυτή τη συμφωνία. Η υλοποίηση της εξωτερικής σύνδεσης εξαρτάται επίσης από τη συμφωνία του Application. Με αυτόν τον τρόπο, η επιχειρηματική λειτουργία μπορεί να χρησιμοποιήσει διαφορετική υλοποίηση όταν αλλάξει ο τρόπος επικοινωνίας. Η αρχή των εξαρτήσεων στην Clean Architecture.

Η διαδρομή των δεδομένων είναι πλέον ξεκάθαρη:

Ο χρήστης ζητά την τιμή
          ↓
Η εφαρμογή ζητά τα δεδομένα από τον προμηθευτή
          ↓
Η σύνδεση SOAP στέλνει το XML και διαβάζει την απάντηση
          ↓
Η εφαρμογή παίρνει την τιμή 19.90 και το νόμισμα EUR
          ↓
Ο χρήστης βλέπει 19,90 €
Enter fullscreen mode Exit fullscreen mode

Οι εταιρείες χρησιμοποιούν SOAP όταν εξυπηρετεί τις διασυνδέσεις τους. Μπορεί ο προμηθευτής να διαθέτει αυτή τη συγκεκριμένη υπηρεσία ή μια εταιρεία να έχει ήδη πολλές λειτουργικές συνδέσεις που βασίζονται σε αυτή. Η αλλαγή απαιτεί συντονισμό και ελέγχους, οπότε χρειάζεται αντίστοιχο όφελος για την επιχείρηση.

Για τον προγραμματιστή που αναλαμβάνει τη σύνδεση, το πρακτικό σημείο εκκίνησης είναι το ίδιο: βρίσκει ποια λειτουργία πρέπει να καλέσει, τι περιμένει το αίτημα και πού βρίσκεται η πληροφορία στην απάντηση. Στο δικό μας παράδειγμα, όλη αυτή η επικοινωνία εξυπηρετεί μία απλή ανάγκη: να εμφανιστεί η σωστή τιμή του προϊόντος στην οθόνη.

Top comments (0)