distributed-spring.html 37.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<p>Das <a href="https://spring.io/">Spring</a>-Framework ist bereits seit vielen Jahren ein beliebtes Framework für komplexe Java-Anwendungen im betrieblichen Umfeld. Die erste Version von Spring hat Rod Johnson bereits in 2002 vorgestellt. Das Spring-Framework hat sich im Laufe der Jahre mehrfach gewandelt und sich an moderne Programmierparadigmen, Entwurfs- und Architekturmuster angepasst. Heute wird Spring von <a href="https://en.wikipedia.org/wiki/Pivotal_Software">Pivotal Software</a> weiterentwickelt.</p> 

<h4>Inversion of Control</h4>

<p>Im Kapitel zu <a href="#unit-dependency_injection" class="navigate">Dependency Injection</a> ist bereits das grundlegende Paradigma eines jeden Frameworks genannt worden, nämlich <i>Inversion of Control (IoC)</i>. Ein Framework gibt einer Anwendung eine grundlegende Struktur vor, also eine Architektur, und steuert den Kontrollfluss der Anwendung. Der Anwendungsentwickler implementiert fachliche Methoden, die er beim Framework an vordefinierten Stellen registriert. Das Framework übernimmt die Steuerung des Kontrollflusses zur Laufzeit und wird die fachlichen Methoden/Anweisungen – ausgelöst durch bestimmte Ereignisse – aufrufen. IoC heißt also, dass die eigenen fachlichen Methoden/Anweisungen in definierte Callback-Methoden eingebettet und dort passiv aufgerufen werden, anstatt dass der Anwendungsentwickler aktiv den Kontrollfluss der Anwendung steuert. Das Spring-Framework hat maßgeblich zur Verbreitung des IoC-Paradigmas beigetragen. Das Spring-Framework selbst übernimmt die Aufgabe des IoC-Containers und verwaltet sogenannte <i>Beans</i>. Alle Klassen, die mit der Spring-Annotation <code>@Component</code> oder einer ihrer Spezialisierungen wie <code>@Service</code>, <code>@Repository</code> oder <code>@Controller</code> versehen sind, werden von Spring automatisch als Beans erkannt und registriert. Die registrierten Beans können zur Laufzeit über das Singleton-Objekt <code>ApplicationContext</code> erreicht werden (s. Kapitel <a href="#unit-dependency_injection" class="navigate">Dependency Injection</a>).</p>

<h4>REST und ORM mit Spring</h4>

<p>Es soll im Folgenden eine REST-API mit <a href="https://spring.io/projects/spring-data">Spring Data</a> entwickelt werden, die die verwalteten Objekte mittels JPA in einem relationalen Datenbanksystem speichert. Als Datenbanksystem wird MariaDB ausgewählt. Damit ergeben sich für das Projekt folgende Abhängigkeiten:</p>

<ul>
<li><a href="https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-rest">Spring Boot Data REST Starter</a></li>
<li><a href="https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa">Spring Boot Data JPA Starter</a></li>
<li><a href="https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client">MariaDB Java Client</a></li>
<li>optional <a href="https://mvnrepository.com/artifact/org.projectlombok/lombok">Lombok</a></li>
</ul>

<span>Bevor wir im Folgenden eigene Beispiele vorstellen, soll an dieser Stelle auf die guten Tutorials auf der Spring-Webseite hingewiesen werden:</span>
<ul>
<li><a href="https://spring.io/guides/gs/accessing-data-jpa/">Einstieg in Spring Data JPA</a></li>
<li><a href="https://spring.io/guides/gs/accessing-data-rest/">Einstieg in Spring Data REST</a></li>
</ul>

24
<p>Die zu entwickelnde REST-API soll Objekte zu den folgenden Entitätsklassen bereitstellen: <code>User</code>, <code>Product</code> und <code>Booking</code>. Der Zusammenhang der Klassen ist in der nächsten Abbildung veranschaulicht. Die API bildet die Datenhaltung für eine minimale Shop-Anwendung ab, in der authentifizierte Anwender (<code>User</code>) Produkte (<code>Product</code>) aus einem Produktkatalog auswählen und für diese beliebig viele Buchungen (<code>Booking</code>) anlegen und wieder entfernen können. Die hier entwickelte API soll in den folgenden Kapiteln zu den UI-Frameworks als gemeinsame Basis genutzt werden, d.h. die später vorgestellten Clients in <a href="#unit-javafx" class="navigate">JavaFX</a> und <a href="#unit-web" class="navigate">JQuery/Angular</a> greifen jeweils auf diese API zu.</p>
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

<img src="media/distributed_spring_entities.png" style="width:540px">
<label>Datenmodell der entwickelten REST-API mit Spring Data</label>

