You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: spring/docs/GUIDE.md
+90-43Lines changed: 90 additions & 43 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,34 +1,42 @@
1
1
# Streaming real-time data with Spring Boot
2
2
3
-
###Goals
3
+
## Goals
4
4
5
-
This guide will show you how to use the Caplin platform and [Spring Boot](https://spring.io/projects/spring-boot) to rapidly build an application that can deliver on-demand, real time data to a browser or mobile application.
5
+
This guide will show you how to use the Caplin platform and [Spring Boot](https://spring.io/projects/spring-boot) to
6
+
rapidly build an application that can deliver on-demand, real time data to a browser or mobile application.
6
7
7
-
###Pre-requisites
8
+
## Pre-requisites
8
9
9
-
This guide assumes that you are familiar with Spring Boot, else it would be beneficial to follow the [Building an Application with Spring Boot](https://spring.io/guides/gs/spring-boot/) guide before returning.
10
+
This guide assumes that you are familiar with Spring Boot, else it would be beneficial to follow
11
+
the [Building an Application with Spring Boot](https://spring.io/guides/gs/spring-boot/) guide before returning.
10
12
11
-
####Software requirements
13
+
### Software requirements
12
14
13
15
* Java JDK 17 or later
14
16
* Docker, or a similar container runtime that supports compose files.
15
17
* A Java or Kotlin IDE
16
18
17
-
###Project setup
19
+
## Project setup
18
20
19
21
Now let's create a simple application.
20
22
21
23
* Navigate to [Spring Initializr](https://start.spring.io/)
22
24
23
-
* It's recommended to choose _Gradle - Kotlin_ for the Project, and _Kotlin_ for the Language, though you may of course use _Java_.
25
+
* It's recommended to choose _Gradle - Kotlin_ for the Project, and _Kotlin_ for the Language, though you may of course
26
+
use _Java_.
24
27
25
28
* Choose the latest Spring Boot release version, at the time of writing this is 3.5.3.
26
29
27
30
* Generate the project, unzip it, and then import it into your IDE.
28
31
29
-
* Now we need to add our DataSource Starter dependency, so open up `build.gradle.kts` and add `implementation("com.caplin.integration.datasourcex:spring-boot-starter-datasource:1.0.0")` to the `dependencies` block.
32
+
* Now we need to add our DataSource Starter dependency, so open up `build.gradle.kts` and add
33
+
`implementation("com.caplin.integration.datasourcex:spring-boot-starter-datasource:1.0.0")` to the `dependencies`
34
+
block.
30
35
31
-
* You will also need to add the Caplin repository to be able to access the Caplin DataSource libraries. To do so, add the following to the `repositories` block. Note the credentials here should be retrieved from your Caplin Account Manager. These are best passed in from the command line, via environment variable or via your global `gradle.properties` file to ensure they are not inadvertently exposed:
36
+
* You will also need to add the Caplin repository to be able to access the Caplin DataSource libraries. To do so, add
37
+
the following to the `repositories` block. Note the credentials here should be retrieved from your Caplin Account
38
+
Manager. These are best passed in from the command line, via environment variable or via your global
39
+
`gradle.properties` file to ensure they are not inadvertently exposed:
@@ -39,26 +47,38 @@ Now let's create a simple application.
39
47
}
40
48
```
41
49
42
-
* Lastly, we'll need to configure the Liberator host that DataSource will connect to, so open `src/main/resources/application.properties` and add the line `caplin.datasource.managed.peer.outgoing=ws://localhost:19000`
50
+
* Lastly, we'll need to configure the Liberator host that DataSource will connect to, so open
51
+
`src/main/resources/application.properties` and add the line
To launch the Caplin platform you can use the [example Docker Compose file](https://github.com/caplin/DataSource-Extensions/tree/main/examples) from the repository examples. Please refer to the brief readme for instructions. This will launch a container running a preconfigured Liberator and expose two ports; `18080` for inbound front end application connections and `19000` for the inbound WebSocket connection from our new server application.
56
+
To launch the Caplin platform you can use
57
+
the [example Docker Compose file](https://github.com/caplin/DataSource-Extensions/tree/main/examples) from the
58
+
repository examples. Please refer to the brief readme for instructions. This will launch a container running a
59
+
preconfigured Liberator and expose two ports; `18080` for inbound front end application connections and `19000` for the
60
+
inbound WebSocket connection from our new server application.
47
61
48
-
###Creating a simple browser client
62
+
## Creating a simple browser client
49
63
50
-
We'll want to be able to request and display some data from our server, so let us create a basic browser client application to do so. Add the following to your project as `./index.html`. This code sets up a connection to the platform with the StreamLink library (In this case, hosted by our Liberator container at `http://localhost:18080/sljs/streamlink.js`) and enables the library's support for handling streaming JSON patches behind the scenes.
64
+
We'll want to be able to request and display some data from our server, so let us create a basic browser client
65
+
application to do so. Add the following to your project as `./index.html`. This code sets up a connection to the
66
+
platform with the StreamLink library (In this case, hosted by our Liberator container at
67
+
`http://localhost:18080/sljs/streamlink.js`) and enables the library's support for handling streaming JSON patches
68
+
behind the scenes.
51
69
52
70
> For the sake of clarity, we are omitting most error handling code.
Now let's add some data! In this case our client wants to retrieve the local time and time zone of the server. To handle this we'll create a new `@Controller` class providing an aptly named `/serverTime` endpoint.
131
+
Now let's add some data! In this case our client wants to retrieve the local time and time zone of the server. To handle
132
+
this we'll create a new `@Controller` class providing an aptly named `/serverTime` endpoint.
112
133
113
134
```kotlin
114
135
@Controller
@@ -138,7 +159,9 @@ and we should see log line indicating our subject has been bound correctly
138
159
Registering [/serverTime] as Static
139
160
```
140
161
141
-
If we now edit our `index.html` to subscribe to this, adding the code provided below in place of the existing `//TODO subscriptions` placeholder, and refresh our browser, we should see the server's time and time zone data being displayed.
162
+
If we now edit our `index.html` to subscribe to this, adding the code provided below in place of the existing
163
+
`//TODO subscriptions` placeholder, and refresh our browser, we should see the server's time and time zone data being
Now as nice as that is, we'd like more than a single response, so let's modify our endpoint to provide a stream of events, rather than just the initial response. For Kotlin we'll be returning a [Flow](https://kotlinlang.org/docs/flow.html). For Java you can instead make use of Reactor's [Flux](https://projectreactor.io/docs/core/release/reference/#flux). Both are powerful abstractions over a stream of data.
182
+
Now as nice as that is, we'd like more than a single response, so let's modify our endpoint to provide a stream of
183
+
events, rather than just the initial response. For Kotlin we'll be returning
184
+
a [Flow](https://kotlinlang.org/docs/flow.html). For Java you can instead make use of
185
+
Reactor's [Flux](https://projectreactor.io/docs/core/release/reference/#flux). Both are powerful abstractions over a
186
+
stream of data.
159
187
160
188
Modify the `StreamingController` class to replace our previous function with the following:
One brief restart of the server application later, and you should see the client updating in real time!
173
201
174
-
###Request parameters
202
+
## Request parameters
175
203
176
-
Now, imagine that the browser client needs to fetch the local time in a specific time zone. To achieve this we can fairly simply add a new endpoint to our controller, this time named `zonedTime` and taking a `@DestinationVariable` that is extracted from the requested subject.
204
+
Now, imagine that the browser client needs to fetch the local time in a specific time zone. To achieve this we can
205
+
fairly simply add a new endpoint to our controller, this time named `zonedTime` and taking a `@DestinationVariable` that
After another quick restart of our server, and a refresh of our browser, we now have two time streams being displayed.
204
235
205
-
###Request payloads
236
+
## Request payloads
206
237
207
-
But what if our stream request becomes a bit more complicated, perhaps containing optional or arrays of parameters? At this point it's more natural to represent our request as a payload object. Let's assume our client now wishes to make a single subscription to the time in various user specified zones, again we can support this with a few minor additions to our server. Create a new endpoint named `/times` in your controller, this time receiving single non-annotated method parameter which will be our payload from the client.
238
+
But what if our stream request becomes a bit more complicated, perhaps containing optional or arrays of parameters? At
239
+
this point it's more natural to represent our request as a payload object. Let's assume our client now wishes to make a
240
+
single subscription to the time in various user specified zones, again we can support this with a few minor additions to
241
+
our server. Create a new endpoint named `/times` in your controller, this time receiving single non-annotated method
242
+
parameter which will be our payload from the client.
Now for our client we need to do something a bit different for this case - we'll need to establish a channel rather than a plain subscription, and then send our request. This is quite simple:
260
+
Now for our client we need to do something a bit different for this case - we'll need to establish a channel rather than
261
+
a plain subscription, and then send our request. This is quite simple:
Running this we'll now see all the requested times being displayed and updating in sync.
242
278
243
-
###Two-way communication
279
+
## Two-way communication
244
280
245
-
Lastly, say we now want our client to have the ability to add new zones to the stream in an ad-hoc manner. Fortunately, we can do this with a just few tweaks.
281
+
Lastly, say we now want our client to have the ability to add new zones to the stream in an ad-hoc manner. Fortunately,
282
+
we can do this with a just few tweaks.
246
283
247
-
On the client we'll add a simple text entry box and button, the clicking of which will send a message through the channel to let the server know to add a new zone.
284
+
On the client we'll add a simple text entry box and button, the clicking of which will send a message through the
285
+
channel to let the server know to add a new zone.
248
286
249
287
```javascript
250
288
window.addZone=function () {
@@ -255,7 +293,8 @@ window.addZone = function () {
And on the server we can update our `/times` endpoint to accept a stream of data from the client by changing our parameter to be either a Flow or Flux accordingly, and then update our responses to include the newly requested zones:
296
+
And on the server we can update our `/times` endpoint to accept a stream of data from the client by changing our
297
+
parameter to be either a Flow or Flux accordingly, and then update our responses to include the newly requested zones:
259
298
260
299
```kotlin
261
300
data classTimesRequest(
@@ -275,4 +314,12 @@ fun times(zoneRequests: Flow<TimesRequest>): Flow<List<TimeEvent>> = zoneRequest
275
314
}
276
315
```
277
316
278
-
One final restart of our application, and by clicking the button we can now see additional times being added to our stream each time we add a new zone.
317
+
One final restart of our application, and by clicking the button we can now see additional times being added to our
or [per-session](https://github.com/caplin/DataSource-Extensions/blob/main/examples/spring-kotlin/src/main/kotlin/example/StreamsController.kt#L87)
325
+
subscriptions and channels, or interacting with a [stateful server](https://github.com/caplin/DataSource-Extensions/blob/main/examples/spring-kotlin-chat/src/main/kotlin/example/ChatController.kt#L77).
0 commit comments