Skip to content

Commit 1c8f3b3

Browse files
committed
Sesjon 2 - EventDriven
1 parent 3bdd237 commit 1c8f3b3

24 files changed

Lines changed: 950 additions & 2 deletions

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
.kiro
2-
.env
1+
target/
2+
/target
3+
.idea/
4+
.idea
5+
*.iml
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Java Event Consumer
2+
3+
A Java application that consumes `idem.created` events from RabbitMQ and writes them to PostgreSQL.
4+
5+
## Prerequisites
6+
7+
- Java 21+
8+
- Maven 3.8+
9+
- RabbitMQ running on localhost:5672 (see docker-compose.yml)
10+
- PostgreSQL running on localhost:5432 (see docker-compose.yml)
11+
12+
NB! If you are experiencing port conflicts, then you are probably already running a service on that port. Stop the existing container and try again.
13+
14+
## Quick Start
15+
16+
### Using Maven Directly
17+
18+
```bash
19+
mvn compile exec:java
20+
```
21+
22+
### Using Docker
23+
24+
```bash
25+
# Build the image
26+
mvn clean install && docker build -t java-consumer .
27+
28+
# Run with host networking (for local development)
29+
docker run --network host java-consumer
30+
31+
# Or run in the docker compose network
32+
docker run --network codeacademy_default \
33+
-e RABBITMQ_URL=amqp://guest:guest@rabbitmq:5672 \
34+
-e DATABASE_URL=jdbc:postgresql://postgres:5432/codeacademy?user=codeacademy\&password=codeacademy \
35+
java-consumer
36+
```
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Local Development Docker Compose
2+
# Build and run all services locally
3+
# Usage: docker compose up --build
4+
5+
name: codeacademy
6+
7+
services:
8+
rabbitmq:
9+
image: rabbitmq:4.1.4-management-alpine
10+
container_name: 'rabbitmq_ca'
11+
ports:
12+
- 5672:5672
13+
- 15672:15672
14+
volumes:
15+
- ~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/
16+
- ~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq
17+
networks:
18+
- code_academy_network
19+
postgres:
20+
container_name: postgres
21+
image: postgres:17-alpine
22+
healthcheck:
23+
test: [ "CMD-SHELL", "pg_isready -U codeacademy -d codeacademy" ]
24+
interval: 5s
25+
timeout: 5s
26+
retries: 5
27+
ports:
28+
- "5432:5432"
29+
environment:
30+
- POSTGRES_USER=codeacademy
31+
- POSTGRES_PASSWORD=codeacademy
32+
- POSTGRES_DB=codeacademy
33+
volumes:
34+
- postgres_data:/var/lib/postgresql/data
35+
restart: unless-stopped
36+
networks:
37+
- code_academy_network
38+
#java-consumer:
39+
# container_name: java-consumer
40+
# image: java-consumer:local
41+
# build: .
42+
# environment:
43+
# - SPRING_PROFILES_ACTIVE=production
44+
# - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/codeacademy
45+
# ports:
46+
# - "8080:8080"
47+
# depends_on:
48+
# postgres:
49+
# condition: service_healthy
50+
# networks:
51+
# - code_academy_network
52+
53+
networks:
54+
code_academy_network:
55+
driver: bridge
56+
57+
volumes:
58+
postgres_data:
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
## Oppgaver
2+
Målet med denne workshoppen er at du skal klare å lage og kjøre en docker-container som leser meldinger fra en fanout rabbitmq kø, samt
3+
at du har forstått alle de forskjellige kø-typene RabbitMQ har å tilby.
4+
### Forarbeid
5+
1. Sørg for at du får startet en lokal instans av rabbitmq i docker. Bruk docker-compose fila.
6+
Default så kan du kommunisere med rabbitmq på port 5672.
7+
2. Gå til http://localhost:15672 og logg deg på med guest/guest. Lag deg en ny bruker dersom du har lyst til det(man bør unngå å bruke default user/pw)
8+
3. Gjør deg litt kjent inne i admin-panelet. Hvor ser man connections? Exchanger? Køer?
9+
4. Gjør deg kjent med innholdet i `RabbitMQConnectionHelper.java`
10+
5. Gjør deg kjent med innholdet i `RabbitMQConfiguration.java`
11+
12+
### FANOUT
13+
1. Lag deg et java-program 'PUBLISHER' som opprett en exchange av typen fanout. Skriv kode som med jevne mellomrom poster data til denne exchangen, f.eks hvert 5 sekund.
14+
2. Ser du "deg selv" under connections?
15+
3. Lag deg et nytt java-program 'SUBSCRIBER 1' som deklarerer og binder en kø til exchangen du lagde. Les fra køen og se at du mottar meldingene fra exchangen
16+
4. Lag deg enda et nytt java program 'SUBSCRIBER 2' som deklarer og binder en ny kø til den samme exchangen. Sørg for at autoDelete er true. Denne skal motta nøyaktig de samme meldingene som den forrige køen.
17+
5. Hva skjer når du stopper java-programmet 'SUBSCRIBER 2'?
18+
6. Hva skjer når du starter java-programmet 'SUBSCRIBER 2' igjen?
19+
7. Hva skjer hvis 'SUBSCRIBER 1' og 'SUBSCRIBER 2' lytter på samme kø?
20+
7. Slett køene og exchangene dine.
21+
22+
### DIRECT
23+
1. Lag deg en ny exchange i PUBLISHER av typen direct. Skriv kode som med jevne mellomrom poster data til denne exchangen, f.eks hvert 5 sekund.
24+
2. Skriv om 'SUBSCRIBER 1' til å deklarerer og binder en kø til den nye exchangen. Bruk en fornuftig routingkey. Mottar du meldingene?
25+
3. Skriv om 'SUBSCRIBER 2' til å deklarerer og binder en kø til den nye exchangen. Konfigurer med en annen routingKey. Mottar du meldingene? Hvorfor ikke?
26+
4. Slett køene og exchangene dine.
27+
28+
### TOPIC
29+
1. Lag deg en ny exchange i PUBLISHER av typen direct. Skriv kode som med jevne mellomrom poster data til denne exchangen, f.eks hvert 5 sekund.
30+
Post annen hver melding med routingkey = 'soprasteria' og routingkey = 'soprasteria.secret'.
31+
2. Skriv om 'SUBSCRIBER 1' til å deklarerer og binder en kø til den nye exchangen. Bruk routingKey = 'soprasteria.applications'. Hvilke meldinger mottar du?
32+
3. Skriv om 'SUBSCRIBER 2' til å deklarerer og binder en kø til den nye exchangen. Bruk routingKey = 'soprasteria.*'. Hvilke meldinger mottar du?
33+
3. Skriv om 'SUBSCRIBER 3' til å deklarerer og binder en kø til den nye exchangen. Bruk routingKey = 'soprasteria.secret'. Hvilke meldinger mottar du?
34+
4. Hvilken subscriber mottar meldingene hvis du endrer PUBLISHER til å poste en melding med routingKey = 'soprasteria.secret.unicorn' ?
35+
5. Slett køene og exchangene dine.
36+
37+
### HEADER
38+
1. Lag deg en ny exchange i PUBLISHER av typen header. Skriv kode som med jevne mellomrom poster data til denne exchangen, f.eks hvert 5 sekund.
39+
Post en melding med headere "type"="vehicle", "color":"red", en annen melding med headere "type"="vehicle", "color":"blue" og en tredje melding med headere "type"="bike", "color":"purple"
40+
2. Skriv om 'SUBSCRIBER 1' til å deklarerer og binder en kø til den nye exchangen. Bind køen med header "type"="vehicle", "color":"red", "x-match":"all". Hvilke meldinger mottar du?
41+
3. Skriv om 'SUBSCRIBER 2' til å deklarerer og binder en kø til den nye exchangen. Bind køen med header "type"="vehicle", "color":"purple", "x-match":"any". Hvilke meldinger mottar du?
42+
4. Slett køene og exchangene dine.
43+
44+
45+
### Ferdig dockerprodukt
46+
1. Lag deg en `Dockerfile` som bygger hele applikasjonene.
47+
2. Applikasjonen skal ha en main-metode som kobler seg på en fanout kø og parser disse meldingene.
48+
3. Skriv ut meldingene til consollen
49+
4. Lag også en publisher metode som publiserer meldinger med jevne mellomrom til exchangen.
50+
51+
### Hvis du trenger en utfordring
52+
1. Lag deg en program som publiserer og consumerer mange hundre meldinger i sekundet
53+
2. Øk prefetch counten til noe stort
54+
3. Hvordan kan du sørge for disconnecte gracefully når man har satt en høy prefetch count? Hva skjer med meldingene som er prefetched, når consumeren f.eks feiler eller restarter? Er meldingene tapt? Hint: ShutDownSignal
55+
4. Prøv å send en fil over rabbitmq. Hvordan får man til det?
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>org.springframework.boot</groupId>
9+
<artifactId>spring-boot-starter-parent</artifactId>
10+
<version>4.0.2</version>
11+
<relativePath/> <!-- lookup parent from repository -->
12+
</parent>
13+
14+
<groupId>no.soprasteria</groupId>
15+
<artifactId>java-consumer</artifactId>
16+
<version>1.0.0-SNAPSHOT</version>
17+
<packaging>jar</packaging>
18+
19+
<name>Idem Event Consumer</name>
20+
<description>Java consumer for idem events from RabbitMQ</description>
21+
22+
<properties>
23+
<maven.compiler.source>21</maven.compiler.source>
24+
<maven.compiler.target>21</maven.compiler.target>
25+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
26+
<java.version>21</java.version>
27+
<openapi.generator.maven.plugin.version>7.2.0</openapi.generator.maven.plugin.version>
28+
<rabbit-amqp-client.version>5.26.0</rabbit-amqp-client.version>
29+
</properties>
30+
31+
<dependencies>
32+
<dependency>
33+
<groupId>org.springframework.boot</groupId>
34+
<artifactId>spring-boot-starter-web</artifactId>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.springframework.boot</groupId>
38+
<artifactId>spring-boot-starter-data-jpa</artifactId>
39+
</dependency>
40+
<dependency>
41+
<groupId>org.springframework.boot</groupId>
42+
<artifactId>spring-boot-starter-flyway</artifactId>
43+
</dependency>
44+
<dependency>
45+
<groupId>ch.qos.logback</groupId>
46+
<artifactId>logback-classic</artifactId>
47+
</dependency>
48+
<dependency>
49+
<groupId>ch.qos.logback</groupId>
50+
<artifactId>logback-core</artifactId>
51+
</dependency>
52+
<!-- Swagger og OpenAPI -->
53+
<dependency>
54+
<groupId>org.springdoc</groupId>
55+
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
56+
<version>2.8.5</version>
57+
</dependency>
58+
<dependency>
59+
<groupId>io.swagger.core.v3</groupId>
60+
<artifactId>swagger-annotations</artifactId>
61+
<version>2.2.20</version>
62+
</dependency>
63+
64+
<dependency>
65+
<groupId>com.rabbitmq</groupId>
66+
<artifactId>amqp-client</artifactId>
67+
<version>${rabbit-amqp-client.version}</version>
68+
</dependency>
69+
<dependency>
70+
<groupId>org.flywaydb</groupId>
71+
<artifactId>flyway-core</artifactId>
72+
</dependency>
73+
<dependency>
74+
<groupId>org.flywaydb</groupId>
75+
<artifactId>flyway-database-postgresql</artifactId>
76+
</dependency>
77+
<dependency>
78+
<groupId>org.postgresql</groupId>
79+
<artifactId>postgresql</artifactId>
80+
<scope>runtime</scope>
81+
</dependency>
82+
</dependencies>
83+
84+
<build>
85+
<plugins>
86+
<plugin>
87+
<groupId>org.apache.maven.plugins</groupId>
88+
<artifactId>maven-compiler-plugin</artifactId>
89+
<version>3.11.0</version>
90+
<configuration>
91+
<source>21</source>
92+
<target>21</target>
93+
</configuration>
94+
</plugin>
95+
<plugin>
96+
<groupId>org.springframework.boot</groupId>
97+
<artifactId>spring-boot-maven-plugin</artifactId>
98+
<configuration>
99+
<mainClass>no.soprasteria.Application</mainClass>
100+
</configuration>
101+
</plugin>
102+
</plugins>
103+
</build>
104+
</project>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package no.soprasteria;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.boot.SpringApplication;
6+
import org.springframework.boot.autoconfigure.SpringBootApplication;
7+
import org.springframework.scheduling.annotation.EnableScheduling;
8+
9+
@SpringBootApplication
10+
@EnableScheduling
11+
public class Application {
12+
13+
private static final Logger log = LoggerFactory.getLogger(Application.class);
14+
15+
public static void main(String[] args) {
16+
log.info("Starting RabbitMQ Application");
17+
SpringApplication.run(Application.class, args);
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package no.soprasteria;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.fasterxml.jackson.databind.SerializationFeature;
5+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.context.annotation.Primary;
9+
10+
@Configuration
11+
public class JacksonConfig {
12+
@Bean
13+
@Primary
14+
public ObjectMapper objectMapper() {
15+
return new ObjectMapper()
16+
.registerModule(new JavaTimeModule())
17+
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
18+
}
19+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package no.soprasteria.controller;
2+
3+
public record Message(String author, String message) {
4+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package no.soprasteria.controller;
2+
3+
import no.soprasteria.db.DataRepository;
4+
import no.soprasteria.domain.IdemDataDTO;
5+
import no.soprasteria.rabbit.FanOutMessageService;
6+
import no.soprasteria.rabbit.RabbitMQConfiguration;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.*;
9+
10+
import java.time.LocalDateTime;
11+
import java.util.List;
12+
import java.util.UUID;
13+
14+
@RestController
15+
@RequestMapping("/api/messages")
16+
public class MessageController {
17+
18+
private final DataRepository dataRepository;
19+
private final FanOutMessageService fanOutMessageService;
20+
21+
public MessageController(DataRepository dataRepository, FanOutMessageService fanOutMessageService) {
22+
this.dataRepository = dataRepository;
23+
this.fanOutMessageService = fanOutMessageService;
24+
}
25+
26+
@GetMapping("latest")
27+
public ResponseEntity<List<IdemDataDTO>> get() {
28+
return ResponseEntity.ok(dataRepository.findLatest()
29+
.stream()
30+
.map(message -> new IdemDataDTO(message.getId().toString(), message.getAuthor(), message.getMessage(), message.getCreatedAt()))
31+
.toList());
32+
}
33+
34+
@PutMapping("post-new-message")
35+
public ResponseEntity<?> postMessage(@RequestBody Message message) {
36+
fanOutMessageService.publishMessageToQueue(new IdemDataDTO(UUID.randomUUID().toString(), message.author(), message.message(), LocalDateTime.now()), RabbitMQConfiguration.EXCHANGE_NAME_FANOUT, "");
37+
return ResponseEntity.accepted().build();
38+
}
39+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package no.soprasteria.db;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
import org.springframework.data.jpa.repository.Query;
5+
import org.springframework.stereotype.Repository;
6+
7+
import java.util.List;
8+
9+
@Repository
10+
public interface DataRepository extends JpaRepository<MessageData, Long> {
11+
12+
@Query("FROM MessageData ORDER BY createdAt desc limit 50")
13+
List<MessageData> findLatest();
14+
}

0 commit comments

Comments
 (0)