<ul class="nav nav-tabs" id="spring1-tabs" role="tablist">
  <li class="nav-item"><a href="#spring1-tabs-product" class="nav-link active" data-toggle="tab" role="tab">Product</a></li>
  <li class="nav-item"><a href="#spring1-tabs-booking" class="nav-link" data-toggle="tab" role="tab">Booking</a></li>
  <li class="nav-item"><a href="#spring1-tabs-user" class="nav-link" data-toggle="tab" role="tab">User</a></li>  
  <li class="nav-item"><a href="#spring1-tabs-id" class="nav-link" data-toggle="tab" role="tab">IdentifiedEntity</a></li>   
</ul>
<div class="tab-content" id="spring1-tabs-content">
  <div class="tab-pane show active" id="spring1-tabs-product" role="tabpanel">
	<pre><code class="language-java line-numbers">@Entity @Getter @Setter
public class Product extends IdentifiedEntity {

	@Column(length = 50, nullable = false)
	String title;

	@Lob
	String description;

	@Lob
	@JsonIgnore
	byte[] image;

	@Column(nullable = false)
	Integer price;

	Boolean instock;
}</code></pre>  
  </div>
  <div class="tab-pane" id="spring1-tabs-booking" role="tabpanel">
	<pre><code class="language-java line-numbers">@Entity @Getter @Setter
public class Booking extends IdentifiedEntity {

    @Temporal(TemporalType.DATE)
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Europe/Berlin")
    Date date = new Date();

    @Column(name = "bookingprice", nullable = false)
    Integer bookingPrice;	
	
    @ManyToOne
    @JoinColumn(name = "user_id")
    User user;

    @ManyToOne
    @JoinColumn(name = "product_id")
    Product product;
}</code></pre>  
  </div>
  <div class="tab-pane" id="spring1-tabs-user" role="tabpanel">
	<pre><code class="language-java line-numbers">@Entity @Getter @Setter
public class User extends IdentifiedEntity {

    @Column(length = 20, nullable = false)
    String name;

    @Column(length = 100, nullable = false)
    String email;

    @Column(name = "password_hash", length = 100, nullable = false)
    String passwordHash;

    @OneToMany(mappedBy = "user")
    List&lt;Booking> bookings;
}</code></pre>  
  </div>
  <div class="tab-pane" id="spring1-tabs-id" role="tabpanel">
	<pre><code class="language-java line-numbers">@MappedSuperclass @Getter @Setter
public abstract class IdentifiedEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    Integer id;
}</code></pre>  
  </div> 
</div> 

<p>Der einfachste Weg mit <i>Spring Data</i> eine REST-API zu erzeugen, ist es das <a href="https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html"><code>CrudRepository</code></a>-Interface zu erweitern. In dem folgenden Code-Beispiel wird dieses Vorgehen demonstriert, indem das leere Interface <code>BookingRepository&lt;Booking, Integer></code> das generische <code>CrudRepository&lt;T, ID></code> erweitert und dadurch die in der <a href="https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html"><code>CrudRepository</code>-Dokumentation</a> genannten Methoden erhält. Das Framework erstellt zur Laufzeit eine passende Implementierung für das <code>BookingRepository&lt;Booking, Integer></code>, wobei <code>Booking</code> die zu verwaltende Entitätsklasse angibt und <code>Integer</code> den Typ des Id-Attributs dieser Klasse.</p>

<pre><code class="language-java line-numbers">import org.springframework.data.repository.CrudRepository;
// ...

public interface BookingRepository extends CrudRepository&lt;Booking, Integer> {
}</code></pre>

<p>Für jedes konkrete <code>CrudRepository</code> werden automatisch sogenannte <i>CRUD</i>-Endpunkte (<i>Create</i>, <i>Read</i>, <i>Update</i> und <i>Delete</i>) in der REST-API erzeugt, d.h. in diesem Fall werden unter dem Pfad <code>/bookings</code> die HTTP-Methoden POST und GET unterstützt und unter dem Pfad <code>/bookings/&lt;id></code> die HTTP-Methoden GET, PUT und DELETE. Folgend wird ein HTTP-Request und die zugehörige HTTP-Response der mit <i>Spring Data REST</i> generierten REST-API dargestellt. Es wird mittels POST eine neue Buchung angelegt, wobei auf bereits vorab existierende Ressourcen – wie hier das gebuchte Produkt und der buchende User – über deren URI verwiesen werden kann.</p>

<pre><code class="language-json line-numbers"><!--​---[HTTP POST request]---
Request URL: http://localhost:8080/bookings
Content-type: application/json; charset=utf-8
{
    "product": "http://localhost:8080/products/1",
    "user": "http://localhost:8080/users/2",
    "bookingPrice": 300
}

---[HTTP response 201]---
Content-type: application/hal+json; charset=utf-8
Location: http://localhost:8080/bookings/1
{
    "date": "2019-12-31",
    "bookingPrice": 300,
    "_links": {
        "self": {"href": "http://localhost:8080/bookings/1"},
        "user": {"href": "http://localhost:8080/bookings/1/user"},
        "product": {"href": "http://localhost:8080/bookings/1/product"}
    }
} --></code></pre>

