Skip to content

Commit 3f16e66

Browse files
authored
Merge pull request #210 from amosproj/sprint-12
Sprint 12
2 parents 20f3d54 + f19bebe commit 3f16e66

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1072
-2324
lines changed

README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,33 @@ We want to achieve this by structuring our UI according to the topology of Apach
1212
* Node.js Version **20.0.0** or higher
1313
* Docker Desktop
1414

15-
## Backend and Pulsar instance
15+
## Startup
1616

1717
First, build the application JAR from the `backend` directory with:
1818

1919
`./mvnw package -DskipTests`
2020

21-
Then, start Docker Desktop and create the pulsar setup from the root-directory with:
21+
### Start Frontend, Backend and Pulsar instance without demodata
22+
23+
Start Docker Desktop and create the pulsar setup from the root-directory with:
24+
25+
```bash
26+
echo BACKEND_IP=localhost >> .env
27+
docker-compose --profile backend --profile frontend up --build -d
28+
```
29+
30+
### Start Frontend, Backend and Pulsar instance with demodata
31+
32+
Start Docker Desktop and create the pulsar setup from the root-directory with:
2233

2334
```bash
2435
echo BACKEND_IP=localhost >> .env
25-
docker-compose --profile demodata --profile backend --profile frontend up --build -d
36+
docker-compose --profile backend --profile frontend --profile demodata -f docker-compose.yml -f docker-compose-setup.yml up --build -d
2637
```
2738

2839
Notes:
40+
* The `docker-compose.yml` includes the pulsar, backend and frontend services.
41+
* The `docker-compose-setup.yml` includes services for the local or AWS demodata setup. `-f` selects the files used for the startup.
2942
* `--build` is needed for the first startup, or when the demodata docker images are changed
3043
* `-d` runs the container in the background, so u can close the terminal without terminating the app itself.
3144
* `--profile demodata` is needed when you want to create the demo topology and start the demo producers & consumers, that will continuously send and receive messages
@@ -64,5 +77,5 @@ so you need to pass it to the `docker-compose` via `-e` flag.
6477

6578
```bash
6679
echo BACKEND_IP=${EC2_IP_ADDRESS} >> .env
67-
docker-compose --profile demodata-aws --profile backend --profile frontend up --build -d
80+
docker-compose --profile backend --profile frontend --profile demodata-aws -f docker-compose.yml -f docker-compose-setup.yml up --build -d
6881
```

backend/src/main/java/de/amos/apachepulsarui/ApachePulsarUiApplication.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
package de.amos.apachepulsarui;
88

