distributed-soap.html 27.4 KB
Newer Older
1
2
3
4
<p>Da <i>Remote Method Invocation (RMI)</i> ein Java-spezifisches Protokoll ist, kann es nicht als programmiersprachenunabhängiger Standard eingesetzt werden. Als allgemeiner Begriff für entfernte Methodenaufrufe zur Interprozesskommunikation hat sich <i>Remote Procedure Call (RPC)</i> durchgesetzt. Der Begriff RPC ist mit dem <a href="https://tools.ietf.org/html/rfc707">RFC 707</a> im Jahr 1976 lange vor dem Entstehen von RMI geprägt worden. Die anfänglichen RPC-Implementierungen sind inzwischen veraltet. Daher beschreibt der Begriff RPC heute eher die grundlegende Idee eines Methodenaufrufs auf Objekten in anderen Adressräumen und weniger ein konkretes Protokoll oder eine konkrete Implementierung.</p>

<h4>SOAP</h4>

Blanke, Daniela's avatar
Blanke, Daniela committed
5
<p>Eine konkrete RPC-Implementierung ist hingegen das XML-basierte Protokoll <i><a href="https://www.w3.org/TR/soap/">SOAP</a></i>, das seit 2000 vom <a href="https://de.wikipedia.org/wiki/World_Wide_Web_Consortium">W3C</a> als empfohlener Standard etabliert worden ist. Wie wir noch im Detail sehen werden, definiert SOAP ein XML-Schema zur Repräsentation der auszutauschenden Daten in XML und nutzt die üblichen Protokolle des <a href="https://de.wikipedia.org/wiki/Internetprotokollfamilie">TCP/IP-Modells</a> zur Nachrichtenübertragung, auf oberster Ebene i.d.R. HTTPS. Ursprünglich war SOAP ein Akronym für <i>Simple Object Access Protocol</i>. Seit der aktuellen <a href="https://www.w3.org/TR/soap12/">Version 1.2</a> soll SOAP aber nicht mehr als Abkürzung, sondern als Eigenname verstanden werden, da es subjektiv nicht unbedingt einfach (<i>simple</i>) ist und nicht ausschließlich dem Zugriff auf Objekte (<i>object access</i>) dient. Historisch geht SOAP auf das Protokoll <a href="https://en.wikipedia.org/wiki/XML-RPC">XML-RPC</a> zurück, das Microsoft und Dave Winer 1998 veröffentlicht haben.</p>
6
7
8
9
10

<span>Im Folgenden soll das Bankkontoservice-Anwendungsbeispiel aus dem vorherigen <a href="#unit-rmi" class="navigate">Kapitel zu RMI</a> in einen SOAP-Webservice überführt werden. Dabei soll die fachliche Funktionaliät des Anwendungsbeispiels möglichst nicht verändert werden. Die einzelnen Methoden des Webservice sind:</span>
<ul>
<li><code>String createAccount(String: owner)</code>: In dieser Methode wird ein neues Bankkonto für den angegebenen Eigentümer erzeugt. Die eindeutige Id des Kontos (vom Typ <code>java.util.UUID</code>) wird in ihrer String-Repräsentation zurückgegeben. Die Klasse <code>java.util.UUID</code> selbst kann nicht als Input- oder Return-Argument verwendet werden, da die Klasse keinen leeren Default-Konstruktor besitzt, der für die Objekterzeugung per Reflection API erforderlich ist.</li>
<li><code>void addAccountEntry(String id, AccountEntry entry)</code>: In dieser Methode wird einem Bankkonto ein neuer Kontoeintrag (<code>AccountEntry</code>) hinzugefügt. Ein Kontoeintrag besteht weiterhin aus einem Betreff, einem Wert und einem Buchungsdatum. Der Klasse <code>AccountEntry</code> ist ein leerer Default-Konstruktor hinzugefügt worden.</li>
Blanke, Daniela's avatar
Blanke, Daniela committed
11
<li><code>AccountEntry[] getAccountEntries(String id)</code>: Diese Methode gibt alle Kontoeinträge zu einem Konto als Array zurück. Da die Bibliothek <a href="https://javaee.github.io/jaxb-v2/">JAXB (Java Architecture for XML Binding)</a> nicht ohne Weiteres Schnittstellen wie z.B. <code>java.util.List</code> verarbeiten kann, ist das Return-Argument der Methode ein Array. JAXB übernimmt in der Java-Implementierung (s. unten) implizit die Serialisierung von Java-Objekten in eine XML-Repräsentation und die Deserialisierung in die andere Richtung.</li>
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<li><code>int getAccountBalance(String id)</code>: Diese Methode gibt den Saldo (= Summe der Werte aller Kontoeinträge) eines Kontos zurück.</li>
</ul>