<p>Der Body der HTTP-Response enthält zwar das Datum und den Preis zu einer Buchung, aber nicht die neu vergebene Id und keine weiteren Attribute des referenzierten Produkts und des referenzierten Users. Die Id wird über den HTTP-Header <code>Location</code> bekanntgegeben. Die Referenzen auf Produkt und User können über die angegebenen Links aufgelöst werden. <i>Spring Data REST</i> erlaubt alternativ die Erstellung von <a href="https://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts">Projektionen</a> für Entitätsklassen, über die deren Ausgabe angepasst werden kann. Das folgende Code-Beispiel ergänzt die Ausgabe einer Buchung um die Details des gebuchten Produkts, so dass diese nicht über einen weiteren HTTP-Request angefragt werden müssen.</p>

<ul class="nav nav-tabs" id="spring2-tabs" role="tablist">
  <li class="nav-item"><a href="#spring2-tabs-projection" class="nav-link active" data-toggle="tab" role="tab">BookingInlineProduct</a></li>
  <li class="nav-item"><a href="#spring2-tabs-repo" class="nav-link" data-toggle="tab" role="tab">BookingRepository</a></li>  
  <li class="nav-item"><a href="#spring2-tabs-http1" class="nav-link" data-toggle="tab" role="tab">HTTP-Request <code>/bookings/&lt;id></code></a></li>
</ul>
<div class="tab-content" id="spring2-tabs-content">
  <div class="tab-pane show active" id="spring2-tabs-projection" role="tabpanel">
	<pre><code class="language-java line-numbers">@Projection(name = "inlineProduct", types = {Booking.class})
public interface BookingInlineProduct {
    String getDate();
    int getBookingPrice();
    Product getProduct();
}</code></pre></div>
  <div class="tab-pane" id="spring2-tabs-repo" role="tabpanel">
	<pre><code class="language-java line-numbers">@RepositoryRestResource(excerptProjection = BookingInlineProduct.class)
public interface BookingRepository extends CrudRepository&lt;Booking, Integer> {
}</code></pre></div>
  <div class="tab-pane" id="spring2-tabs-http1" role="tabpanel">
	<pre><code class="language-json line-numbers"><!--​---[HTTP GET request]---
Request URL: http://localhost:8080/bookings/1?projection=inlineProduct

---[HTTP response 200]---
Content-type: application/hal+json; charset=utf-8
{
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
  "id": 1,
  "date": "2019-12-31",
  "product": {
    "title": "Azzam",
    "description": "Die Azzam ist mit einer Länge von 180 m die längste private Mega-Yacht der Welt. [...]",
    "price": 181,
    "instock": true
  },
  "bookingPrice": 300,
  "_links": {
    "self": { "href": "http://localhost:8080/bookings/1" },
    "booking": { "href": "http://localhost:8080/bookings/1{?projection}", "templated": true },
    "user": { "href": "http://localhost:8080/bookings/1/user" },
    "product": { "href": "http://localhost:8080/bookings/1/product" }
  }
176
177
178
} --></code></pre></div>	
</div>

179
<p>Dass die HTTP-Response nicht nur die Daten selbst, sondern auch weiterführende Links zur Navigation innerhalb der API enthält, entspricht dem sogenannten <a href="http://restcookbook.com/Basics/hateoas/">HATEOAS</a>-Prinzip: <i>Hypermedia As The Engine Of Application State</i>. Da der Client bei einer REST-API (im Gegensatz zur WSDL bei SOAP-Webservices) keine formale Schnittstellenbeschreibung erhält, sind die Entwickler, die eine REST-API aufrufen möchten, auf eine gute Dokumentation angewiesen, in der die mögliche Parametrisierung der API-Endpunkte, die über die Konventionen hinausgeht, beschrieben wird. Die grundlegende Idee von HATEOAS ist es, diese Dokumentation in die API selbst einzubetten. Daher werden neben den angefragten Daten auch Links zurückgegeben, die potentielle Transitionen ausgehend von der aktuellen Anfrage zur nächsten Anfrage angeben. Die REST-API wird auf diese Weise als Zustandsautomat betrachtet, durch den mittels Hypermedia (= Repräsentation einer Ressource inkl. Links) navigiert werden kann. Die nächste Abbildung zeigt einen kleinen Ausschnitt einer REST-API als Zustandsautomat.</p>
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

<img src="media/distributed_spring_hateoas.png" style="width:600px">
<label>REST-API als Zustandsautomat</label>

<p>In der Repräsentation einer Ressource im HTML-Format sind wir Links über die Attribute <code>href</code> oder <code>src</code> gewohnt. Gemäß HATEOAS sind vergleichbare <i>Hyperlinks</i> auch bei anderen Formaten wie JSON oder XML zu ergänzen. Entsprechend hat Leonard Richardson ein nach ihm benanntes Reifegrad-Modell für REST-APIs entwickelt, das in der folgenden Abbildung dargestellt ist.</p>

<img src="media/distributed_spring_richardson.png" style="width:600px">
<label>Richardson Maturity Model für API-Design</label>