9+
import org.springframework.beans.factory.annotation.Value;
910
import org.springframework.boot.SpringApplication;
1011
import org.springframework.boot.autoconfigure.SpringBootApplication;
1112
import org.springframework.cache.annotation.EnableCaching;
@@ -17,6 +18,9 @@
1718
@EnableCaching
1819
public class ApachePulsarUiApplication {
1920

21+
@Value("${frontend.url}")
22+
private String allowedOrigin;
23+
2024
public static void main(String[] args) {
2125
SpringApplication.run(ApachePulsarUiApplication.class, args);
2226
}
@@ -26,7 +30,7 @@ public WebMvcConfigurer configurer() {
2630
return new WebMvcConfigurer() {
2731
@Override
2832
public void addCorsMappings(CorsRegistry corsRegistry) {
29-
corsRegistry.addMapping("/**").allowedOrigins("*");
33+
corsRegistry.addMapping("/**").allowedOrigins(allowedOrigin);
3034
}
3135
};
3236
}

backend/src/main/java/de/amos/apachepulsarui/controller/MessageController.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.springframework.web.bind.annotation.RestController;
1919

2020
import java.util.List;
21+
import java.util.Set;
2122

2223
@RestController
2324
@RequestMapping("/messages")
@@ -30,8 +31,10 @@ public class MessageController {
3031
public ResponseEntity<MessagesDto> getMessages(@RequestParam String topic,
3132
@RequestParam(required = false, defaultValue = "10") Integer numMessages,
3233
@RequestParam(required = false, defaultValue = "") List<String> producers,
33-
@RequestParam(required = false, defaultValue = "") List<String> subscriptions) {
34-
List<MessageDto> messageDtos = messageService.getLatestMessagesFiltered(topic, numMessages, producers, subscriptions);
34+
@RequestParam(required = false, defaultValue = "") List<String> subscriptions)
35+
{
36+
Set<MessageDto> messageDtos = messageService.getLatestMessagesFiltered(topic, numMessages, producers, subscriptions);
3537
return new ResponseEntity<>(new MessagesDto(messageDtos), HttpStatus.OK);
3638
}
39+
3740
}

backend/src/main/java/de/amos/apachepulsarui/dto/MessagesDto.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
import lombok.AllArgsConstructor;
99
import lombok.Data;
1010

11-
import java.util.List;
11+
import java.util.Set;
1212

1313

1414
@Data
1515
@AllArgsConstructor
1616
public class MessagesDto {
1717

18-
private List<MessageDto> messages;
18+
private Set<MessageDto> messages;
1919

2020
}

backend/src/main/java/de/amos/apachepulsarui/service/MessageService.java

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,20 @@
1919

2020
import java.util.ArrayList;
2121
import java.util.Collections;
22+
import java.util.Comparator;
23+
import java.util.LinkedHashSet;
2224
import java.util.List;
25+
import java.util.Set;
26+
import java.util.stream.Collectors;
2327

2428
@Service
2529
@Slf4j
2630
@RequiredArgsConstructor
2731
public class MessageService {
2832
private final PulsarAdmin pulsarAdmin;
2933

30-
public List<MessageDto> getLatestMessagesFiltered(String topic, Integer numMessages, List<String> producers, List<String> subscriptions) {
31-
List<MessageDto> messageDtos = getLatestMessagesOfTopic(topic, numMessages);
34+
public Set<MessageDto> getLatestMessagesFiltered(String topic, Integer numMessages, List<String> producers, List<String> subscriptions) {
35+
Set<MessageDto> messageDtos = getLatestMessagesOfTopic(topic, numMessages);
3236
if (!producers.isEmpty()) {
3337
messageDtos = filterByProducers(messageDtos, producers);
3438
}
@@ -39,12 +43,14 @@ public List<MessageDto> getLatestMessagesFiltered(String topic, Integer numMessa
3943
return messageDtos;
4044
}
4145

42-
private List<MessageDto> filterBySubscription(List<MessageDto> messageDtos, Integer numMessages, String topic, List<String> subscriptions) {
46+
private Set<MessageDto> filterBySubscription(Set<MessageDto> messageDtos, Integer numMessages, String topic, List<String> subscriptions) {
4347
List<String> messageIds = subscriptions.stream()
4448
.flatMap(s -> peekMessageIds(topic, s, numMessages).stream())
4549
.toList();
4650

47-
return messageDtos.stream().filter(m -> messageIds.contains(m.getMessageId())).toList();
51+
return messageDtos.stream()
52+
.filter(m -> messageIds.contains(m.getMessageId()))
53+
.collect(Collectors.toCollection(LinkedHashSet::new));
4854
}
4955

5056
private List<String> peekMessageIds(String topic, String subscription, Integer numMessages) {
@@ -60,14 +66,13 @@ private List<String> peekMessageIds(String topic, String subscription, Integer n
6066
}
6167

6268

63-
private List<MessageDto> filterByProducers(List<MessageDto> messageDtos, List<String> producers) {
69+
private Set<MessageDto> filterByProducers(Set<MessageDto> messageDtos, List<String> producers) {
6470
return messageDtos.stream()
6571
.filter(m -> producers.contains(m.getProducer()))
66-
.toList();
67-
72+
.collect(Collectors.toCollection(LinkedHashSet::new));
6873
}
6974

70-
private List<MessageDto> getLatestMessagesOfTopic(String topic, Integer numMessages) {
75+
private Set<MessageDto> getLatestMessagesOfTopic(String topic, Integer numMessages) {
7176
var schema = getSchemaIfExists(topic);
7277
try {
7378
var messages = new ArrayList<Message<byte[]>>();
@@ -83,7 +88,10 @@ private List<MessageDto> getLatestMessagesOfTopic(String topic, Integer numMessa
8388
}
8489
return messages.stream()
8590
.map(message -> MessageDto.fromExistingMessage(message, schema))
86-
.toList();
91+
// latest message first in set
92+
.sorted(Comparator.comparing(MessageDto::getPublishTime, Comparator.reverseOrder()))
93+
// linked to keep the order!
94+
.collect(Collectors.toCollection(LinkedHashSet::new));
8795
} catch (PulsarAdminException e) {
8896
throw new PulsarApiException(
8997
"Could not examine the amount of '%d' messages for topic '%s'".formatted(numMessages, topic),

backend/src/main/resources/application.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ server.port=8081
22
pulsar.consumer.url = pulsar://localhost:6650
33
pulsar.admin.url = http://localhost:8080
44
server.servlet.context-path=/api
5+
frontend.url = http://localhost:8082

backend/src/test/java/de/amos/apachepulsarui/controller/MessageControllerTest.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
import org.springframework.http.MediaType;
1717
import org.springframework.test.web.servlet.MockMvc;
1818

19-
import java.util.ArrayList;
19+
import java.util.HashSet;
2020
import java.util.List;
21+
import java.util.Set;
2122

2223
import static java.util.Collections.emptyList;
2324
import static org.hamcrest.Matchers.equalTo;
@@ -37,7 +38,7 @@ public class MessageControllerTest {
3738

3839
@Test
3940
void getMessages_returnsMessages() throws Exception {
40-
List<MessageDto> messageDtos = List.of(
41+
Set<MessageDto> messageDtos = Set.of(
4142
aMessage("persistent://public/default/spaceships", "Nebuchadnezzar"),
4243
aMessage("persistent://public/default/spaceships", "Serenity")
4344
);
@@ -54,7 +55,7 @@ void getMessages_returnsMessages() throws Exception {
5455

5556
@Test
5657
void getMessages_withoutNumMessages_returns10Messages() throws Exception {
57-
var messageDtos = new ArrayList<MessageDto>();
58+
HashSet<MessageDto> messageDtos = new HashSet<>();
5859
for (int i = 0; i < 10; i++) {
5960
messageDtos.add(aMessage("persistent://public/default/test", "Test" + i));
6061
}
@@ -70,7 +71,7 @@ void getMessages_withoutNumMessages_returns10Messages() throws Exception {
7071

7172
@Test
7273
void getMessages_withProducer_returns10Messages() throws Exception {
73-
var messageDtos = new ArrayList<MessageDto>();
74+
HashSet<MessageDto> messageDtos = new HashSet<>();
7475
for (int i = 0; i < 10; i++) {
7576
messageDtos.add(aMessage("persistent://public/default/test", "Test" + i));
7677
}
@@ -86,7 +87,7 @@ void getMessages_withProducer_returns10Messages() throws Exception {
8687

8788
@Test
8889
void getMessages_withSubscription_returns10Messages() throws Exception {
89-
var messageDtos = new ArrayList<MessageDto>();
90+
HashSet<MessageDto> messageDtos = new HashSet<>();
9091
for (int i = 0; i < 10; i++) {
9192
messageDtos.add(aMessage("persistent://public/default/test", "Test" + i));
9293
}

backend/src/test/java/de/amos/apachepulsarui/service/MessageServiceIntegrationTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ void getNumberOfLatestMessagesFromTopic_returnsMessages() throws Exception {
6565
}
6666
var messages = messageService.getLatestMessagesFiltered(TOPICNAME, 1, emptyList(), emptyList());
6767

68-
MessageDto messageReceived = messages.get(0);
68+
MessageDto messageReceived = messages.iterator().next();
6969
assertThat(messageReceived.getMessageId()).isNotEmpty(); // generated
7070
assertThat(messageReceived.getTopic()).isEqualTo(messageToSend.getTopic());
7171
assertThat(messageReceived.getPayload()).isEqualTo(messageToSend.getPayload());
@@ -91,7 +91,7 @@ void getNumberOfLatestMessagesFromTopicFilteredByProducer_returnsMessages() thro
9191
}
9292
var messages = messageService.getLatestMessagesFiltered(TOPICNAME, 1, List.of(producerName), emptyList());
9393

94-
MessageDto messageReceived = messages.get(0);
94+
MessageDto messageReceived = messages.iterator().next();
9595
assertThat(messageReceived.getMessageId()).isNotEmpty(); // generated
9696
assertThat(messageReceived.getTopic()).isEqualTo(messageToSend.getTopic());
9797
assertThat(messageReceived.getPayload()).isEqualTo(messageToSend.getPayload());
@@ -155,7 +155,7 @@ void getNumberOfLatestMessagesFromTopicFilteredBySubscription_returnsMessages()
155155
}
156156
var messages = messageService.getLatestMessagesFiltered(TOPICNAME, 1, emptyList(), List.of(subscriptionName));
157157

158-
MessageDto messageReceived = messages.get(0);
158+
MessageDto messageReceived = messages.iterator().next();
159159
assertThat(messageReceived.getMessageId()).isNotEmpty(); // generated
160160
assertThat(messageReceived.getTopic()).isEqualTo(messageToSend.getTopic());
161161
assertThat(messageReceived.getPayload()).isEqualTo(messageToSend.getPayload());
@@ -179,7 +179,7 @@ void getNumberOfLatestMessagesFromTopic_forMessageWithSchema_returnsSchema() thr
179179
}
180180
var messages = messageService.getLatestMessagesFiltered(TOPICNAME, 1, emptyList(), emptyList());
181181

182-
MessageDto messageReceived = messages.get(0);
182+
MessageDto messageReceived = messages.iterator().next();
183183
assertThat(messageReceived.getSchema()).isEqualTo(schema.getSchemaInfo().getSchemaDefinition());
184184
}
185185

docker-compose-setup.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
version: '3'
2+
services:
3+
4+
backend:
5+
depends_on:
6+
setup-topology:
7+
condition: service_completed_successfully
8+
9+
setup-topology:
10+
image: pulsarui/setuptopology
11+
profiles:
12+
- demodata
13+
build:
14+
context: demodata/setup-topology
15+
dockerfile: Dockerfile
16+
environment:
17+
- 'PULSAR_ADMIN_URL=http://pulsar:8080'
18+
- 'USE_AWS=false'
19+
depends_on:
20+
pulsar:
21+
condition: service_healthy
22+
23+
setup-topology-aws:
24+
image: pulsarui/setuptopology
25+
profiles:
26+
- demodata-aws
27+
build:
28+
context: demodata/setup-topology
29+
dockerfile: Dockerfile
30+
environment:
31+
- 'PULSAR_ADMIN_URL=http://pulsar:8080'
32+
- 'USE_AWS=true'
33+
depends_on:
34+
pulsar:
35+
condition: service_healthy
36+
37+
demo-producer-consumer:
38+
image: pulsarui/demoproducerconsumer
39+
profiles:
40+
- demodata
41+
build:
42+
context: demodata/demo-producer-consumer
43+
dockerfile: Dockerfile
44+
environment:
45+
- 'PULSAR_URL=pulsar://pulsar:6650'
46+
- 'PUBLISH_INTERVAL_SECONDS=30'
47+
depends_on:
48+
setup-topology:
49+
condition: service_completed_successfully

docker-compose.yml

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ services:
1212
environment:
1313
- "PULSAR_CONSUMER_URL=pulsar://pulsar:6650"
1414
- "PULSAR_ADMIN_URL=http://pulsar:8080"
15+
- "FRONTEND_URL=http://${BACKEND_IP}:8082"
1516
depends_on:
1617
pulsar:
1718
condition: service_healthy
@@ -48,46 +49,4 @@ services:
4849
test: ["CMD", "bin/pulsar-admin", "brokers", "healthcheck"]
4950
interval: 5s
5051
timeout: 10s
51-
retries: 10
52-
53-
setup-topology:
54-
image: pulsarui/setuptopology
55-
profiles:
56-
- demodata
57-
build:
58-
context: demodata/setup-topology
59-
dockerfile: Dockerfile
60-
environment:
61-
- 'PULSAR_ADMIN_URL=http://pulsar:8080'
62-
- 'USE_AWS=false'
63-
depends_on:
64-
pulsar:
65-
condition: service_healthy
66-
67-
setup-topology-aws:
68-
image: pulsarui/setuptopology
69-
profiles:
70-
- demodata-aws
71-
build:
72-
context: demodata/setup-topology
73-
dockerfile: Dockerfile
74-
environment:
75-
- 'PULSAR_ADMIN_URL=http://pulsar:8080'
76-
- 'USE_AWS=true'
77-
depends_on:
78-
pulsar:
79-
condition: service_healthy
80-
81-
demo-producer-consumer:
82-
image: pulsarui/demoproducerconsumer
83-
profiles:
84-
- demodata
85-
build:
86-
context: demodata/demo-producer-consumer
87-
dockerfile: Dockerfile
88-
environment:
89-
- 'PULSAR_URL=pulsar://pulsar:6650'
90-
- 'PUBLISH_INTERVAL_SECONDS=30'
91-
depends_on:
92-
setup-topology:
93-
condition: service_completed_successfully
52+
retries: 10

0 commit comments

Comments
 (0)