├── .env.example
├── .github
├── dependabot.yml
└── workflows
│ └── docker-upload.yml
├── .gitignore
├── Dockerfile
├── README.md
├── default-debezium.json
├── pom.xml
└── src
└── main
├── java
└── com
│ └── example
│ └── datastore
│ ├── DataStoreApplication.java
│ ├── config
│ ├── KeyHelper.java
│ ├── RedisConfig.java
│ └── RedisSchema.java
│ ├── model
│ ├── Data.java
│ ├── MeasurementType.java
│ ├── Summary.java
│ ├── SummaryType.java
│ └── exception
│ │ └── SensorNotFoundException.java
│ ├── repository
│ ├── SummaryRepository.java
│ └── SummaryRepositoryImpl.java
│ ├── service
│ ├── CDCEventConsumer.java
│ ├── DebeziumEventConsumerImpl.java
│ ├── SummaryService.java
│ └── SummaryServiceImpl.java
│ └── web
│ ├── controller
│ ├── AnalyticsController.java
│ └── ControllerAdvice.java
│ ├── dto
│ └── SummaryDto.java
│ └── mapper
│ ├── Mappable.java
│ └── SummaryMapper.java
└── resources
└── application.yaml
/.env.example:
--------------------------------------------------------------------------------
1 | REDIS_HOST=localhost
2 | REDIS_PORT=6379
3 | KAFKA_BOOTSTRAP_SERVERS=localhost:9092
4 |
5 | KAFKA_BROKER_ID=1
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "maven"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 |
--------------------------------------------------------------------------------
/.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-store-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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 | replay_pid*
25 |
26 | .env
--------------------------------------------------------------------------------
/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 8083
10 | ENTRYPOINT ["java", "-jar", "application.jar"]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Data store microservice
2 |
3 | This is data store microservice
4 | for [YouTube course](https://www.youtube.com/playlist?list=PL3Ur78l82EFBhKojbSO26BVqQ7n4AthHC).
5 |
6 | This application receives data
7 | from [Data analyser service](https://github.com/IlyaLisov/data-analyser-microservice)
8 | with Apache Kafka and Debezium.
9 |
10 | ### Usage
11 |
12 | To start an application you need to pass variables to `.env` file.
13 |
14 | You can use example `.env.example` file with some predefined environments.
15 |
16 | You can find Docker compose file
17 | in [Data analyser service](https://github.com/IlyaLisov/data-analyser-microservice) `docker/docker-compose.yaml`.
18 |
19 | Application is running on port `8083`.
20 |
21 | All insignificant features (checkstyle, build check, dto validation) are not
22 | presented.
23 |
24 | Just after startup application will try to connect to Apache Kafka and begin to
25 | listen topics from `data` topic created by Debezium.
26 |
--------------------------------------------------------------------------------
/default-debezium.json:
--------------------------------------------------------------------------------
1 | {
2 | "schema": {
3 | "type": "struct",
4 | "fields": [
5 | {
6 | "type": "int64",
7 | "optional": false,
8 | "default": 0,
9 | "field": "id"
10 | },
11 | {
12 | "type": "int64",
13 | "optional": false,
14 | "field": "sensor_id"
15 | },
16 | {
17 | "type": "int64",
18 | "optional": false,
19 | "name": "io.debezium.time.MicroTimestamp",
20 | "version": 1,
21 | "field": "timestamp"
22 | },
23 | {
24 | "type": "double",
25 | "optional": false,
26 | "field": "measurement"
27 | },
28 | {
29 | "type": "string",
30 | "optional": false,
31 | "field": "type"
32 | }
33 | ],
34 | "optional": false,
35 | "name": "pg-replica.public.data.Value"
36 | },
37 | "payload": {
38 | "id": 66,
39 | "sensor_id": 1,
40 | "timestamp": 1694607005000000,
41 | "measurement": 12.5,
42 | "type": "TEMPERATURE"
43 | }
44 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 4.0.0
7 |
8 | com.example
9 | data-store-microservice
10 | 0.0.1-SNAPSHOT
11 | data-store-microservice
12 | data-store-microservice
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 3.2.5
18 |
19 |
20 |
21 |
22 | 17
23 | 1.18.32
24 | 1.5.5.Final
25 | 5.1.2
26 | 3.1.4
27 |
28 |
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-web
33 |
34 |
35 |
36 | org.projectlombok
37 | lombok
38 | true
39 | ${lombok.version}
40 |
41 |
42 |
43 | redis.clients
44 | jedis
45 | ${jedis.version}
46 |
47 |
48 |
49 | org.mapstruct
50 | mapstruct
51 | ${mapstruct.version}
52 |
53 |
54 |
55 | org.mapstruct
56 | mapstruct-processor
57 | ${mapstruct.version}
58 |
59 |
60 |
61 | org.springframework.kafka
62 | spring-kafka
63 | ${spring-kafka.version}
64 |
65 |
66 |
67 |
68 |
69 |
70 | org.springframework.boot
71 | spring-boot-maven-plugin
72 |
73 |
74 |
75 | org.projectlombok
76 | lombok
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/DataStoreApplication.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class DataStoreApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(DataStoreApplication.class, args);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/config/KeyHelper.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.config;
2 |
3 | import java.util.Objects;
4 |
5 | public class KeyHelper {
6 |
7 | final private static String defaultPrefix = "app";
8 |
9 | private static String prefix = null;
10 |
11 | public static void setPrefix(String keyPrefix) {
12 | prefix = keyPrefix;
13 | }
14 |
15 | public static String getKey(String key) {
16 | return getPrefix() + ":" + key;
17 | }
18 |
19 | public static String getPrefix() {
20 | return Objects.requireNonNullElse(prefix, defaultPrefix);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/config/RedisConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.config;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import redis.clients.jedis.JedisPool;
7 | import redis.clients.jedis.JedisPoolConfig;
8 |
9 | @Configuration
10 | public class RedisConfig {
11 |
12 | @Value("${spring.data.redis.host}")
13 | private String host;
14 |
15 | @Value("${spring.data.redis.port}")
16 | private int port;
17 |
18 | @Bean
19 | public JedisPool jedisPool() {
20 | JedisPoolConfig config = new JedisPoolConfig();
21 | config.setJmxEnabled(false);
22 | return new JedisPool(config, host, port);
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/config/RedisSchema.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.config;
2 |
3 | import com.example.datastore.model.MeasurementType;
4 |
5 | public class RedisSchema {
6 |
7 | //set
8 | public static String sensorKeys() {
9 | return KeyHelper.getKey("sensors");
10 | }
11 |
12 | //hash with summary types
13 | public static String summaryKey(
14 | long sensorId,
15 | MeasurementType measurementType
16 | ) {
17 | return KeyHelper.getKey("sensors:" + sensorId + ":" + measurementType.name().toLowerCase());
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/model/Data.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.model;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | import java.time.LocalDateTime;
9 |
10 | @NoArgsConstructor
11 | @Getter
12 | @Setter
13 | @ToString
14 | public class Data {
15 |
16 | private Long id;
17 | private Long sensorId;
18 | private LocalDateTime timestamp;
19 | private double measurement;
20 | private MeasurementType measurementType;
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/model/MeasurementType.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.model;
2 |
3 | public enum MeasurementType {
4 |
5 | TEMPERATURE,
6 | VOLTAGE,
7 | POWER
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/model/Summary.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.model;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | import java.util.ArrayList;
9 | import java.util.HashMap;
10 | import java.util.List;
11 | import java.util.Map;
12 |
13 | @Getter
14 | @Setter
15 | @ToString
16 | public class Summary {
17 |
18 | private long sensorId;
19 | private Map> values;
20 |
21 | @NoArgsConstructor
22 | @Getter
23 | @Setter
24 | @ToString
25 | public static class SummaryEntry {
26 |
27 | private SummaryType type;
28 | private double value;
29 | private long counter;
30 |
31 | }
32 |
33 | public Summary() {
34 | this.values = new HashMap<>();
35 | }
36 |
37 | public void addValue(MeasurementType type, SummaryEntry value) {
38 | if (values.containsKey(type)) {
39 | List entries = new ArrayList<>(values.get(type));
40 | entries.add(value);
41 | values.put(type, entries);
42 | } else {
43 | values.put(type, List.of(value));
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/model/SummaryType.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.model;
2 |
3 | public enum SummaryType {
4 |
5 | MIN,
6 | MAX,
7 | AVG,
8 | SUM
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/model/exception/SensorNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.model.exception;
2 |
3 | public class SensorNotFoundException extends RuntimeException {
4 |
5 | public SensorNotFoundException() {
6 | super();
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/repository/SummaryRepository.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.repository;
2 |
3 | import com.example.datastore.model.Data;
4 | import com.example.datastore.model.MeasurementType;
5 | import com.example.datastore.model.Summary;
6 | import com.example.datastore.model.SummaryType;
7 |
8 | import java.util.Optional;
9 | import java.util.Set;
10 |
11 | public interface SummaryRepository {
12 |
13 | Optional findBySensorId(
14 | long sensorId,
15 | Set measurementTypes,
16 | Set summaryTypes
17 | );
18 |
19 | void handle(
20 | Data data
21 | );
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/repository/SummaryRepositoryImpl.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.repository;
2 |
3 | import com.example.datastore.config.RedisSchema;
4 | import com.example.datastore.model.Data;
5 | import com.example.datastore.model.MeasurementType;
6 | import com.example.datastore.model.Summary;
7 | import com.example.datastore.model.SummaryType;
8 | import lombok.RequiredArgsConstructor;
9 | import org.springframework.stereotype.Repository;
10 | import redis.clients.jedis.Jedis;
11 | import redis.clients.jedis.JedisPool;
12 |
13 | import java.util.Optional;
14 | import java.util.Set;
15 |
16 | @Repository
17 | @RequiredArgsConstructor
18 | public class SummaryRepositoryImpl implements SummaryRepository {
19 |
20 | private final JedisPool jedisPool;
21 |
22 | @Override
23 | public Optional findBySensorId(
24 | long sensorId,
25 | Set measurementTypes,
26 | Set summaryTypes
27 | ) {
28 | try (Jedis jedis = jedisPool.getResource()) {
29 | if (!jedis.sismember(
30 | RedisSchema.sensorKeys(),
31 | String.valueOf(sensorId)
32 | )) {
33 | return Optional.empty();
34 | }
35 | if (measurementTypes.isEmpty() && !summaryTypes.isEmpty()) {
36 | return getSummary(
37 | sensorId,
38 | Set.of(MeasurementType.values()),
39 | summaryTypes,
40 | jedis
41 | );
42 | } else if (!measurementTypes.isEmpty() && summaryTypes.isEmpty()) {
43 | return getSummary(
44 | sensorId,
45 | measurementTypes,
46 | Set.of(SummaryType.values()),
47 | jedis
48 | );
49 | } else {
50 | return getSummary(
51 | sensorId,
52 | measurementTypes,
53 | summaryTypes,
54 | jedis
55 | );
56 | }
57 | }
58 | }
59 |
60 | private Optional getSummary(
61 | long sensorId,
62 | Set measurementTypes,
63 | Set summaryTypes,
64 | Jedis jedis
65 | ) {
66 | Summary summary = new Summary();
67 | summary.setSensorId(sensorId);
68 | for (MeasurementType mType : measurementTypes) {
69 | for (SummaryType sType : summaryTypes) {
70 | Summary.SummaryEntry entry = new Summary.SummaryEntry();
71 | entry.setType(sType);
72 | String value = jedis.hget(
73 | RedisSchema.summaryKey(sensorId, mType),
74 | sType.name().toLowerCase()
75 | );
76 | if (value != null) {
77 | entry.setValue(Double.parseDouble(value));
78 | }
79 | String counter = jedis.hget(
80 | RedisSchema.summaryKey(sensorId, mType),
81 | "counter"
82 | );
83 | if (counter != null) {
84 | entry.setCounter(Long.parseLong(counter));
85 | }
86 | summary.addValue(mType, entry);
87 | }
88 | }
89 | return Optional.of(summary);
90 | }
91 |
92 | @Override
93 | public void handle(
94 | Data data
95 | ) {
96 | try (Jedis jedis = jedisPool.getResource()) {
97 | if (!jedis.sismember(
98 | RedisSchema.sensorKeys(),
99 | String.valueOf(data.getSensorId())
100 | )) {
101 | jedis.sadd(
102 | RedisSchema.sensorKeys(),
103 | String.valueOf(data.getSensorId())
104 | );
105 | }
106 | updateMinValue(data, jedis);
107 | updateMaxValue(data, jedis);
108 | updateSumAndAvgValue(data, jedis);
109 | }
110 | }
111 |
112 | private void updateMinValue(
113 | Data data,
114 | Jedis jedis
115 | ) {
116 | String key = RedisSchema.summaryKey(
117 | data.getSensorId(),
118 | data.getMeasurementType()
119 | );
120 | String value = jedis.hget(
121 | key,
122 | SummaryType.MIN.name().toLowerCase()
123 | );
124 | if (value == null || data.getMeasurement() < Double.parseDouble(value)) {
125 | jedis.hset(
126 | key,
127 | SummaryType.MIN.name().toLowerCase(),
128 | String.valueOf(data.getMeasurement())
129 | );
130 | }
131 | }
132 |
133 | private void updateMaxValue(
134 | Data data,
135 | Jedis jedis
136 | ) {
137 | String key = RedisSchema.summaryKey(
138 | data.getSensorId(),
139 | data.getMeasurementType()
140 | );
141 | String value = jedis.hget(
142 | key,
143 | SummaryType.MAX.name().toLowerCase()
144 | );
145 | if (value == null || data.getMeasurement() > Double.parseDouble(value)) {
146 | jedis.hset(
147 | key,
148 | SummaryType.MAX.name().toLowerCase(),
149 | String.valueOf(data.getMeasurement())
150 | );
151 | }
152 | }
153 |
154 | private void updateSumAndAvgValue(
155 | Data data,
156 | Jedis jedis
157 | ) {
158 | updateSumValue(data, jedis);
159 | String key = RedisSchema.summaryKey(
160 | data.getSensorId(),
161 | data.getMeasurementType()
162 | );
163 | String counter = jedis.hget(
164 | key,
165 | "counter"
166 | );
167 | if (counter == null) {
168 | counter = String.valueOf(
169 | jedis.hset(
170 | key,
171 | "counter",
172 | String.valueOf(1)
173 | )
174 | );
175 | } else {
176 | counter = String.valueOf(
177 | jedis.hincrBy(
178 | key,
179 | "counter",
180 | 1
181 | )
182 | );
183 | }
184 | String sum = jedis.hget(
185 | key,
186 | SummaryType.SUM.name().toLowerCase()
187 | );
188 | jedis.hset(
189 | key,
190 | SummaryType.AVG.name().toLowerCase(),
191 | String.valueOf(
192 | Double.parseDouble(sum) / Double.parseDouble(counter)
193 | )
194 | );
195 | }
196 |
197 | private void updateSumValue(
198 | Data data,
199 | Jedis jedis
200 | ) {
201 | String key = RedisSchema.summaryKey(
202 | data.getSensorId(),
203 | data.getMeasurementType()
204 | );
205 | String value = jedis.hget(
206 | key,
207 | SummaryType.SUM.name().toLowerCase()
208 | );
209 | if (value == null) {
210 | jedis.hset(
211 | key,
212 | SummaryType.SUM.name().toLowerCase(),
213 | String.valueOf(data.getMeasurement())
214 | );
215 | } else {
216 | jedis.hincrByFloat(
217 | key,
218 | SummaryType.SUM.name().toLowerCase(),
219 | data.getMeasurement()
220 | );
221 | }
222 | }
223 |
224 | }
225 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/service/CDCEventConsumer.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.service;
2 |
3 | public interface CDCEventConsumer {
4 |
5 | void handle(String message);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/service/DebeziumEventConsumerImpl.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.service;
2 |
3 | import com.example.datastore.model.Data;
4 | import com.example.datastore.model.MeasurementType;
5 | import com.google.gson.JsonObject;
6 | import com.google.gson.JsonParser;
7 | import lombok.RequiredArgsConstructor;
8 | import org.springframework.kafka.annotation.KafkaListener;
9 | import org.springframework.stereotype.Service;
10 |
11 | import java.time.Instant;
12 | import java.time.LocalDateTime;
13 | import java.util.TimeZone;
14 |
15 | @Service
16 | @RequiredArgsConstructor
17 | public class DebeziumEventConsumerImpl implements CDCEventConsumer {
18 |
19 | private final SummaryService summaryService;
20 |
21 | @KafkaListener(topics = "data")
22 | public void handle(String message) {
23 | try {
24 | JsonObject payload = JsonParser.parseString(message)
25 | .getAsJsonObject()
26 | .get("payload")
27 | .getAsJsonObject();
28 | Data data = new Data();
29 | data.setId(
30 | payload.get("id")
31 | .getAsLong()
32 | );
33 | data.setSensorId(
34 | payload.get("sensor_id")
35 | .getAsLong()
36 | );
37 | data.setMeasurement(
38 | payload.get("measurement")
39 | .getAsDouble()
40 | );
41 | data.setMeasurementType(
42 | MeasurementType.valueOf(
43 | payload.get("type")
44 | .getAsString()
45 | )
46 | );
47 | data.setTimestamp(
48 | LocalDateTime.ofInstant(
49 | Instant.ofEpochMilli(
50 | payload.get("timestamp")
51 | .getAsLong() / 1000
52 | ),
53 | TimeZone.getDefault()
54 | .toZoneId()
55 | )
56 | );
57 | summaryService.handle(data);
58 | } catch (Exception e) {
59 | e.printStackTrace();
60 | }
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/service/SummaryService.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.service;
2 |
3 | import com.example.datastore.model.Data;
4 | import com.example.datastore.model.MeasurementType;
5 | import com.example.datastore.model.Summary;
6 | import com.example.datastore.model.SummaryType;
7 |
8 | import java.util.Set;
9 |
10 | public interface SummaryService {
11 |
12 | Summary get(
13 | Long sensorId,
14 | Set measurementTypes,
15 | Set summaryTypes
16 | );
17 |
18 | void handle(
19 | Data data
20 | );
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/service/SummaryServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.service;
2 |
3 | import com.example.datastore.model.Data;
4 | import com.example.datastore.model.MeasurementType;
5 | import com.example.datastore.model.Summary;
6 | import com.example.datastore.model.SummaryType;
7 | import com.example.datastore.model.exception.SensorNotFoundException;
8 | import com.example.datastore.repository.SummaryRepository;
9 | import lombok.RequiredArgsConstructor;
10 | import org.springframework.stereotype.Service;
11 |
12 | import java.util.Set;
13 |
14 | @Service
15 | @RequiredArgsConstructor
16 | public class SummaryServiceImpl implements SummaryService {
17 |
18 | private final SummaryRepository summaryRepository;
19 |
20 | @Override
21 | public Summary get(
22 | Long sensorId,
23 | Set measurementTypes,
24 | Set summaryTypes
25 | ) {
26 | return summaryRepository.findBySensorId(
27 | sensorId,
28 | measurementTypes == null ? Set.of(MeasurementType.values()) : measurementTypes,
29 | summaryTypes == null ? Set.of(SummaryType.values()) : summaryTypes
30 | )
31 | .orElseThrow(SensorNotFoundException::new);
32 | }
33 |
34 | @Override
35 | public void handle(
36 | Data data
37 | ) {
38 | summaryRepository.handle(data);
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/web/controller/AnalyticsController.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.web.controller;
2 |
3 | import com.example.datastore.model.MeasurementType;
4 | import com.example.datastore.model.Summary;
5 | import com.example.datastore.model.SummaryType;
6 | import com.example.datastore.service.SummaryService;
7 | import com.example.datastore.web.dto.SummaryDto;
8 | import com.example.datastore.web.mapper.SummaryMapper;
9 | import lombok.RequiredArgsConstructor;
10 | import org.springframework.web.bind.annotation.GetMapping;
11 | import org.springframework.web.bind.annotation.PathVariable;
12 | import org.springframework.web.bind.annotation.RequestMapping;
13 | import org.springframework.web.bind.annotation.RequestParam;
14 | import org.springframework.web.bind.annotation.RestController;
15 |
16 | import java.util.Set;
17 |
18 | @RestController
19 | @RequestMapping("/api/v1/analytics")
20 | @RequiredArgsConstructor
21 | public class AnalyticsController {
22 |
23 | private final SummaryService summaryService;
24 |
25 | private final SummaryMapper summaryMapper;
26 |
27 | @GetMapping("/summary/{sensorId}")
28 | public SummaryDto getSummary(
29 | @PathVariable long sensorId,
30 | @RequestParam(value = "mt", required = false)
31 | Set measurementTypes,
32 | @RequestParam(value = "st", required = false)
33 | Set summaryTypes
34 | ) {
35 | Summary summary = summaryService.get(
36 | sensorId,
37 | measurementTypes,
38 | summaryTypes
39 | );
40 | return summaryMapper.toDto(summary);
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/web/controller/ControllerAdvice.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.web.controller;
2 |
3 | import com.example.datastore.model.exception.SensorNotFoundException;
4 | import org.springframework.web.bind.annotation.ExceptionHandler;
5 | import org.springframework.web.bind.annotation.RestControllerAdvice;
6 |
7 | @RestControllerAdvice
8 | public class ControllerAdvice {
9 |
10 | @ExceptionHandler(SensorNotFoundException.class)
11 | public String sensorNotFound(SensorNotFoundException e) {
12 | return "Sensor not found.";
13 | }
14 |
15 | @ExceptionHandler
16 | public String server(Exception e) {
17 | e.printStackTrace();
18 | return "Something happened.";
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/web/dto/SummaryDto.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.web.dto;
2 |
3 | import com.example.datastore.model.MeasurementType;
4 | import com.example.datastore.model.Summary;
5 | import lombok.Getter;
6 | import lombok.NoArgsConstructor;
7 | import lombok.Setter;
8 | import lombok.ToString;
9 |
10 | import java.util.List;
11 | import java.util.Map;
12 |
13 | @NoArgsConstructor
14 | @Getter
15 | @Setter
16 | @ToString
17 | public class SummaryDto {
18 |
19 | private long sensorId;
20 | private Map> values;
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/example/datastore/web/mapper/Mappable.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.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/datastore/web/mapper/SummaryMapper.java:
--------------------------------------------------------------------------------
1 | package com.example.datastore.web.mapper;
2 |
3 | import com.example.datastore.model.Summary;
4 | import com.example.datastore.web.dto.SummaryDto;
5 | import org.mapstruct.Mapper;
6 |
7 | @Mapper(componentModel = "spring")
8 | public interface SummaryMapper extends Mappable {
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | spring:
2 | config:
3 | import: optional:file:.env[.properties]
4 | data:
5 | redis:
6 | host: ${REDIS_HOST}
7 | port: ${REDIS_PORT}
8 | kafka:
9 | consumer:
10 | bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS}
11 | group-id: ${KAFKA_BROKER_ID}
12 | auto-offset-reset: earliest
13 | key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
14 | value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
15 | server:
16 | port: 8083
17 |
--------------------------------------------------------------------------------