<p>In einem Interface, das das <code>CrudRepository</code> erweitert, können zusätzlich eigene Methoden spezifiziert werden. Für diese <i>Query Methods</i> existieren leicht interpretierbare Bezeichnungskonventionen, entsprechend derer eine passende Implementierung durch das Framework generiert wird. Falls das Repository mittels JPA an eine relationale Datenbank gebunden ist, werden z.B. je nach Methodenbezeichnung bestimmte JPQL-Anfragen erzeugt. In der <a href="https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods">Dokumentation zu <i>Spring Data JPA</i></a> ist genau beschrieben, wie die <i>Query Methods</i> zu bezeichnen sind und welche JPQL-Anfrage sich daraus ergibt. Das im nächsten Code-Beispiel dargestellte Interface <code>ProductRepository</code> ergänzt z.B. Methoden zur Filterung der ausgegebenen Produkte über ihren Titel, ihre Beschreibung (Volltextsuche) und ihren Preis (Angabe einer Obergrenze).</p>

<pre><code class="language-java line-numbers">import org.springframework.data.repository.PagingAndSortingRepository;
// ...

public interface ProductRepository extends PagingAndSortingRepository&lt;Product, Integer> {

    @RestResource(path = "byTitle")
    List&lt;Product> findByTitle(@Param(value = "title") String title);

    @RestResource(path = "inDescription")
    List&lt;Product> findByDescriptionContainingIgnoreCase(@Param(value = "pattern") String pattern);

    @RestResource(path = "maxPrice")
    List&lt;Product> findByPriceLessThanEqual(@Param(value = "price") Integer price);
}</code></pre>

<p>Das folgende Video demonstriert die resultierende REST-API dieses Kapitels, indem exemplarische Anfragen mittels Postman ausgeführt werden.</p>

<video controls><source src="media/Spring-REST-Postman.mp4" type="video/mp4"></video>
<label>Testen einer mit Spring Data generierten REST-API</label>

<p>Eine Erweiterung des <code>CrudRepository</code> ist das oben bereits verwendete <a href="https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html"><code>PagingAndSortingRepository</code></a>, das entsprechend seiner Bezeichnung erlaubt, die Menge der Elemente, die mittels der GET-Methode für eine Collection-Ressource zurückgeben werden, über URL-Parameter in paginierte Abschnitte zu teilen und zu sortieren. Der folgend dargestellte HTTP-Request enthält URL-Parameter zur Paginierung und zur Sortierung, so dass in der zugehörigen HTTP-Response nur 3 von 15 Produkten ausgegeben werden. Die weiterführenden Links zur Navigation durch die API gemäß des HATEOAS-Prinzips sind deutlich zu erkennen.</p>

<pre><code class="language-json line-numbers"><!--​---[HTTP GET request]---
Request URL: http://localhost:8080/products?sort=title&page=0&size=3

---[HTTP response 200]---
Content-type: application/hal+json; charset=utf-8
{
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
  "_embedded": {
    "products": [
      {
        "title": "Al Mirqab", "description": "Die Al Mirqab wurde 2004 in Auftrag [...]", "price": 133, "instock": true,
        "_links": { "self": { "href": "http://localhost:8080/products/13" } }
      },
      {
        "title": "Al Said", "description": "Die Al Said ist mit einer Länge von 155 m [...]", "price": 155, "instock": true,
        "_links": { "self": { "href": "http://localhost:8080/products/4" } }
      },
      {
        "title": "Al Salamah", "description": "Die Al Salamah ist eine 139 m lange Yacht [...]", "price": 139, "instock": false,
        "_links": { "self": { "href": "http://localhost:8080/products/6" } }
      }
    ]
  },
  "_links": {
    "first": { "href": "http://localhost:8080/products?page=0&size=3&sort=title,asc" },
    "next": { "href": "http://localhost:8080/products?page=1&size=3&sort=title,asc" },
    "last": { "href": "http://localhost:8080/products?page=4&size=3&sort=title,asc" },
    "search": { "href": "http://localhost:8080/products/search" }
240
    },
241
  "page": { "size": 3, "totalElements": 15, "totalPages": 5, "number": 0 }
242
243
} --></code></pre>

244
<p>Die automatisch von Spring generierten Ressourcen-Methoden für ein <code>CrudRepository</code> können über verschiedene Wege angepasst werden. Es kann eine Klasse angelegt werden, die das Interface implementiert und die Methoden überschreibt. Alternativ kann eine Klasse, versehen mit der Annotation <code>@RestController</code>, angelegt und auf dem Pfad der Ressource registriert werden. Beide Wege erlauben das Verhalten der HTTP-Methoden individuell anzupassen. Im folgenden Code-Beispiel überschreibt die Klasse <code>UserController</code> die auf dem Pfad <code>/users</code> registrierten Methoden für POST und GET des Interface <code>UserRepository</code> (Zeilen 4-5). Bei einer GET-Anfrage werden z.B. nur noch die Namen der vorhandenen User ohne weitere Attribute der Klasse ausgegeben (Zeilen 22-26). Die Methode <code>findByName</code> im Interface <code>UserRepository</code> ist ohnehin nicht Teil der REST-API, da sie nicht mit einer Annotation <code>@RestResource</code> versehen ist. Im <code>UserRepository</code> werden explizit noch die Methoden GET und DELETE für den Pfad <code>/users/&lt;id></code> deaktiviert.</p>
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
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