<p>Es folgen entfernte Methodenaufrufe mittels SOAP für die ersten beiden Methoden des Webservice, nämlich <code>String createAccount(String: owner)</code> und <code>void addAccountEntry(String id, AccountEntry entry)</code>. Es wird jeweils exemplarisch ein HTTP-Request und die zugehörige HTTP-Response dargestellt.</p> 

<ul class="nav nav-tabs" id="soap-tabs" role="tablist">
  <li class="nav-item"><a href="#soap-tabs-m1" class="nav-link active" data-toggle="tab" role="tab">SOAP-RPC createAccount</a></li>
  <li class="nav-item"><a href="#soap-tabs-m2" class="nav-link" data-toggle="tab" role="tab">SOAP-RPC addAccountEntry</a></li>
</ul>
<div class="tab-content" id="soap-tabs-content">
  <div class="tab-pane show active" id="soap-tabs-m1" role="tabpanel">
	<pre><code class="language-xml line-numbers"><!--​---[HTTP request]---
Accept: text/xml, multipart/related
Connection: keep-alive
Host: localhost:8080
User-agent: JAX-WS RI 2.3.1
Content-type: text/xml; charset=utf-8
Soapaction: "http://de.oncampus.patterns.soap/AccountService/createAccountRequest"
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
	<S:Body>
		<ns2:createAccount xmlns:ns2="http://de.oncampus.patterns.soap/">
			<owner>Hendricks</owner>
		</ns2:createAccount>
	</S:Body>
</S:Envelope>

---[HTTP response 200]---
Content-type: text/xml; charset=utf-8
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
	<S:Body>
		<ns2:createAccountResponse xmlns:ns2="http://de.oncampus.patterns.soap/">
			<return>ea6f53f3-5381-4061-b0c9-8ad9bde2c523</return>
		</ns2:createAccountResponse>
	</S:Body>
</S:Envelope> --></code></pre>  
  </div>
  <div class="tab-pane" id="soap-tabs-m2" role="tabpanel">
	<pre><code class="language-xml line-numbers"><!--​---[HTTP request]---
Accept: text/xml, multipart/related
Connection: keep-alive
Host: localhost:8080
User-agent: JAX-WS RI 2.3.1
Content-type: text/xml; charset=utf-8
Soapaction: "http://de.oncampus.patterns.soap/AccountService/addAccountEntryRequest"
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
	<S:Body>
		<ns2:addAccountEntry xmlns:ns2="http://de.oncampus.patterns.soap/">
			<accountId>ea6f53f3-5381-4061-b0c9-8ad9bde2c523</accountId>
			<entry>
				<subject>Credit 1</subject>
				<value>50</value>
			</entry>
		</ns2:addAccountEntry>
	</S:Body>
</S:Envelope>

---[HTTP response 200]---
Content-type: text/xml; charset=utf-8
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
	<S:Body>
		<ns2:addAccountEntryResponse xmlns:ns2="http://de.oncampus.patterns.soap/"/>
	</S:Body>
</S:Envelope> --></code></pre>  
  </div>
</div>  

