├── .env.example
├── .github
├── dependabot.yml
└── workflows
│ ├── codecov.yaml
│ └── docker-upload.yml
├── src
├── main
│ ├── resources
│ │ ├── application.yaml
│ │ └── kafka
│ │ │ └── producer.xml
│ └── java
│ │ └── com
│ │ └── example
│ │ └── datageneratormicroservice
│ │ ├── service
│ │ ├── KafkaDataService.java
│ │ ├── TestDataService.java
│ │ ├── KafkaDataServiceImpl.java
│ │ └── TestDataServiceImpl.java
│ │ ├── web
│ │ ├── mapper
│ │ │ ├── Mappable.java
│ │ │ ├── DataMapper.java
│ │ │ └── DataTestOptionsMapper.java
│ │ ├── dto
│ │ │ ├── DataTestOptionsDto.java
│ │ │ └── DataDto.java
│ │ └── controller
│ │ │ └── DataController.java
│ │ ├── model
│ │ ├── test
│ │ │ └── DataTestOptions.java
│ │ └── Data.java
│ │ ├── DataGeneratorMicroserviceApplication.java
│ │ └── config
│ │ ├── TextXpath.java
│ │ ├── BeanConfig.java
│ │ └── KafkaConfig.java
└── test
│ └── java
│ ├── KafkaDataServiceTest.java
│ └── TestDataServiceTest.java
├── Dockerfile
├── .gitignore
├── README.md
└── pom.xml
/.env.example:
--------------------------------------------------------------------------------
1 | KAFKA_BOOTSTRAP_SERVERS=localhost:9092
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "maven"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 |
--------------------------------------------------------------------------------
/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | config:
3 | import: optional:file:.env[.properties]
4 | kafka:
5 | bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS}
6 | server:
7 | port: 8081
8 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/service/KafkaDataService.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.service;
2 |
3 | import com.example.datageneratormicroservice.model.Data;
4 |
5 | public interface KafkaDataService {
6 |
7 | void send(Data data);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM maven:3.8.5-openjdk-17 AS build
2 | COPY pom.xml .
3 | RUN mvn dependency:go-offline
4 | COPY /src /src
5 | RUN mvn clean package -DskipTests
6 |
7 | FROM openjdk:17-jdk-slim
8 | COPY --from=build /target/*.jar application.jar
9 | EXPOSE 8081
10 | ENTRYPOINT ["java", "-jar", "application.jar"]
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/service/TestDataService.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.service;
2 |
3 | import com.example.datageneratormicroservice.model.test.DataTestOptions;
4 |
5 | public interface TestDataService {
6 |
7 | void sendMessages(DataTestOptions testOptions);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/resources/kafka/producer.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | org.apache.kafka.common.serialization.StringSerializer
5 |
6 |
7 | org.springframework.kafka.support.serializer.JsonSerializer
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/web/mapper/Mappable.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.web.mapper;
2 |
3 | import java.util.List;
4 |
5 | public interface Mappable {
6 |
7 | E toEntity(D d);
8 |
9 | List toEntity(List d);
10 |
11 | D toDto(E e);
12 |
13 | List toDto(List e);
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/web/mapper/DataMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.web.mapper;
2 |
3 | import com.example.datageneratormicroservice.model.Data;
4 | import com.example.datageneratormicroservice.web.dto.DataDto;
5 | import org.mapstruct.Mapper;
6 |
7 | @Mapper(componentModel = "spring")
8 | public interface DataMapper extends Mappable {
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/model/test/DataTestOptions.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.model.test;
2 |
3 | import com.example.datageneratormicroservice.model.Data;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | @NoArgsConstructor
9 | @Getter
10 | @Setter
11 | public class DataTestOptions {
12 |
13 | private int delayInSeconds;
14 | private Data.MeasurementType[] measurementTypes;
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/web/dto/DataTestOptionsDto.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.web.dto;
2 |
3 | import com.example.datageneratormicroservice.model.Data;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | @NoArgsConstructor
9 | @Getter
10 | @Setter
11 | public class DataTestOptionsDto {
12 |
13 | private int delayInSeconds;
14 | private Data.MeasurementType[] measurementTypes;
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/web/mapper/DataTestOptionsMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.web.mapper;
2 |
3 | import com.example.datageneratormicroservice.model.test.DataTestOptions;
4 | import com.example.datageneratormicroservice.web.dto.DataTestOptionsDto;
5 | import org.mapstruct.Mapper;
6 |
7 | @Mapper(componentModel = "spring")
8 | public interface DataTestOptionsMapper
9 | extends Mappable {
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/DataGeneratorMicroserviceApplication.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class DataGeneratorMicroserviceApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(DataGeneratorMicroserviceApplication.class, args);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
35 | .env
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/config/TextXpath.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.config;
2 |
3 | import com.jcabi.xml.XML;
4 | import lombok.RequiredArgsConstructor;
5 |
6 | @RequiredArgsConstructor
7 | public final class TextXpath {
8 |
9 | private final XML xml;
10 | private final String node;
11 |
12 | @Override
13 | public String toString() {
14 | return this.xml.nodes(this.node)
15 | .get(0)
16 | .xpath("text()")
17 | .get(0);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/model/Data.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.model;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 |
7 | import java.time.LocalDateTime;
8 |
9 | @NoArgsConstructor
10 | @Getter
11 | @Setter
12 | public class Data {
13 |
14 | private Long sensorId;
15 | private LocalDateTime timestamp;
16 | private double measurement;
17 | private MeasurementType measurementType;
18 |
19 | public enum MeasurementType {
20 |
21 | TEMPERATURE,
22 | VOLTAGE,
23 | POWER
24 |
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/config/BeanConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.config;
2 |
3 | import com.jcabi.xml.XML;
4 | import com.jcabi.xml.XMLDocument;
5 | import lombok.SneakyThrows;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 |
9 | @Configuration
10 | public class BeanConfig {
11 |
12 | @SneakyThrows
13 | @Bean
14 | public XML producerXml() {
15 | return new XMLDocument(
16 | getClass().getResourceAsStream("/kafka/producer.xml").readAllBytes()
17 | );
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/web/dto/DataDto.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.web.dto;
2 |
3 | import com.example.datageneratormicroservice.model.Data;
4 | import com.fasterxml.jackson.annotation.JsonFormat;
5 | import lombok.Getter;
6 | import lombok.NoArgsConstructor;
7 | import lombok.Setter;
8 |
9 | import java.time.LocalDateTime;
10 |
11 | @NoArgsConstructor
12 | @Getter
13 | @Setter
14 | public class DataDto {
15 |
16 | private Long sensorId;
17 |
18 | @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
19 | private LocalDateTime timestamp;
20 |
21 | private double measurement;
22 | private Data.MeasurementType measurementType;
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/codecov.yaml:
--------------------------------------------------------------------------------
1 | name: codecov
2 | on:
3 | push:
4 | branches: [ main ]
5 | pull_request:
6 | branches: [ main ]
7 |
8 | jobs:
9 | codecov:
10 | runs-on: ubuntu-20.04
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: actions/setup-java@v3
14 | with:
15 | distribution: 'temurin'
16 | java-version: 17
17 | - uses: actions/cache@v3
18 | with:
19 | path: ~/.m2/repository
20 | key: maven-${{ hashFiles('**/pom.xml') }}
21 | restore-keys: |
22 | maven-
23 | - run: |
24 | mvn clean install
25 | - name: Upload coverage reports to Codecov
26 | uses: codecov/codecov-action@v3
27 | env:
28 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
29 | with:
30 | files: ./target/site/jacoco/jacoco.xml
31 | fail_ci_if_error: false
--------------------------------------------------------------------------------
/.github/workflows/docker-upload.yml:
--------------------------------------------------------------------------------
1 | name: Docker Image CI
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v3
13 | - name: Login to Docker Hub
14 | uses: docker/login-action@v2
15 | with:
16 | username: ${{ secrets.DOCKERHUB_USERNAME }}
17 | password: ${{ secrets.DOCKERHUB_TOKEN }}
18 | - name: Set up Docker Buildx
19 | uses: docker/setup-buildx-action@v2
20 | - name: Build and Push to Docker Hub
21 | uses: mr-smithers-excellent/docker-build-push@v5
22 | with:
23 | image: ${{ secrets.DOCKERHUB_USERNAME }}/data-generator-microservice
24 | tags: 0.0.$GITHUB_RUN_NUMBER, latest
25 | dockerfile: Dockerfile
26 | registry: docker.io
27 | username: ${{ secrets.DOCKERHUB_USERNAME }}
28 | password: ${{ secrets.DOCKERHUB_TOKEN }}
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Data generator microservice
2 |
3 | [](https://codecov.io/gh/SpringBootCourses/data-generator-microservice)
4 |
5 | This is data generator microservice for [YouTube course](https://www.youtube.com/playlist?list=PL3Ur78l82EFBhKojbSO26BVqQ7n4AthHC).
6 |
7 | This application produces data and sends it to [Data consumer service](https://github.com/IlyaLisov/data-analyser-microservice) with Apache Kafka.
8 |
9 | ### Usage
10 |
11 | To start an application you need to pass variables to `.env` file.
12 |
13 | You can use example `.env.example` file with some predefined environments.
14 |
15 | Application is running on port `8081`.
16 |
17 | All insignificant features (checkstyle, build check, dto validation) are not presented.
18 |
19 | Application has two endpoints:
20 | * POST `/api/v1/data/send`
21 | #### Example JSON
22 | ```json
23 | {
24 | "sensorId": 1,
25 | "timestamp": "2023-09-12T12:10:05",
26 | "measurement": 15.5,
27 | "measurementType": "TEMPERATURE"
28 | }
29 | ```
30 |
31 | * POST `/api/v1/data/test/send`
32 | #### Example JSON
33 | ```json
34 | {
35 | "delayInSeconds": 3,
36 | "measurementTypes": [
37 | "POWER",
38 | "VOLTAGE",
39 | "TEMPERATURE"
40 | ]
41 | }
42 | ```
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/service/KafkaDataServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.service;
2 |
3 | import com.example.datageneratormicroservice.model.Data;
4 | import lombok.RequiredArgsConstructor;
5 | import org.springframework.stereotype.Service;
6 | import reactor.core.publisher.Mono;
7 | import reactor.kafka.sender.KafkaSender;
8 | import reactor.kafka.sender.SenderRecord;
9 |
10 | @Service
11 | @RequiredArgsConstructor
12 | public class KafkaDataServiceImpl implements KafkaDataService {
13 |
14 | private final KafkaSender sender;
15 |
16 | @Override
17 | public void send(Data data) {
18 | String topic = switch (data.getMeasurementType()) {
19 | case TEMPERATURE -> "data-temperature";
20 | case VOLTAGE -> "data-voltage";
21 | case POWER -> "data-power";
22 | };
23 | sender.send(
24 | Mono.just(
25 | SenderRecord.create(
26 | topic,
27 | 0,
28 | System.currentTimeMillis(),
29 | String.valueOf(data.hashCode()),
30 | data,
31 | null
32 | )
33 | )
34 | )
35 | .subscribe();
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/web/controller/DataController.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.web.controller;
2 |
3 | import com.example.datageneratormicroservice.model.Data;
4 | import com.example.datageneratormicroservice.model.test.DataTestOptions;
5 | import com.example.datageneratormicroservice.service.KafkaDataService;
6 | import com.example.datageneratormicroservice.service.TestDataService;
7 | import com.example.datageneratormicroservice.web.dto.DataDto;
8 | import com.example.datageneratormicroservice.web.dto.DataTestOptionsDto;
9 | import com.example.datageneratormicroservice.web.mapper.DataMapper;
10 | import com.example.datageneratormicroservice.web.mapper.DataTestOptionsMapper;
11 | import lombok.RequiredArgsConstructor;
12 | import org.springframework.web.bind.annotation.PostMapping;
13 | import org.springframework.web.bind.annotation.RequestBody;
14 | import org.springframework.web.bind.annotation.RequestMapping;
15 | import org.springframework.web.bind.annotation.RestController;
16 |
17 | @RestController
18 | @RequestMapping("/api/v1/data")
19 | @RequiredArgsConstructor
20 | public class DataController {
21 |
22 | private final KafkaDataService kafkaDataService;
23 | private final TestDataService testDataService;
24 |
25 | private final DataMapper dataMapper;
26 | private final DataTestOptionsMapper dataTestOptionsMapper;
27 |
28 | @PostMapping("/send")
29 | public void send(@RequestBody DataDto dataDto) {
30 | Data data = dataMapper.toEntity(dataDto);
31 | kafkaDataService.send(data);
32 | }
33 |
34 | @PostMapping("/test/send")
35 | public void testSend(@RequestBody DataTestOptionsDto testOptionsDto) {
36 | DataTestOptions testOptions = dataTestOptionsMapper.toEntity(testOptionsDto);
37 | testDataService.sendMessages(testOptions);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/service/TestDataServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.service;
2 |
3 | import com.example.datageneratormicroservice.model.Data;
4 | import com.example.datageneratormicroservice.model.test.DataTestOptions;
5 | import lombok.RequiredArgsConstructor;
6 | import org.springframework.stereotype.Service;
7 |
8 | import java.time.LocalDateTime;
9 | import java.util.concurrent.Executors;
10 | import java.util.concurrent.ScheduledExecutorService;
11 | import java.util.concurrent.TimeUnit;
12 |
13 | @Service
14 | @RequiredArgsConstructor
15 | public class TestDataServiceImpl implements TestDataService {
16 |
17 | private final ScheduledExecutorService executorService
18 | = Executors.newSingleThreadScheduledExecutor();
19 | private final KafkaDataService kafkaDataService;
20 |
21 | @Override
22 | public void sendMessages(DataTestOptions testOptions) {
23 | if (testOptions.getMeasurementTypes().length > 0) {
24 | executorService.scheduleAtFixedRate(
25 | () -> {
26 | Data data = new Data();
27 | data.setSensorId(
28 | (long) getRandomNumber(1, 10)
29 | );
30 | data.setMeasurement(
31 | getRandomNumber(15, 20)
32 | );
33 | data.setMeasurementType(
34 | getRandomMeasurement(
35 | testOptions.getMeasurementTypes()
36 | )
37 | );
38 | data.setTimestamp(
39 | LocalDateTime.now()
40 | );
41 | kafkaDataService.send(data);
42 | },
43 | 0,
44 | testOptions.getDelayInSeconds(),
45 | TimeUnit.SECONDS
46 | );
47 | }
48 | }
49 |
50 | private double getRandomNumber(double min, double max) {
51 | return (Math.random() * (max - min)) + min;
52 | }
53 |
54 | private Data.MeasurementType getRandomMeasurement(
55 | Data.MeasurementType[] measurementTypes
56 | ) {
57 | int randomTypeId = (int) (Math.random() * measurementTypes.length);
58 | return measurementTypes[randomTypeId];
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datageneratormicroservice/config/KafkaConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.datageneratormicroservice.config;
2 |
3 | import com.jcabi.xml.XML;
4 | import lombok.RequiredArgsConstructor;
5 | import org.apache.kafka.clients.admin.NewTopic;
6 | import org.apache.kafka.clients.producer.ProducerConfig;
7 | import org.apache.kafka.common.config.TopicConfig;
8 | import org.springframework.beans.factory.annotation.Value;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 | import org.springframework.kafka.config.TopicBuilder;
12 | import reactor.kafka.sender.KafkaSender;
13 | import reactor.kafka.sender.SenderOptions;
14 |
15 | import java.time.Duration;
16 | import java.util.HashMap;
17 | import java.util.Map;
18 |
19 | @Configuration
20 | @RequiredArgsConstructor
21 | public class KafkaConfig {
22 |
23 | @Value("${spring.kafka.bootstrap-servers}")
24 | private String servers;
25 |
26 | private final XML settings;
27 |
28 | @Bean
29 | public NewTopic temperatureTopic() {
30 | return TopicBuilder.name("data-temperature")
31 | .partitions(5)
32 | .replicas(1)
33 | .config(
34 | TopicConfig.RETENTION_MS_CONFIG,
35 | String.valueOf(Duration.ofDays(7).toMillis())
36 | )
37 | .build();
38 | }
39 |
40 | @Bean
41 | public NewTopic voltageTopic() {
42 | return TopicBuilder.name("data-voltage")
43 | .partitions(5)
44 | .replicas(1)
45 | .config(
46 | TopicConfig.RETENTION_MS_CONFIG,
47 | String.valueOf(Duration.ofDays(7).toMillis())
48 | )
49 | .build();
50 | }
51 |
52 | @Bean
53 | public NewTopic powerTopic() {
54 | return TopicBuilder.name("data-power")
55 | .partitions(5)
56 | .replicas(1)
57 | .config(
58 | TopicConfig.RETENTION_MS_CONFIG,
59 | String.valueOf(Duration.ofDays(7).toMillis())
60 | )
61 | .build();
62 | }
63 |
64 | @Bean
65 | public SenderOptions senderOptions() {
66 | Map props = new HashMap<>(3);
67 | props.put(
68 | ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
69 | servers
70 | );
71 | props.put(
72 | ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
73 | new TextXpath(this.settings, "//keySerializer")
74 | .toString()
75 | );
76 | props.put(
77 | ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
78 | new TextXpath(this.settings, "//valueSerializer")
79 | .toString()
80 | );
81 | return SenderOptions.create(props);
82 | }
83 |
84 | @Bean
85 | public KafkaSender sender(
86 | SenderOptions senderOptions
87 | ) {
88 | return KafkaSender.create(senderOptions);
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/test/java/KafkaDataServiceTest.java:
--------------------------------------------------------------------------------
1 | import com.example.datageneratormicroservice.DataGeneratorMicroserviceApplication;
2 | import com.example.datageneratormicroservice.model.Data;
3 | import com.example.datageneratormicroservice.web.dto.DataDto;
4 | import io.restassured.RestAssured;
5 | import io.restassured.http.ContentType;
6 | import org.apache.kafka.clients.admin.AdminClient;
7 | import org.apache.kafka.clients.admin.AdminClientConfig;
8 | import org.apache.kafka.clients.admin.ListTopicsResult;
9 | import org.awaitility.Awaitility;
10 | import org.junit.jupiter.api.Assertions;
11 | import org.junit.jupiter.api.BeforeAll;
12 | import org.junit.jupiter.api.BeforeEach;
13 | import org.junit.jupiter.api.MethodOrderer;
14 | import org.junit.jupiter.api.Order;
15 | import org.junit.jupiter.api.Test;
16 | import org.junit.jupiter.api.TestMethodOrder;
17 | import org.springframework.boot.test.context.SpringBootTest;
18 | import org.springframework.boot.test.context.TestConfiguration;
19 | import org.springframework.boot.test.web.server.LocalServerPort;
20 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
21 | import org.springframework.test.context.DynamicPropertyRegistry;
22 | import org.springframework.test.context.DynamicPropertySource;
23 | import org.testcontainers.containers.KafkaContainer;
24 | import org.testcontainers.junit.jupiter.Container;
25 | import org.testcontainers.junit.jupiter.Testcontainers;
26 | import org.testcontainers.utility.DockerImageName;
27 |
28 | import java.time.Duration;
29 | import java.time.LocalDateTime;
30 | import java.util.Map;
31 | import java.util.concurrent.TimeUnit;
32 |
33 | @Testcontainers
34 | @TestConfiguration(proxyBeanMethods = false)
35 | @SpringBootTest(classes = DataGeneratorMicroserviceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
36 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
37 | public class KafkaDataServiceTest {
38 |
39 | @Container
40 | @ServiceConnection
41 | protected static final KafkaContainer KAFKA = new KafkaContainer(
42 | DockerImageName.parse("confluentinc/cp-kafka:latest")
43 | );
44 |
45 | @DynamicPropertySource
46 | static void properties(DynamicPropertyRegistry registry) {
47 | registry.add("spring.kafka.bootstrap-servers", KAFKA::getBootstrapServers);
48 | }
49 |
50 | @BeforeAll
51 | static void setup() {
52 | KAFKA.addEnv("KAFKA_BROKER_ID", "1");
53 | KAFKA.addEnv("KAFKA_ADVERTISED_LISTENERS", KAFKA.getBootstrapServers());
54 | KAFKA.addEnv("KAFKA_LISTENERS", "LISTENER_PUBLIC://" + KAFKA.getContainerName() + ":29092,LISTENER_INTERNAL://" + KAFKA.getBootstrapServers());
55 | KAFKA.addEnv("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "LISTENER_PUBLIC:PLAINTEXT,LISTENER_INTERNAL:PLAINTEXT");
56 | adminClient = AdminClient.create(Map.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA.getBootstrapServers()));
57 | }
58 |
59 | @LocalServerPort
60 | private int port;
61 |
62 | private static AdminClient adminClient;
63 |
64 | @BeforeEach
65 | void setupRest() {
66 | RestAssured.baseURI = "http://localhost:" + port;
67 | }
68 |
69 | @Test
70 | @Order(1)
71 | public void testSendPower() {
72 | DataDto data = new DataDto();
73 | data.setSensorId(1L);
74 | data.setTimestamp(LocalDateTime.now());
75 | data.setMeasurementType(Data.MeasurementType.POWER);
76 | data.setMeasurement(15);
77 |
78 | Awaitility.await()
79 | .pollInterval(Duration.ofSeconds(3))
80 | .atMost(10, TimeUnit.SECONDS)
81 | .untilAsserted(() -> {
82 | RestAssured.given()
83 | .contentType(ContentType.JSON)
84 | .body(data)
85 | .post("/api/v1/data/send")
86 | .then()
87 | .statusCode(200);
88 |
89 | ListTopicsResult ltr = adminClient.listTopics();
90 | Assertions.assertTrue(ltr.names().get().contains("data-power"));
91 | });
92 | }
93 |
94 | // @Test
95 | // @Order(2)
96 | // void testConsumeMessagesFromKafka() {
97 | // Properties consumerProps = new Properties();
98 | // consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA.getBootstrapServers());
99 | // consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "1");
100 | // consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
101 | // consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
102 | // ReceiverOptions options = ReceiverOptions.create(consumerProps);
103 | // ReceiverOptions subscribedOptions = options.subscription(List.of("data-power"));
104 | //
105 | // KafkaReceiver consumer = KafkaReceiver.create(subscribedOptions);
106 | // Awaitility.await()
107 | // .pollInterval(Duration.ofSeconds(3))
108 | // .atMost(15, TimeUnit.SECONDS)
109 | // .untilAsserted(() -> consumer.receive().subscribe(r ->
110 | // Assertions.assertTrue(r.value().toString().contains("15"))
111 | // )
112 | // );
113 | // }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/src/test/java/TestDataServiceTest.java:
--------------------------------------------------------------------------------
1 | import com.example.datageneratormicroservice.DataGeneratorMicroserviceApplication;
2 | import com.example.datageneratormicroservice.model.Data;
3 | import com.example.datageneratormicroservice.web.dto.DataTestOptionsDto;
4 | import io.restassured.RestAssured;
5 | import io.restassured.http.ContentType;
6 | import org.apache.kafka.clients.admin.AdminClient;
7 | import org.apache.kafka.clients.admin.AdminClientConfig;
8 | import org.apache.kafka.clients.admin.ListTopicsResult;
9 | import org.awaitility.Awaitility;
10 | import org.junit.jupiter.api.Assertions;
11 | import org.junit.jupiter.api.BeforeAll;
12 | import org.junit.jupiter.api.BeforeEach;
13 | import org.junit.jupiter.api.MethodOrderer;
14 | import org.junit.jupiter.api.Order;
15 | import org.junit.jupiter.api.Test;
16 | import org.junit.jupiter.api.TestMethodOrder;
17 | import org.springframework.boot.test.context.SpringBootTest;
18 | import org.springframework.boot.test.context.TestConfiguration;
19 | import org.springframework.boot.test.web.server.LocalServerPort;
20 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
21 | import org.springframework.test.context.DynamicPropertyRegistry;
22 | import org.springframework.test.context.DynamicPropertySource;
23 | import org.testcontainers.containers.KafkaContainer;
24 | import org.testcontainers.junit.jupiter.Container;
25 | import org.testcontainers.junit.jupiter.Testcontainers;
26 | import org.testcontainers.utility.DockerImageName;
27 |
28 | import java.time.Duration;
29 | import java.util.Map;
30 | import java.util.concurrent.TimeUnit;
31 |
32 | @Testcontainers
33 | @TestConfiguration(proxyBeanMethods = false)
34 | @SpringBootTest(classes = DataGeneratorMicroserviceApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
35 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
36 | public class TestDataServiceTest {
37 |
38 | @Container
39 | @ServiceConnection
40 | protected static final KafkaContainer KAFKA = new KafkaContainer(
41 | DockerImageName.parse("confluentinc/cp-kafka:latest")
42 | );
43 |
44 | @DynamicPropertySource
45 | static void properties(DynamicPropertyRegistry registry) {
46 | registry.add("spring.kafka.bootstrap-servers", KAFKA::getBootstrapServers);
47 | }
48 |
49 | @BeforeAll
50 | static void setup() {
51 | KAFKA.addEnv("KAFKA_BROKER_ID", "1");
52 | KAFKA.addEnv("KAFKA_ADVERTISED_LISTENERS", KAFKA.getBootstrapServers());
53 | KAFKA.addEnv("KAFKA_LISTENERS", "LISTENER_PUBLIC://" + KAFKA.getContainerName() + ":29092,LISTENER_INTERNAL://" + KAFKA.getBootstrapServers());
54 | KAFKA.addEnv("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "LISTENER_PUBLIC:PLAINTEXT,LISTENER_INTERNAL:PLAINTEXT");
55 | adminClient = AdminClient.create(Map.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA.getBootstrapServers()));
56 | }
57 |
58 | @LocalServerPort
59 | private int port;
60 |
61 | private static AdminClient adminClient;
62 |
63 | @BeforeEach
64 | void setupRest() {
65 | RestAssured.baseURI = "http://localhost:" + port;
66 | }
67 |
68 | @Test
69 | @Order(1)
70 | public void testSendPower() {
71 | DataTestOptionsDto data = new DataTestOptionsDto();
72 | data.setDelayInSeconds(3);
73 | data.setMeasurementTypes(new Data.MeasurementType[]{Data.MeasurementType.POWER, Data.MeasurementType.VOLTAGE});
74 |
75 | Awaitility.await()
76 | .pollInterval(Duration.ofSeconds(3))
77 | .atMost(10, TimeUnit.SECONDS)
78 | .untilAsserted(() -> {
79 | RestAssured.given()
80 | .contentType(ContentType.JSON)
81 | .body(data)
82 | .post("/api/v1/data/test/send")
83 | .then()
84 | .statusCode(200);
85 |
86 | ListTopicsResult ltr = adminClient.listTopics();
87 | Assertions.assertTrue(ltr.names().get().contains("data-power"));
88 | Assertions.assertTrue(ltr.names().get().contains("data-voltage"));
89 | });
90 | }
91 |
92 | // @Test
93 | // @Order(2)
94 | // void testConsumeMessagesFromKafka() {
95 | // Properties consumerProps = new Properties();
96 | // consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA.getBootstrapServers());
97 | // consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "1");
98 | // consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
99 | // consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
100 | // ReceiverOptions options = ReceiverOptions.create(consumerProps);
101 | // ReceiverOptions subscribedOptions = options.subscription(List.of("data-power", "data-voltage"));
102 | //
103 | // KafkaReceiver consumer = KafkaReceiver.create(subscribedOptions);
104 | // Awaitility.await()
105 | // .pollInterval(Duration.ofSeconds(15))
106 | // .atMost(45, TimeUnit.SECONDS)
107 | // .untilAsserted(() -> consumer.receive().subscribe(r ->
108 | // Assertions.assertNotNull(r.value())
109 | // )
110 | // );
111 | // }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 4.0.0
7 |
8 | com.example
9 | data-generator-microservice
10 | 0.0.1-SNAPSHOT
11 | data-generator-microservice
12 | Data generator microservice
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 3.2.5
18 |
19 |
20 |
21 |
22 | 17
23 | 1.5.5.Final
24 | 1.18.32
25 | 3.1.4
26 | 0.29.0
27 | 1.3.22
28 | 0.8.12
29 | 5.4.0
30 | 4.2.1
31 |
32 |
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-web
37 |
38 |
39 |
40 | org.springframework.boot
41 | spring-boot-starter-test
42 | test
43 |
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-testcontainers
48 | test
49 |
50 |
51 |
52 | org.testcontainers
53 | kafka
54 | test
55 |
56 |
57 |
58 | org.projectlombok
59 | lombok
60 | true
61 | ${lombok.version}
62 |
63 |
64 |
65 | org.mapstruct
66 | mapstruct
67 | ${mapstruct.version}
68 |
69 |
70 |
71 | org.mapstruct
72 | mapstruct-processor
73 | ${mapstruct.version}
74 |
75 |
76 |
77 | org.springframework.kafka
78 | spring-kafka
79 | ${spring-kafka.version}
80 |
81 |
82 |
83 | io.projectreactor.kafka
84 | reactor-kafka
85 | ${reactor-kafka.version}
86 |
87 |
88 |
89 | com.jcabi
90 | jcabi-xml
91 | ${xml.version}
92 |
93 |
94 |
95 | org.testcontainers
96 | junit-jupiter
97 | test
98 |
99 |
100 |
101 | io.rest-assured
102 | rest-assured
103 | test
104 | ${rest-assured.version}
105 |
106 |
107 |
108 | org.awaitility
109 | awaitility
110 | ${awaitility.version}
111 | test
112 |
113 |
114 |
115 |
116 |
117 |
118 | org.testcontainers
119 | testcontainers-bom
120 | 1.19.7
121 | pom
122 | import
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | org.springframework.boot
131 | spring-boot-maven-plugin
132 |
133 |
134 |
135 | org.projectlombok
136 | lombok
137 |
138 |
139 |
140 |
141 |
142 | org.jacoco
143 | jacoco-maven-plugin
144 | ${jacoco.version}
145 |
146 |
147 | default-prepare-agent
148 |
149 | prepare-agent
150 |
151 |
152 |
153 | report
154 | prepare-package
155 |
156 | report
157 |
158 |
159 |
160 | check
161 |
162 | check
163 |
164 |
165 |
166 |
167 | BUNDLE
168 |
169 |
170 | INSTRUCTION
171 | COVEREDRATIO
172 | 0.65
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
--------------------------------------------------------------------------------