<ul class="nav nav-tabs" id="spring3-tabs" role="tablist">
  <li class="nav-item"><a href="#spring3-tabs-controller" class="nav-link active" data-toggle="tab" role="tab">UserController</a></li>
  <li class="nav-item"><a href="#spring3-tabs-repo" class="nav-link" data-toggle="tab" role="tab">UserRepository</a></li> 
  <li class="nav-item"><a href="#spring3-tabs-http1" class="nav-link" data-toggle="tab" role="tab">HTTP-Request <code>/users</code></a></li>
  <li class="nav-item"><a href="#spring3-tabs-http2" class="nav-link" data-toggle="tab" role="tab">HTTP-Request <code>/users/&lt;id></code></a></li>
</ul>
<div class="tab-content" id="spring3-tabs-content">
  <div class="tab-pane show active" id="spring3-tabs-controller" role="tabpanel">
	<pre><code class="language-java line-numbers">import org.springframework.web.bind.annotation.RestController;
// ...

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;

    // registers a new user
    @PostMapping
    public void register(@RequestBody User user) {
        user.setPasswordHash(bCryptPasswordEncoder.encode(user.getPasswordHash()));
        userRepository.save(user);
    }

    // provides only the user names
    @GetMapping
    public List&lt;String> getUserNames() {
        Stream&lt;User> users = StreamSupport.stream(userRepository.findAll().spliterator(), false);
        return users.map(User::getName).sorted().collect(Collectors.toList());
    }
}</code></pre></div>
  <div class="tab-pane" id="spring3-tabs-repo" role="tabpanel">
	<pre><code class="language-java line-numbers">public interface UserRepository extends CrudRepository&lt;User, Integer> {
   User findByName(@Param(value = "name") String name);
   
   @Override
   @RestResource(exported = false)
   Optional&lt;User> findById(Integer id);   
   
   @Override
   @RestResource(exported = false)
   void delete(User user);  
}</code></pre></div>
  <div class="tab-pane" id="spring3-tabs-http1" role="tabpanel">
	<pre><code class="language-json line-numbers"><!--​---[HTTP GET request]---
Request URL: http://localhost:8080/users

---[HTTP response 200]---
Content-type: application/json; charset=utf-8
[ "Alice", "Bob" ] --></code></pre></div>
  <div class="tab-pane" id="spring3-tabs-http2" role="tabpanel">
	<pre><code class="language-json line-numbers"><!--​---[HTTP GET request]---
Request URL: http://localhost:8080/users/1

---[HTTP response 405]--- --></code></pre></div>
</div>

<p>Für ausgewählte Endpunkte der entwickelten REST-API kann der Zugriff mittels <a href="https://spring.io/projects/spring-security">Spring Security</a> abgesichert werden. Falls JSON Web Tokens (JWT) zur Zugangskontrolle eingesetzt werden sollen, findet sich im <a href="https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/">Blog von Auth0</a> eine umfassende Einführung, an der sich auch die Implementierung im Package <code>shop.security</code> des begleitenden Projekts orientiert. Es entstehen dadurch Abhängigkeiten des Projekts zu <a href="https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security">Spring Boot Security Starter</a> und <a href="https://mvnrepository.com/artifact/com.auth0/java-jwt">Java JWT</a>.</p>


<h4>Reaktive Web-Anwendungen mit Spring</h4>

313
<p>Das ursprüngliche Spring-Framework zur Entwicklung von Web-Anwendungen auf Basis der <a href="https://jcp.org/en/jsr/detail?id=369">Servlet API</a> nennt sich <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html">Spring MVC</a>. Neu in der aktuellen Version 5 des Spring-Frameworks ist die Unterstützung der <a href="http://www.reactive-streams.org/">Reactive Streams API</a> auf Basis der Bibliothek <a href="https://projectreactor.io/">Reactor</a>. Dieser alternative Architekturansatz nennt sich <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html">Spring WebFlux</a> und ist auf asynchrone und nicht-blockierende Kommunikation ausgerichtet. Die folgende Definition aus der Spring-Dokumentation erklärt, was wir unter dem Begriff <a href="https://de.wikipedia.org/wiki/Reaktive_Programmierung">Reaktive Programmierung</a> verstehen.</p>
314
315
316
317
318
319
320
321
322
323
324
325
326
327

<div class="cite"><a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html">"The term, “reactive,” refers to programming models that are built around reacting to change — network components reacting to I/O events, UI controllers reacting to mouse events, and others. In that sense, non-blocking is reactive, because, instead of being blocked, we are now in the mode of reacting to notifications as operations complete or data becomes available. 
There is also another important mechanism that we on the Spring team associate with “reactive” and that is non-blocking back pressure. In synchronous, imperative code, blocking calls serve as a natural form of back pressure that forces the caller to wait. In non-blocking code, it becomes important to control the rate of events so that a fast producer does not overwhelm its destination." (Spring Docs)</a></div>