<p>Der Vorteil von SOAP ist, dass APIs für nahezu jede geläufige Programmiersprache existieren. Dadurch ist es möglich, dass Client und Server unabhängig voneinander in verschiedenen Programmiersprachen implementiert werden und trotzdem Nachrichten untereinander austauschen können. Die auszutauschenden Daten werden dazu automatisiert in SOAP-Nachrichten verpackt und mittels HTTP/HTTPS ausgetauscht. Jede SOAP-Nachricht ist ein gültiges XML-Dokument, das gegenüber einem zugehörigen XML-Schema validiert werden kann (vgl. Zeile 10: Referenz auf XML-Schema von SOAP Version 1.1 unter <a href="http://schemas.xmlsoap.org/soap/envelope/">http://schemas.xmlsoap.org/soap/envelope/</a>). Demzufolge enthält jede SOAP-Nachricht als Wurzelelement einen <i>Envelope</i>, der als Kindelemente einen optionalen <i>Header</i> und einen <i>Body</i> umfasst. Der <i>Body</i> einer SOAP-Nachricht enthält den eigentlichen Methodenaufruf inkl. der Argumente.</p>

<h4>Webservices und WSDL</h4>

<p>Als nächstes betrachten wir den generischen Begriff <i>Webservice</i>, der im Allgemeinen einen nicht weiter spezifizierten Mechanismus zur Maschine-zu-Maschine-Kommunikation auf Basis von HTTP/HTTPS über eine Netzwerkverbindung beschreibt. Während eine Webanwendung also ein User Interface besitzt, das im Browser dargestellt wird, bietet ein Webservice nur eine API ohne UI. Wenn SOAP als Protokoll für einen Webservice eingesetzt wird, sprechen wir explizit von einem SOAP-Webservice. Das W3C konzentriert sich in der folgenden Definition für Webservices auf genau diese SOAP-Variante, wobei auch auf alternative Webservice-Implementierungen hingewiesen wird.</p>

<div class="cite"><a href="https://www.w3.org/TR/ws-gloss/">"There are many things that might be called 'web services' in the world at large. However, [...] we will use the following definition: A web service is a software system designed to support interoperable machine-to-machine interaction over a network. It has an interface described in a machine-processable format (specifically WSDL). Other systems interact with the web service in a manner prescribed by its description using SOAP-messages, typically conveyed using HTTP with an XML serialization in conjunction with other web-related standards." (W3C)</a></div>

<p>Der Definition ist zu entnehmen, dass die Schnittstelle zwischen Client und Server im Falle eines SOAP-Webservice in einem strukturierten Format namens <i><a href="https://www.w3.org/TR/wsdl/">WSDL (Web Service Description Language)</a></i> beschrieben wird. WSDL ist ebenfalls ein W3C-Standard, der im Wesentlichen auf XML-Schemas basiert. Ein WSDL-Dokument wird vom Webservice-Anbieter unter einer URI veröffentlicht. Es wird i.d.R. generiert und nicht händisch erstellt. Ein Client kann das WSDL-Dokument parsen und sich passende DTO-Klassen erzeugen lassen, um anschließend gültige SOAP-Nachrichten an den Webservice-Anbieter senden zu können. Es folgt ein Beispiel für ein WSDL-Dokument, das die Methoden des Webservice für das obige Anwendungsbeispiel beschreibt.</p>

<ul class="nav nav-tabs" id="wsdl-tabs" role="tablist">
  <li class="nav-item"><a href="#wsdl-tabs-doc" class="nav-link active" data-toggle="tab" role="tab">WSDL-Dokument</a></li>
  <li class="nav-item"><a href="#wsdl-tabs-types" class="nav-link" data-toggle="tab" role="tab">Referenziertes XML-Schema mit Typen</a></li>
</ul>
<div class="tab-content" id="wsdl-tabs-content">
  <div class="tab-pane show active" id="wsdl-tabs-doc" role="tabpanel">
	<pre><code class="language-xml line-numbers"><!--<!-​- Generated by JAX-WS RI (http://javaee.github.io/metro-jax-ws). RI's version is JAX-WS RI 2.3.1 -​->
99
100
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://de.oncampus.patterns.soap/" name="AccountServiceFactory"  ...>	
101

102
 <types> <xsd:schema> <xsd:import schemaLocation="http://localhost:8080/AccountService?xsd=1"/> </xsd:schema> </types>
103
	
104
105
 <message name="createAccount"> <part name="owner" type="xsd:string"/> </message>
 <message name="createAccountResponse"> <part name="return" type="xsd:string"/> </message>
106
	
107
108
109
110
111
 <message name="addAccountEntry">
	<part name="accountId" type="xsd:string"/>
	<part name="entry" type="tns:accountEntry"/>
 </message>
 <message name="addAccountEntryResponse"/>
112
	
113
114
 <message name="getAccountEntries"> <part name="accountId" type="xsd:string"/> </message>
 <message name="getAccountEntriesResponse"> <part name="return" type="tns:accountEntryArray"/> </message>
115
	
116
117
118
 <message name="getAccountBalance"> <part name="accountId" type="xsd:string"/> </message>
 <message name="getAccountBalanceResponse"> <part name="return" type="xsd:int"/> </message>
 ...
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
</definitions> --></code></pre>  
  </div>
  <div class="tab-pane" id="wsdl-tabs-types" role="tabpanel">
	<pre><code class="language-xml line-numbers"><!--<!-​- Published by JAX-WS RI (http://jax-ws.java.net). RI's version is JAX-WS RI 2.3.1 -​->
<xs:schema xmlns:tns="http://de.oncampus.patterns.soap/" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0">
	<xs:complexType name="accountEntry">
		<xs:sequence>
			<xs:element name="subject" type="xs:string" minOccurs="0"/>
			<xs:element name="value" type="xs:int"/>
			<xs:element name="date" type="xs:dateTime" minOccurs="0"/>
		</xs:sequence>
	</xs:complexType>
	<xs:complexType name="accountEntryArray">
		<xs:sequence>
			<xs:element name="item" type="tns:accountEntry" minOccurs="0" maxOccurs="unbounded"/>
		</xs:sequence>
	</xs:complexType>
	...
</xs:schema> --></code></pre>  
  </div>
</div>  

<span>Zusammenfassend sind folgende Aspekte bezüglich des WSDL-Formats bemerkenswert:</span>
<ul>
<li>Jedes WSDL-Dokument ist ein wohlgeformtes und gültiges XML-Dokument. Der Bezug zum zugehörigen XML-Schema zur Validierung ist zu Beginn angegeben (Zeile 2: <a href="http://schemas.xmlsoap.org/wsdl/"><code>xmlns="http://schemas.xmlsoap.org/wsdl/"</code></a>).</li>
<li>Eigene komplexe Datentypen werden in einem separaten XML-Schema deklariert und eingebettet (Zeile 5). Das XML-Schema, das die eigenen Typen (z.B. <code>AccounEntry</code>) deklariert, ist oben im zweiten Tab dargestellt.</li>
145
<li>Für jede Methode werden die Input-Argumente und das Return-Argument spezifiziert (Zeilen 7-20). Die Bezeichner der Methoden und Argumente können vom Anbieter des Webservice sinnvoll gewählt werden.</li>
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
</ul>

<h4>JAX-WS (Java API for XML Web Services)</h4>

<p>In Java wird durch den <a href="https://jcp.org/en/jsr/detail?id=224">JSR 224</a> eine API namens JAX-WS für SOAP-Webservices definiert, deren Referenzimplementierung  (RI) das Framework <a href="http://javaee.github.io/metro-jax-ws">JAX-WS RI</a> ist. Maintainer für JAX-WS ist seit dem <a href="http://openjdk.java.net/jeps/320">Entfernen der Java EE-Module aus dem JDK</a> das Projekt <a href="https://javaee.github.io/metro/">Metro</a>. Wir werden im Folgenden sowohl ein Client-Modul als auch ein Server-Modul mittels des Frameworks JAX-WS RI implementieren, so dass beide Module eine Abhängigkeit zum <a href="https://mvnrepository.com/artifact/com.sun.xml.ws/jaxws-rt">JAX-WS RI Runtime Bundle</a> aufweisen werden. Die benötigten Annotationen von JAX-WS befinden sich im Package <code>javax.jws</code>. Es folgt zunächst der Code des Server-Moduls.</p>

<ul class="nav nav-tabs" id="soapserver-tabs" role="tablist">
  <li class="nav-item"><a href="#soapserver-tabs-starter" class="nav-link active" data-toggle="tab" role="tab">WebServiceStarter</a></li>
  <li class="nav-item"><a href="#soapserver-tabs-accountservice-impl" class="nav-link" data-toggle="tab" role="tab">AccountServiceImpl</a></li>
  <li class="nav-item"><a href="#soapserver-tabs-account" class="nav-link" data-toggle="tab" role="tab">Account</a></li>
  <li class="nav-item"><a href="#soapserver-tabs-accountentry" class="nav-link" data-toggle="tab" role="tab">AccountEntry</a></li>
  <li class="nav-item"><a href="#soapserver-tabs-exc" class="nav-link" data-toggle="tab" role="tab">AccountNotFoundException</a></li>
</ul>
<div class="tab-content" id="soapserver-tabs-content">
  <div class="tab-pane show active" id="soapserver-tabs-starter" role="tabpanel">
	<pre><code class="language-java line-numbers">package impl;

import javax.xml.ws.Endpoint;

public class WebServiceStarter {

    public static void main(String[] args) throws Exception {
		System.setProperty("com.sun.xml.ws.transport.http.HttpAdapter.dump", "true"); // dump SOAP messages to console	
	
        Endpoint endpoint = Endpoint.publish("http://localhost:8080/AccountService", new AccountServiceImpl());
        System.out.println("Hit enter to stop web service endpoint.");
        System.in.read();
        endpoint.stop();
    }
}</code></pre>  
  </div>
  <div class="tab-pane" id="soapserver-tabs-accountservice-impl" role="tabpanel">
	<pre><code class="language-java line-numbers">package impl;

import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;
// ...

@WebService(name = "AccountService", serviceName = "AccountServiceFactory")
@SOAPBinding(style = SOAPBinding.Style.RPC)
public class AccountServiceImpl {

	List&lt;Account> accounts = new ArrayList&lt;>();

	@WebMethod
	public String createAccount(@WebParam(name = "owner") String owner) {
		Account account = new Account(owner);
		accounts.add(account);
		return account.id.toString();
	}

	@WebMethod
199
200
	public void addAccountEntry(@WebParam(name = "accountId") String id, @WebParam(name = "entry") AccountEntry entry) 
		throws AccountNotFoundException {
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
		getAccountById(id).entries.add(entry);
	}

	@WebMethod(operationName = "getAccountEntries")
	public AccountEntry[] getEntries(@WebParam(name = "accountId") String id) throws AccountNotFoundException {
		List&lt;AccountEntry> entries = getAccountById(id).entries; // JAXB can't process interfaces
		return entries.toArray(new AccountEntry[entries.size()]);
	}

	@WebMethod
	public int getAccountBalance(@WebParam(name = "accountId") String id) throws AccountNotFoundException {
		return Stream.of(getEntries(id)).mapToInt(entry -> entry.value).sum();
	}

	@WebMethod(exclude = true)
	public void deleteAccount(@WebParam(name = "accountId") String id) throws AccountNotFoundException {
		accounts.remove(getAccountById(id));
	}

	Account getAccountById(String id) throws AccountNotFoundException {
221
222
		return accounts.stream().filter(a -> a.id.toString().equals(id)).findFirst()
			.orElseThrow(() -> new AccountNotFoundException());
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
	}
}</code></pre></div>
  <div class="tab-pane" id="soapserver-tabs-account" role="tabpanel">
	<pre><code class="language-java line-numbers">package dto;
// ...	

public class Account {

    UUID id;
    String owner;
    List&lt;AccountEntry> entries;

    public Account(String owner) {
        this.id = UUID.randomUUID();
        this.owner = owner;
        entries = new ArrayList&lt;>();
    }
}</code></pre></div>
  <div class="tab-pane" id="soapserver-tabs-accountentry" role="tabpanel">
	<pre><code class="language-java line-numbers">package dto;
// ...

public class AccountEntry {

    String subject;
    int value;
    Date date;

    public AccountEntry() { this.date = new Date(); }

    public AccountEntry(String subject, int value) {
        super();
        this.subject = subject;
        this.value = value;
    }
}</code></pre> 
  </div>
  <div class="tab-pane" id="soapserver-tabs-exc" role="tabpanel">
	<pre><code class="language-java line-numbers">package dto;

public class AccountNotFoundException extends Exception { }</code></pre> 
  </div>  
</div>

<ul>
<li><code>WebServiceStarter</code>: In Zeile 10 wird der Webservice gestartet und unter dem Endpunkt <a href="http://localhost:8080/AccountService">http://localhost:8080/AccountService</a> veröffentlicht. In Zeile 13 wird der Webservice wieder beendet. Die Systemeigenschaft, die in Zeile 8 aktiviert wird, sorgt lediglich dafür, dass alle ausgetauschten SOAP-Nachrichten auf die Konsole ausgegeben werden.</li>
Blanke, Daniela's avatar
Blanke, Daniela committed
269
<li><code>AccountServiceImpl</code>: Die Klasse implementiert den SOAP-Webservice. Im Gegensatz zu RMI ist ein zugehöriges Interface, das dem Client zur Verfügung gestellt werden muss, nicht unbedingt erforderlich. Jede Klasse, die als Webservice instantiiert werden soll, benötigt die Annotation <code>Webservice</code> (Zeile 8).</li>
270
271
<ul>
<li>Die optionale Annotation <code>@SOAPBinding(style = SOAPBinding.Style.RPC)</code> sorgt u.a. dafür, dass im generierten WSDL-Dokument Basisdatentypen wie Integer und String auf die entsprechenden Basisdatentypen von XML-Schema abgebildet werden, also z.B. <code>xsd:int</code> und <code>xsd:string</code>.</li>
272
273
<li>Jede öffentliche Methode (Sichtbarkeit <i>public</i>) wird in die Schnittstelle zum Client, also in das WSDL-Dokument, aufgenommen – außer sie ist mit <code>@WebMethod(exclude = true)</code> annotiert. Private Methoden werden nicht in das WSDL-Dokument aufgenommen. Aus eben diesen Gründen sind die Methoden <code>deleteAccount</code> (Zeilen 38-41) und <code>getAccountById</code> (Zeilen 43-46) nicht im obigen WSDL-Dokument wiederzufinden, während alle anderen Methoden der Klasse <code>AccountServiceImpl</code> im WSDL-Dokument enthalten sind.</li>
<li>Mittels der Annotationen <code>@WebMethod(operationName = "...")</code> und <code>@WebParam(name = "...")</code> können Methoden bzw. ihre Argumente gezielt nach außen gerichtet bezeichnet werden. Im obigen WSDL-Dokument wird z.B. die Methode <code>getEntries</code> exemplarisch in <code>getAccountEntries</code> umbenannt (Zeilen 27-28). Die Methodenargumente explizit zu bezeichnen, ist bei JAX-WS RI sinnvoll, da diese im WSDL-Dokument andernfalls schlicht <code>arg0</code>, <code>arg1</code>, ... heißen.</li>
Blanke, Daniela's avatar
Blanke, Daniela committed
274

275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
<li><code>Account</code>, <code>AccountEntry</code> und <code>AccountNotFoundException</code>: Die serverseitigen DTO-Klassen haben sich im Vergleich zum RMI-Anwendungsbeispiel nicht grundlegend geändert. Das Interface <code>java.io.Serializable</code> muss nicht mehr implementiert werden, da die Serialisierung und Deserialisierung in das XML-Format nun über die Bibliothek <a href="https://javaee.github.io/jaxb-v2/">JAXB</a> erfolgt.</li>
</ul>
</ul>

<p>Die gezeigten Code-Beispiele zu SOAP-Webservices finden sich im Verzeichnis <a href="/remote/soap" class="repo-link">/remote/soap</a> des Modul-Repository.</p>

<p>Damit ein Client diesen Webservice nutzen kann, benötigt er passende DTO-Klassen. JAX-WS sieht vor, dass diese DTO-Klassen mittels des Kommandozeilenwerkzeugs <code>wsimport</code> generiert werden. Bis Java 10 war <code>wsimport</code> Teil des JDK, seit Java 11 ist es zusammen mit den Java EE-Modulen ausgelagert worden. Heute kann es auf der <a href="https://javaee.github.io/metro-jax-ws/">Projektseite von JAX-WS</a> heruntergeladen werden. Die folgende Abbildung illustriert die Generierung der DTO-Klassen für einen Client mittels <code>wsimport</code>. Die Parameter werden in der <a href="https://javaee.github.io/metro-jax-ws/doc/user-guide/ch04.html#tools-wsimport">Dokumentation zu <code>wsimport</code></a> genauer erläutert.</p>

<img src="media/distributed_soap_wsimport.png" style="width:900px">
<label>Generieren der Datentransferobjekte (DTO) mittels wsimport</label>

<p>Anschließend kann der Client die generierten DTO-Klassen verwenden, um SOAP-Nachrichten an den Webservice zu senden und Antworten zu empfangen. Im folgenden Code des Clients wird das Stub-Objekt in Zeile 12 erzeugt. Auf diesem Stub-Objekt werden danach die folgenden RPCs ausgeführt: Es wird ein neues Konto angelegt (Zeile 14), neue Kontoeinträge hinzugefügt (Zeilen 16-24) und der Saldo und die Anzahl der Kontoeinträge abgefragt (Zeilen 26-27).</p>

<pre><code class="language-java line-numbers">package client;

import client.dto.AccountService;
import client.dto.AccountServiceFactory;
import client.dto.AccountEntry;
// ...

public class Client {

    public static void main(String[] args) {
        try {
            AccountService stub = new AccountServiceFactory().getAccountServicePort(); // create remote proxy

            String id = stub.createAccount("Hendricks");

            AccountEntry e1 = new AccountEntry();
            e1.setSubject("Credit 1");
            e1.setValue(50);
            stub.addAccountEntry(id, e1);

            AccountEntry e2 = new AccountEntry();
            e2.setSubject("Credit 2");
            e2.setValue(30);
            stub.addAccountEntry(id, e2);

            System.out.println("Account balance = " + stub.getAccountBalance(id));
            System.out.println("Account entries = " + stub.getAccountEntries(id).getItem().size());
        } catch (AccountNotFoundException_Exception e) {
            e.printStackTrace();
        }
    }
}</code></pre>

<p>Bezüglich der generierten DTO-Klassen ist zu beachten, dass diese nicht 1:1 den serverseitigen DTO-Klassen entsprechen. Es fehlen z.B. geeignete Konstruktoren, obwohl diese in den serverseitigen DTO-Klassen vorhanden sind. So müssen im obigen Code z.B. die Attribute zu einem Kontoeintrag einzeln gesetzt werden (Zeilen 17-18), weil kein entsprechender Konstruktor generiert worden ist. Es bietet sich an, die generierten DTO-Klassen über einen <a href="#unit-adapter" class="navigate">Adapter</a> an die Bedürfnisse des Clients anzupassen.</p>

In der folgenden Abbildung wird der Ablauf der Kommunikation bei SOAP-Webservices (hier speziell für JAX-WS) zusammengefasst: 
<ol>
<li>Der Anbieter (= Server) veröffentlicht seine Webservice-Implementierung unter einer bestimmten URI als Endpunkt – bei JAX-WS z.B. mittels <code>javax.xml.ws.Endpoint.publish(...)</code>.</li>
<li>Ein Client greift auf das veröffentlichte WSDL-Dokument zu, um sich passende Zugriffsklassen generieren zu lassen – bei JAX-WS z.B. mittels <code>wsimport</code>.</li>
<li>Der Client sendet mit Hilfe der generierten Zugriffsklassen SOAP-Nachrichten im XML-Format an den Server und wartet anschließend auf eine SOAP-Nachricht als Antwort des Servers. SOAP-Webservices sind auf synchrone Kommunikation ausgelegt.</li>
</ol>

<img src="media/distributed_soap.png" style="width:600px">
<label>Ablauf der Kommunikation bei SOAP-Webservices</label>

<p>SOAP-Webservices sind unabhängig von einer konkreten Laufzeitumgebung oder Programmiersprache. Das ist ein gewichtiger Vorteil zur Entkopplung der Komponenten in einem komplexen Softwaresystem. Der Preis dafür ist ein Performance-Nachteil gegenüber Protokollen wie RMI, die eine effizientere, binäre Serialisierung unterstützen. Bei starker Last kann mit RMI ein höherer Durchsatz an Nachrichten erreicht werden als mit SOAP-Webservices, da bei letzteren sämtliche Datentransferobjekte stets zwischen ihrer objektorientierten Repräsentation und XML transformiert werden müssen. Ob die Entkopplung von Komponenten oder die Maximierung des Nachrichtendurchsatzes im Fokus stehen, hängt von den konkreten Anforderungen eines Projekts ab.</p>

<h4>WS-*</h4>

<p>Im Kontext von SOAP und WSDL sind noch diverse weitere W3C/OASIS-Spezifikationen – insbesondere zur sicheren und zuverlässigen Kommunikation über Organisationsgrenzen hinaus – entstanden, die wir heute zusammenfassend und durchaus etwas despektierlich als <a href="https://de.wikipedia.org/wiki/WS-*">WS-*</a> bezeichnen. Diese Spezifikationen wie WS-Security, WS-SecureConversation, WS-ReliableMessaging, WS-Policy, WS-Discovery, usw. nehmen der grundlegenden Idee "XML über HTTP" ihre Einfachheit und haben zu einer Komplexität geführt, die zusammen mit dem Datenformat JSON das Aufkommen von <a href="#unit-rest" class="navigate">REST Webservices</a> (s. nächstes Kapitel) als populäre Alternative zu SOAP begünstigte. SOAP-Webservices sind in großen Unternehmen weiterhin verbreitet, da sie lange von Marktführern wie Microsoft und IBM vorangetrieben worden sind. Letztlich gilt aber auch für den Entwurf von Webservices, dass nach einer einfachen Klarheit in der technischen Lösung gestrebt wird. Keith Ballinger vergleicht die historische Entwicklung der Wahrnehmung von SOAP und REST unter Software-Entwicklern in seinem Artikel <a href="http://keithba.net/simplicity-and-utility-or-why-soap-lost">"Simplicity and Utility, or, Why SOAP Lost"</a> sehr anschaulich und endet mit: "Simple is important. Perhaps the most important thing. [...] YAGNI trumps future extensibility." <i>YAGNI (You Aren’t Gonna Need It)</i> ist ein bekanntes Prinzip des <a href="https://en.wikipedia.org/wiki/Extreme_programming"><i>Extreme Programming (XP)</i></a>, das besagt Funktionalität erst zu implementieren, wenn sie auch benötigt wird.</p>

<h4>Verzeichnisdienst UDDI</h4>

<p>Ähnlich der Registry bei RMI war auch für SOAP-Webservices ein zentraler Verzeichnisdienst vorgesehen, über den Webservices publiziert werden sollten. Die grundlegende Idee war, dass sich aus diesem Verzeichnisdienst ein weltweiter Marktplatz für Webservices ähnlich des <a href="https://www.apple.com/de/ios/app-store/">Apple App Store</a> oder des <a href="https://play.google.com/store">Google Play Store</a> für mobile Anwendungen entwickelt. Dieser Verzeichnisdienst hieß <i><a href="https://de.wikipedia.org/wiki/Universal_Description,_Discovery_and_Integration">UDDI (Universal Description, Discovery and Integration)</a></i>, hat sich in der Praxis aber nicht durchgesetzt. Bereits 2005 haben die initialen Unterstützer IBM, Microsoft und SAP ihren UDDI-Verzeichnisdienst geschlossen.</p>