<p>Voraussetzung ist eine reaktive Datenquelle, die als <i>Publisher</i> bei ihr registrierte <i>Subscriber</i> über neue oder veränderte Daten benachrichtigen kann, wie z.B. <a href="https://www.mongodb.com/">MongoDB</a>, <a href="https://redis.io/">Redis</a> oder <a href="http://cassandra.apache.org/">Cassandra</a>. JDBC ist nicht darauf ausgelegt. Reaktive Programmierung ist insbesondere für Anwendungsfälle sinnvoll, in denen kontinuierliche, unbegrenzte Datenströme verarbeitet und in einem User Interface dargestellt werden müssen. Die folgende Abbildung stellt Spring MVC und Spring WebFlux einander gegenüber.</p>

<img src="media/diagram-boot-reactor.svg" style="width:600px">
<label>Gegenüberstellung von Spring MVC und Spring WebFlux</label>
<span class="source"><a href="https://spring.io/">Pivotal Software: Spring Framework 5, https://spring.io/.</a></span>

<p><i>Spring WebFlux</i>  übernimmt die Begriffe <i>Flux</i> und <i>Mono</i> aus der Bibliothek <a href="https://projectreactor.io/">Reactor</a>. Ein <i>Flux</i> ist ein generischer Datenstrom (<i>Stream</i>) der 0..N Elemente emittieren kann, ein <i>Mono</i> hingegen nur 0..1 Element. Die Klassen <code>reactor.core.publisher.Flux</code> und <code>reactor.core.publisher.Mono</code> implementieren das Interface <code>org.reactivestreams.Publisher</code>, das lediglich die Methode <code>void subscribe(Subscriber subscriber)</code> spezifiziert. Ein Nachrichtenempfänger (<i>Subscriber</i>) kann sich demzufolge bei einem <code>Flux</code>- oder einem <code>Mono</code>-Objekt registrieren, um kontinuierlich mit neuen Nachrichten versorgt zu werden. Spring ermöglicht es, <code>Flux</code>- oder <code>Mono</code>-Objekte einem Client über eine API zur Verfügung zu stellen, und erzeugt dazu eine <a href="https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface"><code>EventSource</code></a> gemäß des Protokolls <a href="https://html.spec.whatwg.org/multipage/server-sent-events.html">Server-Sent Events (SSE)</a>. Das folgende Code-Beispiel verdeutlicht wie mittels <i>Spring WebFlux</i> ein endlicher Datenstrom (<i>Stream</i>) erzeugt wird, in dem verzögert bis zu einem bestimmten Zielwert hochgezählt wird. Der Datenstrom endet, wenn der Zielwert erreicht ist.</p>

<ul class="nav nav-tabs" id="flux1-tabs" role="tablist">
  <li class="nav-item"><a href="#flux1-tabs-controller" class="nav-link active" data-toggle="tab" role="tab">CounterController</a></li>
328
  <li class="nav-item"><a href="#flux1-tabs-client" class="nav-link" data-toggle="tab" role="tab">counter.html</a></li>
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
</ul>
<div class="tab-content" id="flux1-tabs-content">
  <div class="tab-pane show active" id="flux1-tabs-controller" role="tabpanel">
	<pre><code class="language-java line-numbers">import reactor.core.publisher.Flux;
// ...

@RestController
@RequestMapping("/count")
public class CounterController {

    @GetMapping(path = "/{number}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    @CrossOrigin
    public Flux&lt;Integer> countToNumber(@PathVariable("number") int number) {

        Flux&lt;Integer> numbers = Flux.create(sink -> {

            Runnable counter = () -> {
                for (int i = 1; i &lt;= number; i++) {
                    sink.next(i); // send number to client
                    try {
                        TimeUnit.SECONDS.sleep(1); // delay counting
                    } catch (InterruptedException e) { }
                }
                sink.complete(); // counting is completed, stream ends
            };

            new Thread(counter).start();
        });
        return numbers;
    }
}</code></pre></div>
  <div class="tab-pane" id="flux1-tabs-client" role="tabpanel">
	<pre><code class="language-html line-numbers"><!--​<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
        <script>
            $(document).ready(function() {
                var source = new EventSource("http://localhost:8080/count/10");
                source.onmessage = function(event) {
                    $("#count").append(event.data + " ... ");
                }
                source.onerror = function(event) {
                    source.close(); // client tries to reconnect if source is not closed
                    $("#count").append("finished! ");
                }
            });
        </script>
    </head>
    <body>
        <label id="count"></label>
    </body>
</html> --></code></pre></div>
</div>

<ul>
	<li>Die Rückgabe der API unter dem Pfad <code>/count/&lt;n></code> ist ein <code>Flux&lt;Integer></code> (Zeile 10). Der zurückgegebene MIME-Typ ist explizit angegeben als <code>text/event-stream</code> (Zeile 8).</li>
	<li>In Zeile 24 wird ein Thread gestartet, dessen <code>Runnable</code> eine For-Schleife von 1..n durchläuft (Zeilen 15-20). Je Iteration wird über die Senke des Flux eine Zahl emittiert (Zeile 16) und anschließend für 1 Sek. pausiert (Zeilen 17-19).</li>
	<li>In Zeile 21 wird der Datenstrom beendet. Er ist daher endlich.</li>
	<li>Der Client in <code>counter.html</code> baut eine Verbindung zur EventSource auf (Zeile 6). Die EventSource emittiert Nachrichten an den Client über die EventHandler-Funktion (Zeilen 7-9), die auf dem Attribut <code>onmessage</code> registriert ist.</li>
	<li>Wenn der Datenstrom serverseitig geschlossen wird oder in anderen Fehlerfällen, wird die EventSource geschlossen (Zeilen 10-13). Andernfalls würde der Client gemäß SSE-Protokoll versuchen die Verbindung neu aufzubauen.</li>
</ul>

391
<p>Im nächsten Code-Beispiel soll die Quelle eines Datenstroms ein MongoDB-Datenbanksystem sein. Jedes Mal, wenn neue Dokumente in eine Collection innerhalb der MongoDB-Datenbank eingefügt werden, soll die serverseitige Komponente auf Basis von <i>Spring WebFlux</i> einen registrierten Client darüber benachrichtigen. Die Dokumente könnten z.B. Tweets sein, die aus einer unendlichen externen Quelle bezogen werden. Die Architektur entspricht damit durchgängig dem reaktiven Programmiermodell.</p> 
392
393
394
395
396
397

<ul class="nav nav-tabs" id="flux2-tabs" role="tablist">
  <li class="nav-item"><a href="#flux2-tabs-controller" class="nav-link active" data-toggle="tab" role="tab">TweetController</a></li>
  <li class="nav-item"><a href="#flux2-tabs-repo" class="nav-link" data-toggle="tab" role="tab">TweetRepository</a></li>  
  <li class="nav-item"><a href="#flux2-tabs-model" class="nav-link" data-toggle="tab" role="tab">Tweet</a></li>
    <li class="nav-item"><a href="#flux2-tabs-starter" class="nav-link" data-toggle="tab" role="tab">TweetStarter</a></li>
398
  <li class="nav-item"><a href="#flux2-tabs-client" class="nav-link" data-toggle="tab" role="tab">tweets.html</a></li>
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
</ul>
<div class="tab-content" id="flux2-tabs-content">
  <div class="tab-pane show active" id="flux2-tabs-controller" role="tabpanel">
	<pre><code class="language-java line-numbers">import reactor.core.publisher.Flux;
// ...

@RestController
public class TweetController {

    @Autowired
    TweetRepository tweetRepository;

    @GetMapping("/tweets") @CrossOrigin
    public Flux&lt;Tweet> getTweets() {
        return tweetRepository.findTweetsBy(); // return infinite stream
    }
}</code></pre></div>
  <div class="tab-pane" id="flux2-tabs-repo" role="tabpanel">
	<pre><code class="language-java line-numbers">import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.data.mongodb.repository.Tailable;
// ...

public interface TweetRepository extends ReactiveMongoRepository&lt;Tweet, String> {

    @Tailable
    Flux&lt;Tweet> findTweetsBy(); // infinite stream as annotated by @Tailable
}</code></pre></div>
  <div class="tab-pane" id="flux2-tabs-model" role="tabpanel">
	<pre><code class="language-java line-numbers">import org.springframework.data.mongodb.core.mapping.Document;
// ...

@Document(collection = Tweet.COLLECTION_NAME) @Getter @Setter
public class Tweet {

	public final static String COLLECTION_NAME = "tweet";

	@Id
	String id;
	String text;
	Date date;

	public Tweet(String text) {
		this.text = text;
		this.date = new Date();
	}
}</code></pre></div>
  <div class="tab-pane" id="flux2-tabs-starter" role="tabpanel">
	<pre><code class="language-java line-numbers">@SpringBootApplication
public class TweetStarter implements CommandLineRunner {

    @Autowired
    TweetRepository tweetRepository;

    @Autowired
    ReactiveMongoTemplate reactiveMongoTemplate;

    public static void main(String[] args) {
        SpringApplication.run(TweetStarter.class, args);
    }

    @Override
    public void run(String... args) throws Exception {

        // create a capped collection which supports tailable cursors, limited to 5 documents here
        reactiveMongoTemplate.dropCollection(Tweet.COLLECTION_NAME).block();
464
465
        reactiveMongoTemplate.createCollection(Tweet.COLLECTION_NAME, 
          CollectionOptions.empty().capped().size(4096).maxDocuments(5)).block();
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510

        // prepare HTTP request for new lorem ipsum tweet
        HttpClient client = HttpClient.newHttpClient();
        String loremIpsumApi = "https://baconipsum.com/api/?type=all-meat&sentences=1&start-with-lorem=1&format=text";
        HttpRequest request = HttpRequest.newBuilder().uri(URI.create(loremIpsumApi)).build();

        // post a new tweet every 3 seconds
        Runnable changePrices = () -> {
            try {
                HttpResponse&lt;String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
                Tweet tweet = new Tweet(response.body());
                tweetRepository.save(tweet).block();
            } catch (InterruptedException | IOException e) { }
        };
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(changePrices, 0, 3, TimeUnit.SECONDS);
    }
}</code></pre></div>	
  <div class="tab-pane" id="flux2-tabs-client" role="tabpanel">
	<pre><code class="language-html line-numbers"><!--​<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
        <script>
            $(document).ready(function() {
                var source = new EventSource("http://localhost:8080/tweets");
                source.onmessage = function(event) {
                    var tweet = JSON.parse(event.data);
                    var time = new Date(tweet.date).toTimeString().substring(0, 8);
                    $("#tweet-list").append("<li>" + time + " <b>" + tweet.text + "</b></li>");
                }
                source.onerror = function(event) {
                    source.close();
                    console.log("Event source closed.")
                }
            });
        </script>
    </head>
    <body>
        <ul id="tweet-list"></ul>
    </body>
</html> --></code></pre></div>
</div>

<ul>
<li><b>TweetController</b>: Als Teil der API wird ein Datenstrom unter dem Pfad <code>/tweets</code> registriert (Zeilen 10-13). Es wird eine Methode aus dem zugehörigen <code>TweetRepository</code> aufgerufen, um auf die Datenbank zuzugreifen.</li>
511
<li><b>TweetRepository</b>: Das Interface erweitert <code>ReactiveMongoRepository&lt;Tweet, String></code> und ist damit ein spezielles reaktives Repository. JDBC-Datenquellen können nicht als reaktives Repository eingesetzt werden. Bei einer Anfrage an die Datenbank gibt die Java-API von MongoDB einen Cursor vom Typ <a href="http://api.mongodb.com/java/current/com/mongodb/DBCursor.html"><code>com.mongodb.DBCursor</code></a> zurück, über den ähnlich einem <code>java.sql.ResultSet</code> iteriert werden kann, um das Ergebnis der Anfrage zu verarbeiten. Per Default schließt MongoDB diesen Cursor, wenn über alle seine Objekte iteriert worden ist. Damit würde aber auch der Stream beendet.<br>MongoDB erlaubt es für sogenannte <a href="https://docs.mongodb.com/manual/core/capped-collections/"><i>Capped Collections</i></a> einen <a href="https://docs.mongodb.com/manual/core/tailable-cursors/"><i>Tailable Cursor</i></a> zu verwenden, der geöffnet bleibt, nachdem alle ursprünglich zurückgegebenen Objekte verarbeitet worden sind. Die Methoden eines <code>ReactiveMongoRepository</code> aus <a href="https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo.reactive.repositories.infinite-streams"><i>Spring Data MongoDB</i></a> können mit der Annotation <a href="https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo.reactive.repositories.infinite-streams"><code>@Tailable</code></a> versehen werden, damit sie einen derartigen nicht abgeschlossenen, unendlichen Datenstrom zurückgeben, d.h. das zurückgegebene <code>Flux</code>-Objekt ist noch nicht beendet. Nur in diesem Fall wird über die API eine SSE-EventSource veröffentlicht. Die Bezeichnung der Annotation <code>@Tailable</code> ist übrigens an den Linux-Befehl <a href="https://en.wikipedia.org/wiki/Tail_(Unix)#File_monitoring"><code>tail -f</code></a> angelehnt.</li>
512
<li><b>TweetStarter</b>: In der überschriebenen Methode <code>run</code> wird zuerst die verwendete Collection in eine auf max. 5 Dokumente begrenzte <i>Capped Collection</i> gewandelt (Zeilen 18-20). Anschließend wird ein HTTP-Request vorbereitet (Zeilen 23-25), der in einer Endlosschleife alle 3 Sekunden ausgeführt wird (Zeile 36), um den Text für einen <a href="https://baconipsum.com/">fleischlastigen Lorem Ipsum-Tweet</a> anzufragen (Zeile 30) und diesen in der <i>Capped Collection</i> zu speichern (Zeilen 31-32).</li>
513
514
515
516
<li><b>tweets.html</b>: Der Client in <code>tweets.html</code> baut eine Verbindung zur EventSource auf (Zeile 6). Die EventSource emittiert neue Tweets an den Client über die EventHandler-Funktion (Zeilen 7-11), innerhalb derer die Tweets dem DOM hinzugefügt werden.</li>
</ul>

<p>Die gezeigten Code-Beispiele zum Umgang mit reaktiven Streams in Spring finden sich im Verzeichnis <a href="/remote/spring-webflux" class="repo-link">/remote/spring-webflux</a> des Modul-Repository.</p>