├── query ├── src │ └── main │ │ ├── java │ │ ├── lombok.config │ │ └── com │ │ │ └── github │ │ │ └── joumenharzli │ │ │ └── cdc │ │ │ └── query │ │ │ ├── service │ │ │ ├── dto │ │ │ │ ├── AddressDto.java │ │ │ │ ├── QueryParameter.java │ │ │ │ ├── QueryOperator.java │ │ │ │ ├── JobDto.java │ │ │ │ └── UserDto.java │ │ │ ├── mapper │ │ │ │ ├── JobMapper.java │ │ │ │ ├── AddressMapper.java │ │ │ │ └── UserMapper.java │ │ │ ├── UserService.java │ │ │ └── UserServiceImpl.java │ │ │ ├── domain │ │ │ ├── Address.java │ │ │ ├── Job.java │ │ │ └── User.java │ │ │ ├── QueryApplication.java │ │ │ ├── web │ │ │ ├── error │ │ │ │ ├── RestFieldErrorDto.java │ │ │ │ ├── RestErrorDto.java │ │ │ │ ├── RestErrorConstants.java │ │ │ │ ├── RestFieldsErrorsDto.java │ │ │ │ └── RestExceptionTranslator.java │ │ │ └── UserResource.java │ │ │ ├── repository │ │ │ └── UserRepository.java │ │ │ ├── config │ │ │ ├── LocalizationConfiguration.java │ │ │ └── ElasticsearchConfiguration.java │ │ │ ├── util │ │ │ └── QueryUtils.java │ │ │ └── aop │ │ │ └── ReactiveTimedAspect.java │ │ └── resources │ │ ├── i18n │ │ ├── messages_fr.properties │ │ └── messages_en.properties │ │ ├── application.yml │ │ └── logback-spring.xml ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ ├── maven-wrapper.properties │ │ └── MavenWrapperDownloader.java ├── Dockerfile ├── pom.xml └── mvnw.cmd ├── command ├── src │ └── main │ │ ├── java │ │ ├── lombok.config │ │ └── com │ │ │ └── github │ │ │ └── joumenharzli │ │ │ └── cdc │ │ │ └── command │ │ │ ├── service │ │ │ ├── dto │ │ │ │ ├── AddressDto.java │ │ │ │ ├── JobDto.java │ │ │ │ └── UserDto.java │ │ │ ├── mapper │ │ │ │ ├── UserMapper.java │ │ │ │ ├── JobMapper.java │ │ │ │ └── AddressMapper.java │ │ │ ├── UserService.java │ │ │ └── UserServiceImpl.java │ │ │ ├── exception │ │ │ └── EntityNotFoundException.java │ │ │ ├── web │ │ │ ├── error │ │ │ │ ├── RestFieldErrorDto.java │ │ │ │ ├── RestErrorDto.java │ │ │ │ ├── RestErrorConstants.java │ │ │ │ ├── RestFieldsErrorsDto.java │ │ │ │ └── RestExceptionTranslator.java │ │ │ └── UserResource.java │ │ │ ├── repository │ │ │ └── UserRepository.java │ │ │ ├── CommandApplication.java │ │ │ ├── domain │ │ │ ├── Address.java │ │ │ ├── Job.java │ │ │ └── User.java │ │ │ ├── config │ │ │ └── LocalizationConfiguration.java │ │ │ └── aop │ │ │ └── ReactiveTimedAspect.java │ │ └── resources │ │ ├── i18n │ │ ├── messages_fr.properties │ │ └── messages_en.properties │ │ ├── application.yml │ │ └── logback-spring.xml ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ ├── maven-wrapper.properties │ │ └── MavenWrapperDownloader.java ├── Dockerfile ├── pom.xml └── mvnw.cmd ├── denormalizer ├── src │ └── main │ │ ├── java │ │ ├── lombok.config │ │ └── com │ │ │ └── github │ │ │ └── joumenharzli │ │ │ └── cdc │ │ │ └── denormalizer │ │ │ ├── listener │ │ │ ├── EventHandler.java │ │ │ ├── support │ │ │ │ └── DebeziumEvent.java │ │ │ ├── handler │ │ │ │ ├── AbstractSimpleEventHandler.java │ │ │ │ ├── UserEventHandler.java │ │ │ │ ├── JobEventHandler.java │ │ │ │ └── AddressEventHandler.java │ │ │ ├── EventDispatcher.java │ │ │ └── EventHandlerFactory.java │ │ │ ├── DenormalizerApplication.java │ │ │ ├── domain │ │ │ ├── Address.java │ │ │ ├── Job.java │ │ │ └── User.java │ │ │ ├── exception │ │ │ └── EntityNotFoundException.java │ │ │ ├── repository │ │ │ └── UserRepository.java │ │ │ ├── service │ │ │ ├── mapper │ │ │ │ ├── JobMapper.java │ │ │ │ ├── UserMapper.java │ │ │ │ └── AddressMapper.java │ │ │ ├── dto │ │ │ │ ├── UserDto.java │ │ │ │ ├── AddressDto.java │ │ │ │ └── JobDto.java │ │ │ ├── UserService.java │ │ │ └── UserServiceImpl.java │ │ │ ├── config │ │ │ ├── ElasticsearchConfiguration.java │ │ │ └── KafkaConfiguration.java │ │ │ └── aop │ │ │ └── TimedAspect.java │ │ └── resources │ │ ├── application.yml │ │ └── logback-spring.xml ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.properties │ │ ├── maven-wrapper.jar │ │ └── MavenWrapperDownloader.java ├── Dockerfile └── pom.xml ├── mysql ├── initdb.sql ├── Dockerfile └── mysql.cnf ├── connect └── Dockerfile ├── clear_query.sh ├── .gitignore ├── test_query.sh ├── test_command.sh ├── activate_connector.sh ├── pom.xml ├── README.md └── docker-compose.yml /query/src/main/java/lombok.config: -------------------------------------------------------------------------------- 1 | lombok.log.fieldName = LOGGER 2 | -------------------------------------------------------------------------------- /command/src/main/java/lombok.config: -------------------------------------------------------------------------------- 1 | lombok.log.fieldName = LOGGER 2 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/lombok.config: -------------------------------------------------------------------------------- 1 | lombok.log.fieldName = LOGGER 2 | -------------------------------------------------------------------------------- /mysql/initdb.sql: -------------------------------------------------------------------------------- 1 | GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'debezium' IDENTIFIED BY 'dbz'; -------------------------------------------------------------------------------- /query/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joumenharzli/spring-rdbms-cdc-kafka-elasticsearch/HEAD/query/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /query/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip -------------------------------------------------------------------------------- /command/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joumenharzli/spring-rdbms-cdc-kafka-elasticsearch/HEAD/command/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /command/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip -------------------------------------------------------------------------------- /denormalizer/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip -------------------------------------------------------------------------------- /mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:5.7 2 | 3 | MAINTAINER Joumen Ali HARZLI 4 | 5 | COPY mysql.cnf /etc/mysql/conf.d/ 6 | COPY initdb.sql /docker-entrypoint-initdb.d/ -------------------------------------------------------------------------------- /denormalizer/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joumenharzli/spring-rdbms-cdc-kafka-elasticsearch/HEAD/denormalizer/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /command/src/main/resources/i18n/messages_fr.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joumenharzli/spring-rdbms-cdc-kafka-elasticsearch/HEAD/command/src/main/resources/i18n/messages_fr.properties -------------------------------------------------------------------------------- /query/src/main/resources/i18n/messages_fr.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joumenharzli/spring-rdbms-cdc-kafka-elasticsearch/HEAD/query/src/main/resources/i18n/messages_fr.properties -------------------------------------------------------------------------------- /mysql/mysql.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | 3 | # Enable the binlog for replication & CDC 4 | server-id = 223344 5 | log_bin = mysql-bin 6 | binlog_format = row 7 | binlog_row_image = full 8 | expire_logs_days = 10 9 | 10 | # Enable GTIDs on this master 11 | gtid_mode = on 12 | enforce_gtid_consistency = on 13 | 14 | -------------------------------------------------------------------------------- /query/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8082 3 | 4 | spring: 5 | application: 6 | name: user-query 7 | 8 | jackson: 9 | serialization.write-dates-as-timestamps: false 10 | 11 | data: 12 | elasticsearch: 13 | cluster-name: es-cluster 14 | cluster-nodes: elasticsearch:9300 15 | 16 | management: 17 | endpoints: 18 | web: 19 | exposure: 20 | include: health, metrics, info, prometheus 21 | -------------------------------------------------------------------------------- /connect/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM confluentinc/cp-kafka-connect:latest 2 | 3 | MAINTAINER Joumen Ali HARZLI 4 | 5 | WORKDIR / 6 | 7 | RUN wget https://repo1.maven.org/maven2/io/debezium/debezium-connector-mysql/0.3.0/debezium-connector-mysql-0.3.0-plugin.tar.gz -O /tmp/debezium-connector-mysql.tar.gz 8 | RUN mkdir -p /usr/share/java/debezium-connector-mysql 9 | RUN tar -xvzf /tmp/debezium-connector-mysql.tar.gz --directory /usr/share/java/debezium-connector-mysql 10 | RUN rm /tmp/debezium-connector-mysql.tar.gz 11 | -------------------------------------------------------------------------------- /denormalizer/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8084 3 | 4 | spring: 5 | application: 6 | name: user-denormalizer 7 | 8 | jackson: 9 | serialization.write-dates-as-timestamps: false 10 | 11 | data: 12 | elasticsearch: 13 | cluster-name: es-cluster 14 | cluster-nodes: elasticsearch:9300 15 | 16 | management: 17 | endpoints: 18 | web: 19 | exposure: 20 | include: health, metrics, info, prometheus 21 | 22 | application: 23 | kafka: 24 | bootstrapServers: kafka:29092 25 | groupId: user-denormalizer 26 | -------------------------------------------------------------------------------- /clear_query.sh: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 Joumen Harzli 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | 15 | #!/usr/bin/env bash 16 | curl -X DELETE http://localhost:9200/users 17 | -------------------------------------------------------------------------------- /query/src/main/resources/i18n/messages_en.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 Joumen Harzli 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | # 15 | error.internal=Something unexpected went wrong 16 | error.validation=Request content is invalid 17 | error.notFound=The entity was not found 18 | -------------------------------------------------------------------------------- /command/src/main/resources/i18n/messages_en.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 Joumen Harzli 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | # 15 | error.internal=Something unexpected went wrong 16 | error.validation=Request content is invalid 17 | error.notFound=The entity was not found 18 | -------------------------------------------------------------------------------- /.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 | 25 | target/ 26 | !command/.mvn/wrapper/maven-wrapper.jar 27 | !query/.mvn/wrapper/maven-wrapper.jar 28 | !denormalizer/.mvn/wrapper/maven-wrapper.jar 29 | 30 | ### STS ### 31 | .apt_generated 32 | .classpath 33 | .factorypath 34 | .project 35 | .settings 36 | .springBeans 37 | 38 | ### IntelliJ IDEA ### 39 | .idea 40 | *.iws 41 | *.iml 42 | *.ipr 43 | 44 | ### NetBeans ### 45 | nbproject/private/ 46 | build/ 47 | nbbuild/ 48 | dist/ 49 | nbdist/ 50 | .nb-gradle/ 51 | -------------------------------------------------------------------------------- /test_query.sh: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 Joumen Harzli 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | 15 | #!/usr/bin/env bash 16 | echo "Entities in elasticsearch" 17 | curl http://localhost:9200/users/users/_search?pretty 18 | 19 | echo "Entities retrived by user query" 20 | curl "http://localhost:8082/api/v1/users/search?page=0&size=10" | json_pp 21 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/service/dto/AddressDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.service.dto; 17 | 18 | import lombok.Data; 19 | 20 | /** 21 | * AddressDto 22 | * 23 | * @author Joumen Harzli 24 | */ 25 | @Data 26 | public class AddressDto { 27 | 28 | private String id; 29 | 30 | private String name; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/service/dto/AddressDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.service.dto; 17 | 18 | import lombok.Data; 19 | 20 | /** 21 | * AddressDto 22 | * 23 | * @author Joumen Harzli 24 | */ 25 | @Data 26 | public class AddressDto { 27 | 28 | private String id; 29 | 30 | private String name; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /command/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 Joumen Harzli 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | 15 | FROM openjdk:8u171-jre 16 | 17 | MAINTAINER Joumen HARZLI 18 | 19 | ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS 20 | 21 | ADD target/user-command.jar ./app.jar 22 | 23 | EXPOSE 8081 24 | 25 | CMD java ${JAVA_ARGS} -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap \ 26 | -XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom \ 27 | -jar /app.jar -------------------------------------------------------------------------------- /query/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 Joumen Harzli 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | 15 | FROM openjdk:8u171-jre 16 | 17 | MAINTAINER Joumen HARZLI 18 | 19 | ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS 20 | 21 | ADD target/user-query.jar ./app.jar 22 | 23 | EXPOSE 8082 24 | 25 | CMD java ${JAVA_ARGS} -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap \ 26 | -XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom \ 27 | -jar /app.jar -------------------------------------------------------------------------------- /denormalizer/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 Joumen Harzli 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | 15 | FROM openjdk:8u171-jre 16 | 17 | MAINTAINER Joumen HARZLI 18 | 19 | ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS 20 | 21 | ADD target/user-denormalizer.jar ./app.jar 22 | 23 | EXPOSE 8084 24 | 25 | CMD java ${JAVA_ARGS} -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap \ 26 | -XX:+UseG1GC -Djava.security.egd=file:/dev/./urandom \ 27 | -jar /app.jar -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/listener/EventHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.listener; 17 | 18 | import com.github.joumenharzli.cdc.denormalizer.listener.support.DebeziumEvent; 19 | 20 | /** 21 | * Event Handler 22 | * 23 | * @author Joumen Harzli 24 | */ 25 | public interface EventHandler { 26 | 27 | void process(DebeziumEvent event); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /command/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | 4 | spring: 5 | application: 6 | name: user-command 7 | 8 | jackson: 9 | serialization.write-dates-as-timestamps: false 10 | 11 | datasource: 12 | type: com.zaxxer.hikari.HikariDataSource 13 | url: jdbc:mysql://mysql:3306/cdc 14 | username: cdc_user 15 | password: cdc_pass 16 | hikari: 17 | data-source-properties: 18 | cachePrepStmts: true 19 | prepStmtCacheSize: 250 20 | prepStmtCacheSqlLimit: 2048 21 | useServerPrepStmts: true 22 | 23 | jpa: 24 | database-platform: org.hibernate.dialect.MySQL5InnoDBDialect 25 | open-in-view: false 26 | show-sql: true 27 | properties: 28 | hibernate.id.new_generator_mappings: true 29 | hibernate: 30 | ddl-auto: create 31 | naming: 32 | physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl 33 | 34 | management: 35 | endpoints: 36 | web: 37 | exposure: 38 | include: health, metrics, info, prometheus 39 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/service/dto/QueryParameter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.service.dto; 17 | 18 | import lombok.Builder; 19 | import lombok.Data; 20 | 21 | /** 22 | * Parameter of a query 23 | * 24 | * @author Joumen Harzli 25 | */ 26 | @Data 27 | @Builder 28 | public class QueryParameter { 29 | 30 | private String field; 31 | private String value; 32 | private QueryOperator operator; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/domain/Address.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.domain; 17 | 18 | import lombok.Data; 19 | import lombok.EqualsAndHashCode; 20 | 21 | import java.io.Serializable; 22 | 23 | /** 24 | * AddressDto Type 25 | * 26 | * @author Joumen Harzli 27 | */ 28 | @Data 29 | @EqualsAndHashCode(of = {"id"}) 30 | public class Address implements Serializable { 31 | 32 | private String id; 33 | 34 | private String name; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /test_command.sh: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 Joumen Harzli 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | 15 | #!/usr/bin/env bash 16 | while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8081/actuator/health)" != "200" ]]; do sleep 5; done 17 | 18 | curl -X POST -H "Content-Type: application/json" localhost:8081/api/v1/users -d @- << EOF 19 | 20 | { 21 | "name": "Joumen", 22 | "jobs":[{"name":"Software Architect","startDate":"2010-06-03T09:14:56.284Z","endDate":"2018-06-03T09:14:56.284Z","description":"just a test"}], 23 | "addresses":[{"name":"tunisia"}] 24 | } 25 | 26 | EOF 27 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/DenormalizerApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | 21 | @SpringBootApplication 22 | public class DenormalizerApplication { 23 | 24 | public static void main(String[] args) { 25 | SpringApplication.run(DenormalizerApplication.class, args); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/service/dto/QueryOperator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.service.dto; 17 | 18 | import lombok.Getter; 19 | import lombok.RequiredArgsConstructor; 20 | 21 | /** 22 | * Query Operator 23 | * 24 | * @author Joumen Harzli 25 | */ 26 | @RequiredArgsConstructor 27 | public enum QueryOperator { 28 | EQUALS("="), DIFFERENT("<>"), GREATER(">"), LESS("<"); 29 | 30 | @Getter 31 | private final String operation; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/service/dto/JobDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.service.dto; 17 | 18 | import java.time.Instant; 19 | import lombok.Data; 20 | 21 | /** 22 | * JobDto 23 | * 24 | * @author Joumen Harzli 25 | */ 26 | @Data 27 | public class JobDto { 28 | 29 | private String id; 30 | 31 | private String name; 32 | 33 | private String description; 34 | 35 | private Instant startDate; 36 | 37 | private Instant endDate; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/exception/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.exception; 17 | 18 | /** 19 | * Exception thrown when no entity was found 20 | * 21 | * @author Joumen Harzli 22 | */ 23 | public class EntityNotFoundException extends RuntimeException { 24 | 25 | private final String message; 26 | 27 | public EntityNotFoundException(String message) { 28 | super(message); 29 | this.message = message; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/domain/Address.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.domain; 17 | 18 | import java.io.Serializable; 19 | import lombok.Data; 20 | import lombok.EqualsAndHashCode; 21 | 22 | /** 23 | * AddressDto Type 24 | * 25 | * @author Joumen Harzli 26 | */ 27 | @Data 28 | @EqualsAndHashCode(of = {"id"}) 29 | public class Address implements Serializable { 30 | 31 | private String id; 32 | 33 | private String name; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/service/dto/JobDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.service.dto; 17 | 18 | import java.time.Instant; 19 | import lombok.Data; 20 | 21 | /** 22 | * JobDto 23 | * 24 | * @author Joumen Harzli 25 | */ 26 | @Data 27 | public class JobDto { 28 | 29 | private String id; 30 | 31 | private String name; 32 | 33 | private String description; 34 | 35 | private Instant startDate; 36 | 37 | private Instant endDate; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/exception/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.exception; 17 | 18 | /** 19 | * Exception thrown when no entity was found 20 | * 21 | * @author Joumen Harzli 22 | */ 23 | public class EntityNotFoundException extends RuntimeException { 24 | 25 | private final String message; 26 | 27 | public EntityNotFoundException(String message) { 28 | super(message); 29 | this.message = message; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/QueryApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 21 | 22 | @SpringBootApplication 23 | @EnableAspectJAutoProxy 24 | public class QueryApplication { 25 | 26 | public static void main(String[] args) { 27 | SpringApplication.run(QueryApplication.class, args); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/web/error/RestFieldErrorDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.web.error; 17 | 18 | import java.io.Serializable; 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | 22 | /** 23 | * A representation for the rest field error 24 | * 25 | * @author Joumen Harzli 26 | */ 27 | @Data 28 | @AllArgsConstructor 29 | public class RestFieldErrorDto implements Serializable { 30 | 31 | private String field; 32 | private String code; 33 | private String message; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/web/error/RestFieldErrorDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.web.error; 17 | 18 | import java.io.Serializable; 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | 22 | /** 23 | * A representation for the rest field error 24 | * 25 | * @author Joumen Harzli 26 | */ 27 | @Data 28 | @AllArgsConstructor 29 | public class RestFieldErrorDto implements Serializable { 30 | 31 | private String field; 32 | private String code; 33 | private String message; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.repository; 17 | 18 | import java.util.UUID; 19 | 20 | import org.springframework.data.jpa.repository.JpaRepository; 21 | import org.springframework.stereotype.Repository; 22 | 23 | import com.github.joumenharzli.cdc.command.domain.User; 24 | 25 | /** 26 | * JPA Repository for the entity {@link User} 27 | * 28 | * @author Joumen Harzli 29 | */ 30 | @Repository 31 | public interface UserRepository extends JpaRepository { 32 | } 33 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.repository; 17 | 18 | import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; 19 | import org.springframework.stereotype.Repository; 20 | 21 | import com.github.joumenharzli.cdc.query.domain.User; 22 | 23 | /** 24 | * Elastic search repository for the index {@link User} 25 | * 26 | * @author Joumen Harzli 27 | */ 28 | @Repository 29 | public interface UserRepository extends ElasticsearchRepository { 30 | } 31 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/web/error/RestErrorDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.web.error; 17 | 18 | import java.io.Serializable; 19 | import io.swagger.annotations.ApiModel; 20 | import lombok.AllArgsConstructor; 21 | import lombok.Data; 22 | 23 | /** 24 | * A representation for the rest error 25 | * 26 | * @author Joumen Harzli 27 | */ 28 | @ApiModel("RestErrorDto") 29 | @Data 30 | @AllArgsConstructor 31 | public class RestErrorDto implements Serializable { 32 | 33 | private String code; 34 | private String message; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/web/error/RestErrorDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.web.error; 17 | 18 | import java.io.Serializable; 19 | import io.swagger.annotations.ApiModel; 20 | import lombok.AllArgsConstructor; 21 | import lombok.Data; 22 | 23 | /** 24 | * A representation for the rest error 25 | * 26 | * @author Joumen Harzli 27 | */ 28 | @ApiModel("RestErrorDto") 29 | @Data 30 | @AllArgsConstructor 31 | public class RestErrorDto implements Serializable { 32 | 33 | private String code; 34 | private String message; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.repository; 17 | 18 | import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; 19 | import org.springframework.stereotype.Repository; 20 | 21 | import com.github.joumenharzli.cdc.denormalizer.domain.User; 22 | 23 | /** 24 | * Elastic search repository for the index {@link User} 25 | * 26 | * @author Joumen Harzli 27 | */ 28 | @Repository 29 | public interface UserRepository extends ElasticsearchRepository { 30 | } 31 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/service/dto/UserDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.service.dto; 17 | 18 | import java.util.List; 19 | 20 | import com.google.common.collect.Lists; 21 | 22 | import lombok.Data; 23 | 24 | /** 25 | * UserDto 26 | * 27 | * @author Joumen Harzli 28 | */ 29 | @Data 30 | public class UserDto { 31 | 32 | private String id; 33 | 34 | private String name; 35 | 36 | private Integer age; 37 | 38 | private List addresses = Lists.newArrayList(); 39 | 40 | private List jobs = Lists.newArrayList(); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/service/dto/UserDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.service.dto; 17 | 18 | import java.util.List; 19 | 20 | import com.google.common.collect.Lists; 21 | 22 | import lombok.Data; 23 | 24 | /** 25 | * UserDto 26 | * 27 | * @author Joumen Harzli 28 | */ 29 | @Data 30 | public class UserDto { 31 | 32 | private String id; 33 | 34 | private String name; 35 | 36 | private Integer age; 37 | 38 | private List addresses = Lists.newArrayList(); 39 | 40 | private List jobs = Lists.newArrayList(); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/web/error/RestErrorConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.web.error; 17 | 18 | import lombok.AccessLevel; 19 | import lombok.NoArgsConstructor; 20 | 21 | /** 22 | * Constants for rest error 23 | * 24 | * @author Joumen Harzli 25 | */ 26 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 27 | public final class RestErrorConstants { 28 | 29 | public static final String ERR_INTERNAL_SERVER_ERROR = "error.internal"; 30 | public static final String ERR_VALIDATION_ERROR = "error.validation"; 31 | public static final String ERR_NOT_FOUND_ERROR = "error.notFound"; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/web/error/RestErrorConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.web.error; 17 | 18 | import lombok.AccessLevel; 19 | import lombok.NoArgsConstructor; 20 | 21 | /** 22 | * Constants for rest error 23 | * 24 | * @author Joumen Harzli 25 | */ 26 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 27 | public final class RestErrorConstants { 28 | 29 | public static final String ERR_INTERNAL_SERVER_ERROR = "error.internal"; 30 | public static final String ERR_VALIDATION_ERROR = "error.validation"; 31 | public static final String ERR_NOT_FOUND_ERROR = "error.notFound"; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/service/mapper/JobMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.service.mapper; 17 | 18 | import org.mapstruct.Mapper; 19 | import org.mapstruct.NullValueMappingStrategy; 20 | 21 | import com.github.joumenharzli.cdc.denormalizer.domain.Job; 22 | import com.github.joumenharzli.cdc.denormalizer.service.dto.JobDto; 23 | 24 | 25 | /** 26 | * JobMapper 27 | * 28 | * @author Joumen Harzli 29 | */ 30 | @Mapper(componentModel = "spring", 31 | nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) 32 | public interface JobMapper { 33 | 34 | Job toEntity(JobDto jobDto); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/service/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.service.mapper; 17 | 18 | import org.mapstruct.Mapper; 19 | import org.mapstruct.NullValueMappingStrategy; 20 | 21 | import com.github.joumenharzli.cdc.denormalizer.domain.User; 22 | import com.github.joumenharzli.cdc.denormalizer.service.dto.UserDto; 23 | 24 | 25 | /** 26 | * UserMapper 27 | * 28 | * @author Joumen Harzli 29 | */ 30 | @Mapper(componentModel = "spring", 31 | nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) 32 | public interface UserMapper { 33 | 34 | User toEntity(UserDto user); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/service/dto/UserDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.service.dto; 17 | 18 | import com.fasterxml.jackson.annotation.JsonProperty; 19 | import lombok.Data; 20 | import lombok.EqualsAndHashCode; 21 | 22 | import java.io.Serializable; 23 | 24 | /** 25 | * User Dto 26 | * 27 | * @author Joumen Harzli 28 | */ 29 | @Data 30 | @EqualsAndHashCode(of = {"id"}) 31 | public class UserDto implements Serializable { 32 | 33 | @JsonProperty("ID") 34 | private String id; 35 | 36 | @JsonProperty("NAME") 37 | private String name; 38 | 39 | @JsonProperty("AGE") 40 | private Integer age; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/service/dto/AddressDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.service.dto; 17 | 18 | import com.fasterxml.jackson.annotation.JsonProperty; 19 | import lombok.Data; 20 | import lombok.EqualsAndHashCode; 21 | 22 | import java.io.Serializable; 23 | 24 | /** 25 | * Address Dto 26 | * 27 | * @author Joumen Harzli 28 | */ 29 | @Data 30 | @EqualsAndHashCode(of = {"id"}) 31 | public class AddressDto implements Serializable { 32 | 33 | @JsonProperty("ID") 34 | private String id; 35 | 36 | @JsonProperty("NAME") 37 | private String name; 38 | 39 | @JsonProperty("USER_ID") 40 | private String userId; 41 | 42 | } 43 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/service/mapper/JobMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.service.mapper; 17 | 18 | 19 | import com.github.joumenharzli.cdc.query.domain.Job; 20 | import com.github.joumenharzli.cdc.query.service.dto.JobDto; 21 | import org.mapstruct.Mapper; 22 | import org.mapstruct.NullValueMappingStrategy; 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * JobMapper 28 | * 29 | * @author Joumen Harzli 30 | */ 31 | @Mapper(componentModel = "spring", nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) 32 | public interface JobMapper { 33 | 34 | JobDto toDto(Job job); 35 | 36 | List toDtos(List job); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/service/mapper/AddressMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.service.mapper; 17 | 18 | import org.mapstruct.Mapper; 19 | import org.mapstruct.NullValueMappingStrategy; 20 | 21 | import com.github.joumenharzli.cdc.denormalizer.domain.Address; 22 | import com.github.joumenharzli.cdc.denormalizer.service.dto.AddressDto; 23 | 24 | 25 | /** 26 | * AddressMapper 27 | * 28 | * @author Joumen Harzli 29 | */ 30 | @Mapper(componentModel = "spring", 31 | nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) 32 | public interface AddressMapper { 33 | 34 | Address toEntity(AddressDto addressDto); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/service/mapper/AddressMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.service.mapper; 17 | 18 | import com.github.joumenharzli.cdc.query.domain.Address; 19 | import com.github.joumenharzli.cdc.query.service.dto.AddressDto; 20 | import org.mapstruct.Mapper; 21 | import org.mapstruct.NullValueMappingStrategy; 22 | 23 | import java.util.List; 24 | 25 | 26 | /** 27 | * AddressMapper 28 | * 29 | * @author Joumen Harzli 30 | */ 31 | @Mapper(componentModel = "spring", nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) 32 | public interface AddressMapper { 33 | 34 | AddressDto toDto(Address address); 35 | 36 | List toDtos(List
address); 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/service/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.service.mapper; 17 | 18 | import org.mapstruct.Mapper; 19 | import org.mapstruct.Mapping; 20 | import org.mapstruct.NullValueMappingStrategy; 21 | 22 | import com.github.joumenharzli.cdc.command.domain.User; 23 | import com.github.joumenharzli.cdc.command.service.dto.UserDto; 24 | 25 | /** 26 | * UserMapper 27 | * 28 | * @author Joumen Harzli 29 | */ 30 | @Mapper(componentModel = "spring", uses = {AddressMapper.class, JobMapper.class}, 31 | nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) 32 | public interface UserMapper { 33 | 34 | User toEntity(UserDto user); 35 | 36 | UserDto toDto(User user); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/service/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.service.mapper; 17 | 18 | import com.github.joumenharzli.cdc.query.domain.User; 19 | import com.github.joumenharzli.cdc.query.service.dto.UserDto; 20 | import org.mapstruct.Mapper; 21 | import org.mapstruct.NullValueMappingStrategy; 22 | 23 | import java.util.List; 24 | 25 | 26 | /** 27 | * UserMapper 28 | * 29 | * @author Joumen Harzli 30 | */ 31 | @Mapper(componentModel = "spring", uses = {AddressMapper.class, JobMapper.class}, 32 | nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) 33 | public interface UserMapper { 34 | 35 | UserDto toDto(User user); 36 | 37 | List toDtos(List user); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/CommandApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 21 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 22 | import org.springframework.transaction.annotation.EnableTransactionManagement; 23 | 24 | @SpringBootApplication 25 | @EnableJpaRepositories 26 | @EnableTransactionManagement 27 | @EnableAspectJAutoProxy 28 | public class CommandApplication { 29 | 30 | public static void main(String[] args) { 31 | SpringApplication.run(CommandApplication.class, args); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /activate_connector.sh: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 Joumen Harzli 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | 15 | #!/usr/bin/env bash 16 | 17 | while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8083)" != "200" ]]; do sleep 5; done 18 | 19 | curl -X POST -H "Content-Type: application/json" localhost:8083/connectors -d @- << EOF 20 | 21 | { 22 | "name": "debezium-connector-mysql", 23 | "config": { 24 | "connector.class": "io.debezium.connector.mysql.MySqlConnector", 25 | "database.hostname": "mysql", 26 | "database.port": "3306", 27 | "database.user": "debezium", 28 | "database.password": "dbz", 29 | "database.server.id": "184054", 30 | "database.server.name": "mysqlcdc", 31 | "database.history.kafka.bootstrap.servers": "kafka:29092", 32 | "database.history.kafka.topic": "dbhistory.mysqlcdc", 33 | "include.schema.changes": "true" 34 | } 35 | } 36 | 37 | EOF 38 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/service/UserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.service; 17 | 18 | import com.github.joumenharzli.cdc.denormalizer.service.dto.AddressDto; 19 | import com.github.joumenharzli.cdc.denormalizer.service.dto.JobDto; 20 | import com.github.joumenharzli.cdc.denormalizer.service.dto.UserDto; 21 | 22 | import reactor.core.publisher.Mono; 23 | 24 | /** 25 | * UserService 26 | * 27 | * @author Joumen Harzli 28 | */ 29 | public interface UserService { 30 | void save(UserDto userDto); 31 | 32 | void saveUserAddress(AddressDto addressDto); 33 | 34 | void saveUserJob(JobDto jobDto); 35 | 36 | void delete(UserDto userDto); 37 | 38 | void deleteUserJob(JobDto jobDto); 39 | 40 | void deleteUserAddress(AddressDto addressDto); 41 | } 42 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/service/mapper/JobMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.service.mapper; 17 | 18 | import java.util.List; 19 | 20 | import org.mapstruct.Mapper; 21 | import org.mapstruct.Mapping; 22 | import org.mapstruct.NullValueMappingStrategy; 23 | 24 | import com.github.joumenharzli.cdc.command.domain.Job; 25 | import com.github.joumenharzli.cdc.command.service.dto.JobDto; 26 | 27 | /** 28 | * JobMapper 29 | * 30 | * @author Joumen Harzli 31 | */ 32 | @Mapper(componentModel = "spring", nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) 33 | public interface JobMapper { 34 | 35 | Job toEntity(JobDto job); 36 | 37 | List toEntities(List jobs); 38 | 39 | JobDto toDto(Job job); 40 | 41 | List toDtos(List job); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/service/mapper/AddressMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.service.mapper; 17 | 18 | import java.util.List; 19 | 20 | import org.mapstruct.Mapper; 21 | import org.mapstruct.Mapping; 22 | import org.mapstruct.NullValueMappingStrategy; 23 | 24 | import com.github.joumenharzli.cdc.command.domain.Address; 25 | import com.github.joumenharzli.cdc.command.service.dto.AddressDto; 26 | 27 | /** 28 | * AddressMapper 29 | * 30 | * @author Joumen Harzli 31 | */ 32 | @Mapper(componentModel = "spring", nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) 33 | public interface AddressMapper { 34 | 35 | Address toEntity(AddressDto address); 36 | 37 | List
toEntities(List
addresss); 38 | 39 | AddressDto toDto(Address address); 40 | 41 | List toDtos(List
address); 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/service/dto/JobDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.service.dto; 17 | 18 | import com.fasterxml.jackson.annotation.JsonProperty; 19 | import lombok.Data; 20 | import lombok.EqualsAndHashCode; 21 | 22 | import java.io.Serializable; 23 | import java.time.Instant; 24 | import java.util.Date; 25 | 26 | /** 27 | * Job Dto 28 | * 29 | * @author Joumen Harzli 30 | */ 31 | @Data 32 | @EqualsAndHashCode(of = {"id"}) 33 | public class JobDto implements Serializable { 34 | 35 | @JsonProperty("ID") 36 | private String id; 37 | 38 | @JsonProperty("NAME") 39 | private String name; 40 | 41 | @JsonProperty("DESCRIPTION") 42 | private String description; 43 | 44 | @JsonProperty("START_DATE") 45 | private Date startDate; 46 | 47 | @JsonProperty("END_DATE") 48 | private Date endDate; 49 | 50 | @JsonProperty("USER_ID") 51 | private String userId; 52 | } 53 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/domain/Address.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.domain; 17 | 18 | import java.io.Serializable; 19 | import java.util.UUID; 20 | import javax.persistence.*; 21 | 22 | import org.hibernate.annotations.GenericGenerator; 23 | 24 | import lombok.Data; 25 | import lombok.EqualsAndHashCode; 26 | 27 | /** 28 | * Address Entity 29 | * 30 | * @author Joumen Harzli 31 | */ 32 | @Entity 33 | @Table(name = "ADDRESSES") 34 | @Data 35 | @EqualsAndHashCode(of = {"id"}) 36 | public class Address implements Serializable { 37 | 38 | @Id 39 | @GeneratedValue(generator = "uuid2") 40 | @GenericGenerator(name = "uuid2", strategy = "uuid2") 41 | @Column(name = "ID", columnDefinition = "VARCHAR(36)") 42 | private String id; 43 | 44 | @Column(name = "NAME") 45 | private String name; 46 | 47 | @Column(name = "USER_ID", columnDefinition = "VARCHAR(36)", length = 36) 48 | private String userId; 49 | } 50 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/domain/Job.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.domain; 17 | 18 | import lombok.Data; 19 | import lombok.EqualsAndHashCode; 20 | import org.springframework.data.elasticsearch.annotations.DateFormat; 21 | import org.springframework.data.elasticsearch.annotations.Field; 22 | 23 | import java.io.Serializable; 24 | import java.time.Instant; 25 | 26 | import static org.springframework.data.elasticsearch.annotations.FieldType.Date; 27 | 28 | /** 29 | * Job Type 30 | * 31 | * @author Joumen Harzli 32 | */ 33 | @Data 34 | @EqualsAndHashCode(of = {"id"}) 35 | public class Job implements Serializable { 36 | 37 | private String id; 38 | 39 | private String name; 40 | 41 | private String description; 42 | 43 | @Field(type = Date, format = DateFormat.date_time) 44 | private Instant startDate; 45 | 46 | 47 | @Field(type = Date, format = DateFormat.date_time) 48 | private Instant endDate; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/domain/Job.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.domain; 17 | 18 | import lombok.Data; 19 | import lombok.EqualsAndHashCode; 20 | import org.springframework.data.elasticsearch.annotations.DateFormat; 21 | import org.springframework.data.elasticsearch.annotations.Field; 22 | import org.springframework.data.elasticsearch.annotations.FieldType; 23 | 24 | import java.io.Serializable; 25 | import java.util.Date; 26 | 27 | /** 28 | * Job Type 29 | * 30 | * @author Joumen Harzli 31 | */ 32 | @Data 33 | @EqualsAndHashCode(of = {"id"}) 34 | public class Job implements Serializable { 35 | 36 | private String id; 37 | 38 | private String name; 39 | 40 | private String description; 41 | 42 | @Field(type = FieldType.Date, format = DateFormat.date_time) 43 | private Date startDate; 44 | 45 | 46 | @Field(type = FieldType.Date, format = DateFormat.date_time) 47 | private Date endDate; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/config/LocalizationConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.config; 17 | 18 | import org.springframework.context.annotation.Bean; 19 | import org.springframework.context.annotation.Configuration; 20 | import org.springframework.context.support.ResourceBundleMessageSource; 21 | 22 | /** 23 | * Localization Configuration 24 | * 25 | * @author Joumen Harzli 26 | */ 27 | @Configuration 28 | public class LocalizationConfiguration { 29 | 30 | /** 31 | * Set the source of i18n messages under "resources/i18n/messages_[locale].properties" 32 | * 33 | * @return instance of {@link ResourceBundleMessageSource} 34 | */ 35 | @Bean 36 | public ResourceBundleMessageSource messageSource() { 37 | ResourceBundleMessageSource source = new ResourceBundleMessageSource(); 38 | source.setBasenames("i18n/messages"); 39 | source.setUseCodeAsDefaultMessage(true); 40 | return source; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/config/LocalizationConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.config; 17 | 18 | import org.springframework.context.annotation.Bean; 19 | import org.springframework.context.annotation.Configuration; 20 | import org.springframework.context.support.ResourceBundleMessageSource; 21 | 22 | /** 23 | * Localization Configuration 24 | * 25 | * @author Joumen Harzli 26 | */ 27 | @Configuration 28 | public class LocalizationConfiguration { 29 | 30 | /** 31 | * Set the source of i18n messages under "resources/i18n/messages_[locale].properties" 32 | * 33 | * @return instance of {@link ResourceBundleMessageSource} 34 | */ 35 | @Bean 36 | public ResourceBundleMessageSource messageSource() { 37 | ResourceBundleMessageSource source = new ResourceBundleMessageSource(); 38 | source.setBasenames("i18n/messages"); 39 | source.setUseCodeAsDefaultMessage(true); 40 | return source; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/service/UserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.service; 17 | 18 | import java.util.List; 19 | 20 | import org.springframework.data.domain.Page; 21 | import org.springframework.data.domain.Pageable; 22 | 23 | import com.github.joumenharzli.cdc.query.domain.User; 24 | import com.github.joumenharzli.cdc.query.service.dto.QueryParameter; 25 | import com.github.joumenharzli.cdc.query.service.dto.UserDto; 26 | 27 | import reactor.core.publisher.Mono; 28 | 29 | /** 30 | * Search Service for the index {@link User} 31 | * 32 | * @author Joumen Harzli 33 | */ 34 | public interface UserService { 35 | 36 | /** 37 | * Find users using the provided parameters 38 | * 39 | * @param parameters filters to use 40 | * @param pageable page number, size and sorting 41 | * @return the found users in a page 42 | * @throws IllegalArgumentException if any given argument is invalid 43 | */ 44 | Mono> findByCriteria(List parameters, Pageable pageable); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/domain/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.domain; 17 | 18 | import com.google.common.collect.Sets; 19 | import lombok.Data; 20 | import lombok.EqualsAndHashCode; 21 | import org.springframework.data.annotation.Id; 22 | import org.springframework.data.elasticsearch.annotations.Document; 23 | import org.springframework.data.elasticsearch.annotations.Field; 24 | import org.springframework.data.elasticsearch.annotations.FieldType; 25 | 26 | import java.io.Serializable; 27 | import java.util.Set; 28 | 29 | /** 30 | * User Index 31 | * 32 | * @author Joumen Harzli 33 | */ 34 | @Document(indexName = "users", type = "users") 35 | @Data 36 | @EqualsAndHashCode(of = {"id"}) 37 | public class User implements Serializable { 38 | 39 | @Id 40 | private String id; 41 | 42 | private String name; 43 | 44 | private Integer age; 45 | 46 | @Field(type = FieldType.Nested) 47 | private Set
addresses = Sets.newHashSet(); 48 | 49 | @Field(type = FieldType.Nested) 50 | private Set jobs = Sets.newHashSet(); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/domain/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.domain; 17 | 18 | import java.io.Serializable; 19 | import java.util.Set; 20 | 21 | import org.springframework.data.annotation.Id; 22 | import org.springframework.data.elasticsearch.annotations.Document; 23 | import org.springframework.data.elasticsearch.annotations.Field; 24 | import org.springframework.data.elasticsearch.annotations.FieldType; 25 | 26 | import com.google.common.collect.Sets; 27 | 28 | import lombok.Data; 29 | import lombok.EqualsAndHashCode; 30 | 31 | /** 32 | * User Index 33 | * 34 | * @author Joumen Harzli 35 | */ 36 | @Document(indexName = "users", type = "users") 37 | @Data 38 | @EqualsAndHashCode(of = {"id"}) 39 | public class User implements Serializable { 40 | 41 | @Id 42 | private String id; 43 | 44 | private String name; 45 | 46 | private Integer age; 47 | 48 | @Field(type = FieldType.Nested) 49 | private Set
addresses = Sets.newHashSet(); 50 | 51 | @Field(type = FieldType.Nested) 52 | private Set jobs = Sets.newHashSet(); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/domain/Job.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.domain; 17 | 18 | import java.io.Serializable; 19 | import java.time.Instant; 20 | import java.util.UUID; 21 | import javax.persistence.*; 22 | 23 | import org.hibernate.annotations.GenericGenerator; 24 | 25 | import lombok.Data; 26 | import lombok.EqualsAndHashCode; 27 | 28 | /** 29 | * Job Entity 30 | * 31 | * @author Joumen Harzli 32 | */ 33 | @Entity 34 | @Table(name = "JOBS") 35 | @Data 36 | @EqualsAndHashCode(of = {"id"}) 37 | public class Job implements Serializable { 38 | 39 | @Id 40 | @GeneratedValue(generator = "uuid2") 41 | @GenericGenerator(name = "uuid2", strategy = "uuid2") 42 | @Column(name = "ID", columnDefinition = "VARCHAR(36)") 43 | private String id; 44 | 45 | @Column(name = "NAME") 46 | private String name; 47 | 48 | @Column(name = "DESCRIPTION") 49 | private String description; 50 | 51 | @Column(name = "START_DATE") 52 | private Instant startDate; 53 | 54 | @Column(name = "END_DATE") 55 | private Instant endDate; 56 | 57 | @Column(name = "USER_ID", columnDefinition = "VARCHAR(36)", length = 36) 58 | private String userId; 59 | } 60 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/listener/support/DebeziumEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.listener.support; 17 | 18 | import com.fasterxml.jackson.annotation.JsonProperty; 19 | import com.fasterxml.jackson.annotation.JsonValue; 20 | import lombok.Data; 21 | import lombok.RequiredArgsConstructor; 22 | 23 | import java.util.Date; 24 | import java.util.Map; 25 | 26 | /** 27 | * Debezium Event 28 | * 29 | * @author Joumen Harzli 30 | */ 31 | @Data 32 | public class DebeziumEvent { 33 | 34 | private Map schema; 35 | private DebeziumEventPayload payload; 36 | 37 | @RequiredArgsConstructor 38 | public enum DebeziumEventPayloadOperation { 39 | CREATE("c"), UPDATE("u"), DELETE("r"); 40 | private final String value; 41 | 42 | @JsonValue 43 | public String getValue() { 44 | return value; 45 | } 46 | } 47 | 48 | @Data 49 | public static class DebeziumEventPayload { 50 | private Map before; 51 | private Map after; 52 | private Map source; 53 | 54 | @JsonProperty("op") 55 | private DebeziumEventPayloadOperation operation; 56 | 57 | @JsonProperty("ts_ms") 58 | private Date date; 59 | 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/domain/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.domain; 17 | 18 | import java.io.Serializable; 19 | import java.util.List; 20 | import java.util.UUID; 21 | import javax.persistence.*; 22 | 23 | import org.hibernate.annotations.GenericGenerator; 24 | 25 | import com.google.common.collect.Lists; 26 | 27 | import lombok.Data; 28 | import lombok.EqualsAndHashCode; 29 | 30 | /** 31 | * User Entity 32 | * 33 | * @author Joumen Harzli 34 | */ 35 | @Entity 36 | @Table(name = "USERS") 37 | @Data 38 | @EqualsAndHashCode(of = {"id"}) 39 | public class User implements Serializable { 40 | 41 | @Id 42 | @GeneratedValue(generator = "uuid2") 43 | @GenericGenerator(name = "uuid2", strategy = "uuid2") 44 | @Column(name = "ID", columnDefinition = "VARCHAR(36)") 45 | private String id; 46 | 47 | @Column(name = "NAME") 48 | private String name; 49 | 50 | @Column(name = "AGE") 51 | private Integer age; 52 | 53 | @OneToMany(cascade = CascadeType.ALL) 54 | @JoinColumn(name = "USER_ID", referencedColumnName = "ID") 55 | private List
addresses = Lists.newArrayList(); 56 | 57 | @OneToMany(cascade = CascadeType.ALL) 58 | @JoinColumn(name = "USER_ID", referencedColumnName = "ID") 59 | private List jobs = Lists.newArrayList(); 60 | 61 | } 62 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | com.github.joumenharzli 8 | cdc 9 | 0.0.1-SNAPSHOT 10 | pom 11 | 12 | cdc 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.2.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | 1.2.0.Final 24 | 25.1-jre 25 | 1.0.4 26 | UTF-8 27 | 1.8 28 | 2.7.0 29 | 5.1.46 30 | 3.7 31 | 0.9.2 32 | 2.7.4 33 | 34 | 35 | 36 | query 37 | command 38 | denormalizer 39 | 40 | 41 | 42 | 43 | ${project.name} 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-maven-plugin 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/listener/handler/AbstractSimpleEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.joumenharzli.cdc.denormalizer.listener.handler; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.github.joumenharzli.cdc.denormalizer.listener.support.DebeziumEvent; 5 | import com.google.common.collect.Maps; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.core.GenericTypeResolver; 9 | 10 | import java.util.Map; 11 | import java.util.Objects; 12 | import java.util.function.BiConsumer; 13 | 14 | /** 15 | * A simple generic event handler 16 | * 17 | * @author Joumen Harzli 18 | */ 19 | @RequiredArgsConstructor 20 | @Slf4j 21 | public abstract class AbstractSimpleEventHandler { 22 | 23 | protected final Map> actions = Maps.newConcurrentMap(); 24 | 25 | private final ObjectMapper mapper; 26 | 27 | public abstract void initActions(); 28 | 29 | @SuppressWarnings("unchecked") 30 | public void process(DebeziumEvent event) { 31 | DebeziumEvent.DebeziumEventPayload payload = event.getPayload(); 32 | 33 | DebeziumEvent.DebeziumEventPayloadOperation operation = payload.getOperation(); 34 | Map payloadBefore = payload.getBefore(); 35 | Map payloadAfter = payload.getAfter(); 36 | 37 | LOGGER.debug("Request to handle {} event with payload that was {} and become {}", operation, payloadBefore, payloadAfter); 38 | 39 | Class entityClass = (Class) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractSimpleEventHandler.class); 40 | 41 | if (Objects.isNull(entityClass)){ 42 | throw new IllegalArgumentException("AbstractSimpleEventHandler should have a type a argument"); 43 | } 44 | 45 | T before = mapper.convertValue(payloadBefore, entityClass); 46 | T after = mapper.convertValue(payloadAfter, entityClass); 47 | 48 | actions.get(operation).accept(before, after); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/web/error/RestFieldsErrorsDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.web.error; 17 | 18 | import java.io.Serializable; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import org.springframework.util.Assert; 23 | 24 | import io.swagger.annotations.ApiModel; 25 | import lombok.EqualsAndHashCode; 26 | import lombok.Getter; 27 | import lombok.ToString; 28 | 29 | /** 30 | * A representation for the rest fields errors list 31 | * 32 | * @author Joumen Harzli 33 | */ 34 | @ApiModel("RestFieldsErrorsDto") 35 | @ToString 36 | @EqualsAndHashCode(callSuper = true) 37 | public class RestFieldsErrorsDto extends RestErrorDto implements Serializable { 38 | 39 | @Getter 40 | private List fieldsErrors; 41 | 42 | /** 43 | * Constructor for the rest fields errors entity 44 | */ 45 | public RestFieldsErrorsDto(String code, String message) { 46 | super(code, message); 47 | fieldsErrors = new ArrayList<>(); 48 | } 49 | 50 | /** 51 | * Add a rest field error to the list of fields errors 52 | * 53 | * @param error error to add to the list 54 | */ 55 | public void addError(RestFieldErrorDto error) { 56 | Assert.notNull(error, "Cannot add a null error to the list of fields errors"); 57 | 58 | if (fieldsErrors == null) { 59 | fieldsErrors = new ArrayList<>(); 60 | } 61 | 62 | fieldsErrors.add(error); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/web/error/RestFieldsErrorsDto.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.web.error; 17 | 18 | import java.io.Serializable; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import org.springframework.util.Assert; 23 | 24 | import io.swagger.annotations.ApiModel; 25 | import lombok.EqualsAndHashCode; 26 | import lombok.Getter; 27 | import lombok.ToString; 28 | 29 | /** 30 | * A representation for the rest fields errors list 31 | * 32 | * @author Joumen Harzli 33 | */ 34 | @ApiModel("RestFieldsErrorsDto") 35 | @ToString 36 | @EqualsAndHashCode(callSuper = true) 37 | public class RestFieldsErrorsDto extends RestErrorDto implements Serializable { 38 | 39 | @Getter 40 | private List fieldsErrors; 41 | 42 | /** 43 | * Constructor for the rest fields errors entity 44 | */ 45 | public RestFieldsErrorsDto(String code, String message) { 46 | super(code, message); 47 | fieldsErrors = new ArrayList<>(); 48 | } 49 | 50 | /** 51 | * Add a rest field error to the list of fields errors 52 | * 53 | * @param error error to add to the list 54 | */ 55 | public void addError(RestFieldErrorDto error) { 56 | Assert.notNull(error, "Cannot add a null error to the list of fields errors"); 57 | 58 | if (fieldsErrors == null) { 59 | fieldsErrors = new ArrayList<>(); 60 | } 61 | 62 | fieldsErrors.add(error); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/service/UserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.service; 17 | 18 | import com.github.joumenharzli.cdc.command.domain.User; 19 | import com.github.joumenharzli.cdc.command.exception.EntityNotFoundException; 20 | import com.github.joumenharzli.cdc.command.service.dto.UserDto; 21 | 22 | import reactor.core.publisher.Mono; 23 | 24 | /** 25 | * Reactive service for the entity {@link User} 26 | * 27 | * @author Joumen Harzli 28 | */ 29 | public interface UserService { 30 | 31 | /** 32 | * Create a user 33 | * 34 | * @param userDto entity to create 35 | * @return the created user 36 | * @throws IllegalArgumentException when any given argument is invalid 37 | */ 38 | Mono create(UserDto userDto); 39 | 40 | /** 41 | * Update a user 42 | * 43 | * @param userDto entity to update 44 | * @return the updated user 45 | * @throws EntityNotFoundException if no entity was found 46 | * @throws IllegalArgumentException when any given argument is invalid 47 | */ 48 | Mono update(UserDto userDto); 49 | 50 | /** 51 | * Delete a user using his id 52 | * 53 | * @param userId id of the user to delete 54 | * @return an empty Mono if success 55 | * @throws EntityNotFoundException if no entity was found 56 | * @throws IllegalArgumentException when any given argument is invalid 57 | */ 58 | Mono deleteById(String userId); 59 | } 60 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/listener/handler/UserEventHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.listener.handler; 17 | 18 | import com.fasterxml.jackson.databind.ObjectMapper; 19 | import com.github.joumenharzli.cdc.denormalizer.listener.EventHandler; 20 | import com.github.joumenharzli.cdc.denormalizer.listener.support.DebeziumEvent.DebeziumEventPayloadOperation; 21 | import com.github.joumenharzli.cdc.denormalizer.service.UserService; 22 | import com.github.joumenharzli.cdc.denormalizer.service.dto.UserDto; 23 | import lombok.extern.slf4j.Slf4j; 24 | import org.springframework.stereotype.Component; 25 | 26 | import javax.annotation.PostConstruct; 27 | 28 | /** 29 | * User Event Handler 30 | * 31 | * @author Joumen Harzli 32 | */ 33 | @Component 34 | @Slf4j 35 | public class UserEventHandler extends AbstractSimpleEventHandler implements EventHandler { 36 | 37 | private final UserService userService; 38 | 39 | public UserEventHandler(ObjectMapper mapper, UserService userService) { 40 | super(mapper); 41 | this.userService = userService; 42 | } 43 | 44 | @PostConstruct 45 | public void init() { 46 | initActions(); 47 | } 48 | 49 | @Override 50 | public void initActions() { 51 | actions.put(DebeziumEventPayloadOperation.CREATE, (before, after) -> userService.save(after)); 52 | actions.put(DebeziumEventPayloadOperation.UPDATE, (before, after) -> userService.save(after)); 53 | actions.put(DebeziumEventPayloadOperation.DELETE, (before, after) -> userService.delete(before)); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/listener/handler/JobEventHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.listener.handler; 17 | 18 | import com.fasterxml.jackson.databind.ObjectMapper; 19 | import com.github.joumenharzli.cdc.denormalizer.listener.EventHandler; 20 | import com.github.joumenharzli.cdc.denormalizer.listener.support.DebeziumEvent.DebeziumEventPayloadOperation; 21 | import com.github.joumenharzli.cdc.denormalizer.service.UserService; 22 | import com.github.joumenharzli.cdc.denormalizer.service.dto.JobDto; 23 | import lombok.extern.slf4j.Slf4j; 24 | import org.springframework.stereotype.Component; 25 | 26 | import javax.annotation.PostConstruct; 27 | 28 | /** 29 | * Job Event Handler 30 | * 31 | * @author Joumen Harzli 32 | */ 33 | @Component 34 | @Slf4j 35 | public class JobEventHandler extends AbstractSimpleEventHandler implements EventHandler { 36 | 37 | private final UserService userService; 38 | 39 | public JobEventHandler(ObjectMapper mapper, UserService userService) { 40 | super(mapper); 41 | this.userService = userService; 42 | } 43 | 44 | @PostConstruct 45 | public void init() { 46 | initActions(); 47 | } 48 | 49 | @Override 50 | public void initActions() { 51 | actions.put(DebeziumEventPayloadOperation.CREATE, (before, after) -> userService.saveUserJob(after)); 52 | actions.put(DebeziumEventPayloadOperation.UPDATE, (before, after) -> userService.saveUserJob(after)); 53 | actions.put(DebeziumEventPayloadOperation.DELETE, (before, after) -> userService.deleteUserJob(before)); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/listener/handler/AddressEventHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.listener.handler; 17 | 18 | import com.fasterxml.jackson.databind.ObjectMapper; 19 | import com.github.joumenharzli.cdc.denormalizer.listener.EventHandler; 20 | import com.github.joumenharzli.cdc.denormalizer.listener.support.DebeziumEvent.DebeziumEventPayloadOperation; 21 | import com.github.joumenharzli.cdc.denormalizer.service.UserService; 22 | import com.github.joumenharzli.cdc.denormalizer.service.dto.AddressDto; 23 | import lombok.extern.slf4j.Slf4j; 24 | import org.springframework.stereotype.Component; 25 | 26 | import javax.annotation.PostConstruct; 27 | 28 | /** 29 | * Address Event Handler 30 | * 31 | * @author Joumen Harzli 32 | */ 33 | @Component 34 | @Slf4j 35 | public class AddressEventHandler extends AbstractSimpleEventHandler implements EventHandler { 36 | 37 | private final UserService userService; 38 | 39 | public AddressEventHandler(ObjectMapper mapper, UserService userService) { 40 | super(mapper); 41 | this.userService = userService; 42 | } 43 | 44 | @PostConstruct 45 | public void init() { 46 | initActions(); 47 | } 48 | 49 | @Override 50 | public void initActions() { 51 | actions.put(DebeziumEventPayloadOperation.CREATE, (before, after) -> userService.saveUserAddress(after)); 52 | actions.put(DebeziumEventPayloadOperation.UPDATE, (before, after) -> userService.saveUserAddress(after)); 53 | actions.put(DebeziumEventPayloadOperation.DELETE, (before, after) -> userService.deleteUserAddress(before)); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/listener/EventDispatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.listener; 17 | 18 | import com.github.joumenharzli.cdc.denormalizer.listener.support.DebeziumEvent; 19 | import io.micrometer.core.annotation.Timed; 20 | import lombok.RequiredArgsConstructor; 21 | import lombok.extern.slf4j.Slf4j; 22 | import org.apache.kafka.clients.consumer.ConsumerRecord; 23 | import org.springframework.kafka.annotation.KafkaListener; 24 | import org.springframework.kafka.support.Acknowledgment; 25 | import org.springframework.stereotype.Component; 26 | 27 | import java.util.Comparator; 28 | import java.util.List; 29 | import java.util.stream.Collectors; 30 | 31 | /** 32 | * Event Dispatcher 33 | * 34 | * @author Joumen Harzli 35 | */ 36 | @Component 37 | @RequiredArgsConstructor 38 | @Slf4j 39 | public class EventDispatcher { 40 | 41 | private final EventHandlerFactory handlerFactory; 42 | 43 | @KafkaListener(topics = { 44 | "mysqlcdc.cdc.USERS", 45 | "mysqlcdc.cdc.JOBS", 46 | "mysqlcdc.cdc.ADDRESSES"}) 47 | @Timed 48 | public void handleEvents(List> records, 49 | Acknowledgment acknowledgment) { 50 | 51 | LOGGER.debug("Request to process {} records", records.size()); 52 | 53 | List> sortedRecords = records.stream() 54 | .sorted(Comparator.comparing(r -> r.value().getPayload().getDate())) 55 | .collect(Collectors.toList()); 56 | 57 | sortedRecords.forEach(record -> { 58 | 59 | LOGGER.debug("Request to handle {} event in the topic {}", record.value().getPayload().getOperation(), record.topic()); 60 | 61 | handlerFactory.getHandler(record.topic()).process(record.value()); 62 | 63 | }); 64 | 65 | acknowledgment.acknowledge(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/listener/EventHandlerFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.listener; 17 | 18 | import com.github.joumenharzli.cdc.denormalizer.listener.handler.AddressEventHandler; 19 | import com.github.joumenharzli.cdc.denormalizer.listener.handler.JobEventHandler; 20 | import com.github.joumenharzli.cdc.denormalizer.listener.handler.UserEventHandler; 21 | import com.google.common.collect.Maps; 22 | import lombok.RequiredArgsConstructor; 23 | import lombok.extern.slf4j.Slf4j; 24 | import org.apache.commons.lang.StringUtils; 25 | import org.springframework.stereotype.Component; 26 | import org.springframework.util.Assert; 27 | 28 | import javax.annotation.PostConstruct; 29 | import java.util.Map; 30 | import java.util.Optional; 31 | 32 | /** 33 | * Event Handler Factory 34 | * 35 | * @author Joumen Harzli 36 | */ 37 | @Component 38 | @RequiredArgsConstructor 39 | @Slf4j 40 | public class EventHandlerFactory { 41 | 42 | private final AddressEventHandler addressEventHandler; 43 | private final JobEventHandler jobEventHandler; 44 | private final UserEventHandler userEventHandler; 45 | 46 | private final Map handlers = Maps.newConcurrentMap(); 47 | 48 | @PostConstruct 49 | public void init() { 50 | LOGGER.debug("Initializing events handlers"); 51 | 52 | handlers.put("users", userEventHandler); 53 | handlers.put("jobs", jobEventHandler); 54 | handlers.put("addresses", addressEventHandler); 55 | } 56 | 57 | public EventHandler getHandler(String topicName) { 58 | Assert.hasText(topicName, "Topic name cannot be null/empty"); 59 | 60 | String tableName = StringUtils.substringAfterLast(topicName, ".").toLowerCase(); 61 | 62 | LOGGER.debug("Request to use a handler for the table {}", tableName); 63 | 64 | return Optional.ofNullable(handlers.get(tableName)) 65 | .orElseThrow(() -> new IllegalArgumentException("No suitable handler was found for the topic " + topicName)); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /query/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | true 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /command/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | true 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /denormalizer/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | true 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/config/ElasticsearchConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.config; 17 | 18 | import com.fasterxml.jackson.databind.DeserializationFeature; 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import org.elasticsearch.client.Client; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.data.elasticsearch.core.DefaultEntityMapper; 24 | import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; 25 | import org.springframework.data.elasticsearch.core.EntityMapper; 26 | import org.springframework.data.elasticsearch.core.geo.CustomGeoModule; 27 | 28 | import java.io.IOException; 29 | 30 | /** 31 | * Create a custom elastic search template that supports JSR-310 32 | * 33 | * @author Joumen Harzli 34 | */ 35 | @Configuration 36 | public class ElasticsearchConfiguration { 37 | 38 | @Bean 39 | public ElasticsearchTemplate elasticsearchTemplate(Client client, ObjectMapper objectMapper) { 40 | return new ElasticsearchTemplate(client, new CustomEntityMapper(objectMapper)); 41 | } 42 | 43 | /** 44 | * A custom entity mapper inspired from {@link DefaultEntityMapper} 45 | */ 46 | class CustomEntityMapper implements EntityMapper { 47 | 48 | private final ObjectMapper objectMapper; 49 | 50 | CustomEntityMapper(ObjectMapper objectMapper) { 51 | // clone the current object mapper that have JSR-310 modules registred 52 | this.objectMapper = objectMapper.copy(); 53 | 54 | this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 55 | this.objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); 56 | this.objectMapper.registerModule(new CustomGeoModule()); 57 | } 58 | 59 | @Override 60 | public String mapToString(Object object) throws IOException { 61 | return objectMapper.writeValueAsString(object); 62 | } 63 | 64 | @Override 65 | public T mapToObject(String source, Class clazz) throws IOException { 66 | return objectMapper.readValue(source, clazz); 67 | } 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/config/ElasticsearchConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.config; 17 | 18 | import com.fasterxml.jackson.databind.DeserializationFeature; 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import org.elasticsearch.client.Client; 21 | import org.springframework.context.annotation.Bean; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.data.elasticsearch.core.DefaultEntityMapper; 24 | import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; 25 | import org.springframework.data.elasticsearch.core.EntityMapper; 26 | import org.springframework.data.elasticsearch.core.geo.CustomGeoModule; 27 | 28 | import java.io.IOException; 29 | 30 | /** 31 | * Create a custom elastic search template that supports JSR-310 32 | * 33 | * @author Joumen Harzli 34 | */ 35 | @Configuration 36 | public class ElasticsearchConfiguration { 37 | 38 | @Bean 39 | public ElasticsearchTemplate elasticsearchTemplate(Client client, ObjectMapper objectMapper) { 40 | return new ElasticsearchTemplate(client, new CustomEntityMapper(objectMapper)); 41 | } 42 | 43 | /** 44 | * A custom entity mapper inspired from {@link DefaultEntityMapper} 45 | */ 46 | class CustomEntityMapper implements EntityMapper { 47 | 48 | private final ObjectMapper objectMapper; 49 | 50 | CustomEntityMapper(ObjectMapper objectMapper) { 51 | // clone the current object mapper that have JSR-310 modules registred 52 | this.objectMapper = objectMapper.copy(); 53 | 54 | this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 55 | this.objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); 56 | this.objectMapper.registerModule(new CustomGeoModule()); 57 | } 58 | 59 | @Override 60 | public String mapToString(Object object) throws IOException { 61 | return objectMapper.writeValueAsString(object); 62 | } 63 | 64 | @Override 65 | public T mapToObject(String source, Class clazz) throws IOException { 66 | return objectMapper.readValue(source, clazz); 67 | } 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/aop/TimedAspect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.aop; 17 | 18 | import io.micrometer.core.annotation.Timed; 19 | import io.micrometer.core.instrument.MeterRegistry; 20 | import io.micrometer.core.instrument.Tags; 21 | import io.micrometer.core.instrument.Timer; 22 | import io.vavr.control.Try; 23 | import lombok.RequiredArgsConstructor; 24 | import lombok.extern.slf4j.Slf4j; 25 | import org.aspectj.lang.ProceedingJoinPoint; 26 | import org.aspectj.lang.annotation.Around; 27 | import org.aspectj.lang.annotation.Aspect; 28 | import org.aspectj.lang.reflect.MethodSignature; 29 | import org.springframework.stereotype.Component; 30 | 31 | import java.util.concurrent.TimeUnit; 32 | 33 | 34 | /** 35 | * Aspect for intercepting methods annotated with {@link Timed} 36 | * 37 | * @author Joumen Harzli 38 | */ 39 | @Aspect 40 | @Component 41 | @RequiredArgsConstructor 42 | @Slf4j 43 | public class TimedAspect { 44 | 45 | private final MeterRegistry registry; 46 | 47 | @Around("execution (@io.micrometer.core.annotation.Timed * *.*(..))") 48 | public Object timedMethod(ProceedingJoinPoint joinPoint) throws Throwable { 49 | 50 | MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); 51 | 52 | String className = methodSignature.getDeclaringType().getSimpleName(); 53 | String methodName = methodSignature.getName(); 54 | String metricName = String.format("%s.%s", className, methodName); 55 | 56 | Timer.Sample timer = Timer.start(registry); 57 | 58 | return Try.of(joinPoint::proceed) 59 | .andFinally(() -> stopTimer(className, methodName, metricName, timer)); 60 | } 61 | 62 | private void stopTimer(String className, String methodName, String metricName, Timer.Sample timer) { 63 | long elapsedTime = timer.stop(getTimer(className, methodName, metricName)); 64 | long elapsedTimeInSeconds = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS); 65 | 66 | LOGGER.debug("Execution of method : {} took : {} milliseconds", metricName, elapsedTimeInSeconds); 67 | } 68 | 69 | private Timer getTimer(String className, String methodName, String metricName) { 70 | return Timer.builder(metricName) 71 | .tags(Tags.of("class", className, 72 | "method", methodName)) 73 | .publishPercentiles(0.75, 0.95, 0.99) 74 | .register(registry); 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/service/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.service; 17 | 18 | import java.util.List; 19 | 20 | import org.springframework.data.domain.Page; 21 | import org.springframework.data.domain.PageImpl; 22 | import org.springframework.data.domain.Pageable; 23 | import org.springframework.stereotype.Service; 24 | import org.springframework.util.Assert; 25 | 26 | import com.github.joumenharzli.cdc.query.repository.UserRepository; 27 | import com.github.joumenharzli.cdc.query.service.dto.QueryParameter; 28 | import com.github.joumenharzli.cdc.query.service.dto.UserDto; 29 | import com.github.joumenharzli.cdc.query.service.mapper.UserMapper; 30 | import com.github.joumenharzli.cdc.query.util.SearchQueryBuilder; 31 | 32 | import lombok.RequiredArgsConstructor; 33 | import lombok.extern.slf4j.Slf4j; 34 | import reactor.core.publisher.Mono; 35 | import reactor.core.scheduler.Schedulers; 36 | 37 | /** 38 | * Implementation for {@link UserService} 39 | * 40 | * @author Joumen Harzli 41 | */ 42 | @Service 43 | @RequiredArgsConstructor 44 | @Slf4j 45 | public class UserServiceImpl implements UserService { 46 | 47 | private final UserRepository userRepository; 48 | private final UserMapper userMapper; 49 | 50 | /** 51 | * Find users using the provided parameters 52 | * 53 | * @param parameters filters to use 54 | * @param pageable page number, size and sorting 55 | * @return the found users in a page 56 | * @throws IllegalArgumentException if any given argument is invalid 57 | */ 58 | @Override 59 | public Mono> findByCriteria(List parameters, Pageable pageable) { 60 | LOGGER.debug("Request to search for users with parameters {} and {}", parameters, pageable); 61 | 62 | Assert.notNull(parameters, "List of parameters cannot be null"); 63 | Assert.notNull(pageable, "Pageable cannot be null"); 64 | 65 | //@formatter:off 66 | return Mono.fromSupplier(() -> SearchQueryBuilder.fromParameters(parameters) 67 | .withPageable(pageable) 68 | .build()) 69 | .flatMap(searchQuery -> Mono.just(userRepository.search(searchQuery))) 70 | .subscribeOn(Schedulers.elastic()) 71 | .map(page -> new PageImpl<>(userMapper.toDtos(page.getContent()), page.getPageable(), page.getTotalElements())); 72 | //@formatter:on 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/web/UserResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.web; 17 | 18 | import java.util.Optional; 19 | 20 | import org.springframework.data.domain.Page; 21 | import org.springframework.data.domain.PageRequest; 22 | import org.springframework.http.ResponseEntity; 23 | import org.springframework.web.bind.annotation.*; 24 | 25 | import com.github.joumenharzli.cdc.query.domain.User; 26 | import com.github.joumenharzli.cdc.query.service.UserService; 27 | import com.github.joumenharzli.cdc.query.service.dto.UserDto; 28 | import com.github.joumenharzli.cdc.query.util.QueryUtils; 29 | import com.google.common.collect.Lists; 30 | 31 | import io.micrometer.core.annotation.Timed; 32 | import lombok.RequiredArgsConstructor; 33 | import lombok.extern.slf4j.Slf4j; 34 | import reactor.core.publisher.Mono; 35 | 36 | /** 37 | * Reactive REST resource for the index {@link User} 38 | * 39 | * @author Joumen Harzli 40 | */ 41 | @RequiredArgsConstructor 42 | @Slf4j 43 | @RestController 44 | @RequestMapping(value = "/api/v1/users") 45 | public class UserResource { 46 | 47 | private final UserService userService; 48 | 49 | /** 50 | * GET /users/search/:parameters: get users using multiple criteria 51 | *

52 | * example: /users/search/name=joumen&job.name=engineer&age>20&page=0&size=10 53 | * 54 | * @param parameters filters to use separated by "&" 55 | * @param page number of the page 56 | * @param size size of the page 57 | * @return the ResponseEntity with status 200 (OK) and with body containing the page with the found results 58 | * @throws IllegalArgumentException when any given argument is invalid 59 | */ 60 | @GetMapping(value = {"/search/{parameters}", "/search"}) 61 | @Timed 62 | public Mono>> search(@PathVariable Optional parameters, 63 | @RequestParam(name = "page") int page, 64 | @RequestParam(name = "size") int size) { 65 | 66 | LOGGER.debug("REST request to search for users with parameters {} and page {} and size {}", parameters, page, size); 67 | 68 | //@formatter:off 69 | return Mono.fromSupplier(() -> parameters.map(QueryUtils::parseURLParameters) 70 | .orElse(Lists.newArrayList())) 71 | .flatMap(parameterList -> userService.findByCriteria(parameterList, PageRequest.of(page, size))) 72 | .map(ResponseEntity::ok); 73 | //@formatter:on 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/util/QueryUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.util; 17 | 18 | import java.util.Arrays; 19 | import java.util.List; 20 | import java.util.stream.Collectors; 21 | 22 | import org.apache.commons.lang3.StringUtils; 23 | import org.springframework.util.Assert; 24 | 25 | import com.github.joumenharzli.cdc.query.service.dto.QueryOperator; 26 | import com.github.joumenharzli.cdc.query.service.dto.QueryParameter; 27 | 28 | import lombok.AccessLevel; 29 | import lombok.NoArgsConstructor; 30 | 31 | /** 32 | * Query Utils 33 | * 34 | * @author Joumen Harzli 35 | */ 36 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 37 | public final class QueryUtils { 38 | 39 | /** 40 | * Convert a list of URL parameters to a list of {@link QueryParameter} 41 | * 42 | * @param parameters url parameters 43 | * @return list of {@link QueryParameter} 44 | * @throws IllegalArgumentException if any given argument is invalid 45 | */ 46 | public static List parseURLParameters(String parameters) { 47 | Assert.hasText(parameters, "url parameters cannot be null/empty"); 48 | 49 | //@formatter:off 50 | return Arrays.stream(parameters.split("&")) 51 | .map(QueryUtils::parseURLParameter) 52 | .collect(Collectors.toList()); 53 | //@formatter:on 54 | } 55 | 56 | /** 57 | * Extract {@link QueryParameter} from text 58 | * 59 | * @param text text that contains a valid format of a query parameter like name=Joumen 60 | * @return the parsed query parameter 61 | */ 62 | private static QueryParameter parseURLParameter(String text) { 63 | 64 | QueryOperator operator = getQueryOperator(text); 65 | String[] values = StringUtils.split(text, operator.getOperation()); 66 | 67 | //@formatter:off 68 | return QueryParameter.builder() 69 | .field(values[0]) 70 | .value(values[1]) 71 | .operator(operator) 72 | .build(); 73 | //@formatter:on 74 | } 75 | 76 | /** 77 | * Get query operator from text 78 | * 79 | * @param text text that contains a valid format of a query parameter like name=Joumen 80 | * @return the found operator 81 | * @throws IllegalArgumentException if the text don't have a valid operator 82 | */ 83 | private static QueryOperator getQueryOperator(String text) { 84 | 85 | //@formatter:off 86 | return Arrays.stream(QueryOperator.values()) 87 | .filter(op -> StringUtils.contains(text, op.getOperation())) 88 | .findFirst() 89 | .orElseThrow( 90 | () -> new IllegalArgumentException(String.format("No supported operator was found in the text %s", text))); 91 | //@formatter:on 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/config/KafkaConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.config; 17 | 18 | import com.github.joumenharzli.cdc.denormalizer.listener.support.DebeziumEvent; 19 | import com.google.common.collect.ImmutableMap; 20 | import org.apache.kafka.clients.consumer.ConsumerConfig; 21 | import org.apache.kafka.common.serialization.StringDeserializer; 22 | import org.springframework.beans.factory.annotation.Value; 23 | import org.springframework.context.annotation.Bean; 24 | import org.springframework.context.annotation.Configuration; 25 | import org.springframework.kafka.annotation.EnableKafka; 26 | import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; 27 | import org.springframework.kafka.core.ConsumerFactory; 28 | import org.springframework.kafka.core.DefaultKafkaConsumerFactory; 29 | import org.springframework.kafka.support.serializer.JsonDeserializer; 30 | 31 | import java.util.Map; 32 | 33 | import static org.springframework.kafka.listener.AbstractMessageListenerContainer.AckMode.MANUAL; 34 | 35 | /** 36 | * Kafka Consumer Configuration 37 | * 38 | * @author Joumen Harzli 39 | */ 40 | @Configuration 41 | @EnableKafka 42 | public class KafkaConfiguration { 43 | 44 | @Value("${application.kafka.bootstrapServers}") 45 | private String bootstrapServers; 46 | 47 | @Value("${application.kafka.groupId}") 48 | private String groupId; 49 | 50 | @Bean 51 | public ConcurrentKafkaListenerContainerFactory 52 | kafkaListenerContainerFactory() { 53 | ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); 54 | 55 | factory.setConsumerFactory(consumerFactory()); 56 | factory.getContainerProperties().setAckMode(MANUAL); 57 | factory.setBatchListener(true); 58 | 59 | return factory; 60 | } 61 | 62 | @Bean 63 | public ConsumerFactory consumerFactory() { 64 | return new DefaultKafkaConsumerFactory<>(consumerConfigs(), 65 | new StringDeserializer(), 66 | new JsonDeserializer<>(DebeziumEvent.class)); 67 | } 68 | 69 | @Bean 70 | public Map consumerConfigs() { 71 | return ImmutableMap.builder() 72 | .put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers) 73 | .put(ConsumerConfig.GROUP_ID_CONFIG, groupId) 74 | .put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class) 75 | .put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class) 76 | .put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest") 77 | .put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false") 78 | .put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, "10") 79 | .build(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Denormalize and Index MySQL database with ElasticSearch and Kafka 2 | 3 | ## Overview 4 | * This example demonstrates how we can create a real time search engine for entities in an SQL database using Change Data Capture. 5 | * The same mechanism can be applied to a NonSQL database like MongoDb or to manage a cache database. 6 | * This example relies on Debezium Connector for MySQL and Kafka Connect. 7 | * The microservices was developed using Spring boot 2. 8 | 9 | ## Donations 10 | If you find my work useful, consider donating to support it :) 11 | ### Image of Ethereum Ethereum 12 | You can simply scan this QR code to get my Ethereum address 13 | 14 | My QR Code 15 | 16 | ## Architecture 17 | 18 | 19 | ## Running the example 20 | 21 | ### Requirements 22 | This example requires: 23 | * JDK 1.8 24 | * Docker >= 17 25 | * Docker-compose >= 3.6 26 | 27 | ### Steps 28 | Start by building the Spring projects using 29 | ```bash 30 | sh build.sh 31 | ``` 32 | 33 | Next, run the containers Command, MySQL, Kafka, Zookeeper, Kafka Connect using 34 | ```bash 35 | docker-compose up --build -d mysql user-command kafka-connect 36 | ``` 37 | 38 | Note: You can start all the project containers but this costs a lots of memory. You can simply use 39 | ```bash 40 | docker-compose up --build -d 41 | ``` 42 | 43 | After the successful start of the containers, activate the Kafka connector by running the command 44 | ```bash 45 | sh activate_connector.sh 46 | ``` 47 | If there are entities in the database Debezium will create a Snapshot. 48 | 49 | When the connector is successfully added, you can add an entity by running the command 50 | ```bash 51 | sh test_command.sh 52 | ``` 53 | Add, update and remove endpoints was implemented in the Command Microservice but this will not be documented in this tutorial you can try them by yourself. After running the command it's possible to stop this Microservice and MySQL container. 54 | User Command will insert an entity into MySQL. Debezium will capture the changes and send events to kafka. 55 | 56 | When the new entity is added, start the Denormalizer Microservice and ElasticSearch. 57 | The Denormalizer will process the events received from Kafka and apply changes in ElasticSearch. 58 | ```bash 59 | docker-compose up --build -d user-denormalizer elasticsearch 60 | ``` 61 | 62 | Note: It's possible also to stop MySQL and User Command if you don't plan to test some other operations 63 | ```bash 64 | docker-compose stop user-command mysql 65 | ``` 66 | 67 | Check if the events were successfully processed by reading the logs of the Denormalizer. You will find the message 68 | EventDispatcher.handleEvents took : x milliseconds 69 | 70 | To verify that everything is working start the Query Microservice and send a request to retrieve the entities. 71 | Use the commands 72 | ```bash 73 | docker-compose up --build -d user-query 74 | sh test_query.sh 75 | ``` 76 | Note: It's possible also to stop Denormalizer 77 | ```bash 78 | docker-compose stop user-denormalizer 79 | ``` 80 | 81 | You can acheive a realtime indexing and denormalizing by running all the containers in the same time and launching the same test commands in the tutorial for example 82 | ```bash 83 | sh test_command.sh 84 | sleep 3 # the process takes usually 2-3 seconds 85 | sh test_query.sh 86 | ``` 87 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/aop/ReactiveTimedAspect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.aop; 17 | 18 | import java.util.concurrent.TimeUnit; 19 | 20 | import org.aspectj.lang.ProceedingJoinPoint; 21 | import org.aspectj.lang.annotation.Around; 22 | import org.aspectj.lang.annotation.Aspect; 23 | import org.aspectj.lang.reflect.MethodSignature; 24 | import org.springframework.stereotype.Component; 25 | 26 | import io.micrometer.core.annotation.Timed; 27 | import io.micrometer.core.instrument.MeterRegistry; 28 | import io.micrometer.core.instrument.Tags; 29 | import io.micrometer.core.instrument.Timer; 30 | import static io.vavr.API.*; 31 | import static io.vavr.Predicates.instanceOf; 32 | import io.vavr.control.Try; 33 | import lombok.RequiredArgsConstructor; 34 | import lombok.extern.slf4j.Slf4j; 35 | import reactor.core.publisher.Flux; 36 | import reactor.core.publisher.Mono; 37 | 38 | 39 | /** 40 | * Aspect for intercepting methods annotated with {@link Timed} 41 | *

42 | * It also provide supports for the types {@link Mono} and {@link Flux} 43 | * 44 | * @author Joumen Harzli 45 | */ 46 | @Aspect 47 | @Component 48 | @RequiredArgsConstructor 49 | @Slf4j 50 | public class ReactiveTimedAspect { 51 | 52 | private final MeterRegistry registry; 53 | 54 | @Around("execution (@io.micrometer.core.annotation.Timed * *.*(..))") 55 | public Object timedMethod(ProceedingJoinPoint joinPoint) throws Throwable { 56 | 57 | MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); 58 | 59 | String className = methodSignature.getDeclaringType().getSimpleName(); 60 | String methodName = methodSignature.getName(); 61 | String metricName = String.format("%s.%s", className, methodName); 62 | 63 | Timer.Sample timer = Timer.start(registry); 64 | 65 | //@formatter:off 66 | return Try.of(joinPoint::proceed) 67 | .map(value -> 68 | Match(value).of( 69 | Case($(instanceOf(Mono.class)), 70 | m -> m.doOnTerminate(() -> stopTimer(className, methodName, metricName, timer))), 71 | 72 | Case($(instanceOf(Flux.class)), 73 | f -> f.doOnTerminate(() -> stopTimer(className, methodName, metricName, timer))), 74 | 75 | Case($(), res -> { 76 | stopTimer(className, methodName, metricName, timer); 77 | return res; 78 | }) 79 | )) 80 | .get(); 81 | //@formatter:on 82 | } 83 | 84 | private void stopTimer(String className, String methodName, String metricName, Timer.Sample timer) { 85 | long elapsedTime = timer.stop(getTimer(className, methodName, metricName)); 86 | long elapsedTimeInSeconds = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS); 87 | 88 | LOGGER.debug("Execution of method : {} took : {} milliseconds", metricName, elapsedTimeInSeconds); 89 | } 90 | 91 | private Timer getTimer(String className, String methodName, String metricName) { 92 | //@formatter:off 93 | return Timer.builder(metricName) 94 | .tags(Tags.of("class", className, 95 | "method", methodName)) 96 | .publishPercentiles(0.75, 0.95, 0.99) 97 | .register(registry); 98 | //@formatter:on 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/aop/ReactiveTimedAspect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.aop; 17 | 18 | import java.util.concurrent.TimeUnit; 19 | 20 | import org.aspectj.lang.ProceedingJoinPoint; 21 | import org.aspectj.lang.annotation.Around; 22 | import org.aspectj.lang.annotation.Aspect; 23 | import org.aspectj.lang.reflect.MethodSignature; 24 | import org.springframework.stereotype.Component; 25 | 26 | import io.micrometer.core.annotation.Timed; 27 | import io.micrometer.core.instrument.MeterRegistry; 28 | import io.micrometer.core.instrument.Tags; 29 | import io.micrometer.core.instrument.Timer; 30 | import static io.vavr.API.*; 31 | import static io.vavr.Predicates.instanceOf; 32 | import io.vavr.control.Try; 33 | import lombok.RequiredArgsConstructor; 34 | import lombok.extern.slf4j.Slf4j; 35 | import reactor.core.publisher.Flux; 36 | import reactor.core.publisher.Mono; 37 | 38 | 39 | /** 40 | * Aspect for intercepting methods annotated with {@link Timed} 41 | *

42 | * It also provide supports for the types {@link Mono} and {@link Flux} 43 | * 44 | * @author Joumen Harzli 45 | */ 46 | @Aspect 47 | @Component 48 | @RequiredArgsConstructor 49 | @Slf4j 50 | public class ReactiveTimedAspect { 51 | 52 | private final MeterRegistry registry; 53 | 54 | @Around("execution (@io.micrometer.core.annotation.Timed * *.*(..))") 55 | public Object timedMethod(ProceedingJoinPoint joinPoint) throws Throwable { 56 | 57 | MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); 58 | 59 | String className = methodSignature.getDeclaringType().getSimpleName(); 60 | String methodName = methodSignature.getName(); 61 | String metricName = String.format("%s.%s", className, methodName); 62 | 63 | Timer.Sample timer = Timer.start(registry); 64 | 65 | //@formatter:off 66 | return Try.of(joinPoint::proceed) 67 | .map(value -> 68 | Match(value).of( 69 | Case($(instanceOf(Mono.class)), 70 | m -> m.doOnTerminate(() -> stopTimer(className, methodName, metricName, timer))), 71 | 72 | Case($(instanceOf(Flux.class)), 73 | f -> f.doOnTerminate(() -> stopTimer(className, methodName, metricName, timer))), 74 | 75 | Case($(), res -> { 76 | stopTimer(className, methodName, metricName, timer); 77 | return res; 78 | }) 79 | )) 80 | .get(); 81 | //@formatter:on 82 | } 83 | 84 | private void stopTimer(String className, String methodName, String metricName, Timer.Sample timer) { 85 | long elapsedTime = timer.stop(getTimer(className, methodName, metricName)); 86 | long elapsedTimeInSeconds = TimeUnit.MILLISECONDS.convert(elapsedTime, TimeUnit.NANOSECONDS); 87 | 88 | LOGGER.debug("Execution of method : {} took : {} milliseconds", metricName, elapsedTimeInSeconds); 89 | } 90 | 91 | private Timer getTimer(String className, String methodName, String metricName) { 92 | //@formatter:off 93 | return Timer.builder(metricName) 94 | .tags(Tags.of("class", className, 95 | "method", methodName)) 96 | .publishPercentiles(0.75, 0.95, 0.99) 97 | .register(registry); 98 | //@formatter:on 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/web/UserResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.web; 17 | 18 | import java.net.URI; 19 | import java.util.Objects; 20 | import javax.validation.Valid; 21 | 22 | import org.apache.commons.lang3.StringUtils; 23 | import org.springframework.http.ResponseEntity; 24 | import org.springframework.web.bind.annotation.*; 25 | 26 | import com.github.joumenharzli.cdc.command.domain.User; 27 | import com.github.joumenharzli.cdc.command.service.UserService; 28 | import com.github.joumenharzli.cdc.command.service.dto.UserDto; 29 | 30 | import io.micrometer.core.annotation.Timed; 31 | import lombok.RequiredArgsConstructor; 32 | import lombok.extern.slf4j.Slf4j; 33 | import reactor.core.publisher.Mono; 34 | 35 | /** 36 | * Reactive REST resource for the entity {@link User} 37 | * 38 | * @author Joumen Harzli 39 | */ 40 | @RestController 41 | @RequestMapping("/api/v1/users") 42 | @RequiredArgsConstructor 43 | @Slf4j 44 | public class UserResource { 45 | 46 | private final UserService userService; 47 | 48 | /** 49 | * POST /users: create a new user 50 | * 51 | * @param userDto entity to save 52 | * @return the ResponseEntity with status 201 (Created) and with body the new user 53 | * @throws IllegalArgumentException when any given argument is invalid 54 | */ 55 | @PostMapping 56 | @Timed 57 | Mono> create(@Valid @RequestBody UserDto userDto) { 58 | LOGGER.debug("REST request to create a new user : {}", userDto); 59 | 60 | if (Objects.nonNull(userDto.getId())) { 61 | throw new IllegalArgumentException("A new user cannot already have an ID"); 62 | } 63 | 64 | //@formatter:off 65 | return userService.create(userDto) 66 | .map(createdUser -> ResponseEntity.created(URI.create(String.format("/api/v1/users/%s", createdUser.getId()))) 67 | .body(createdUser)); 68 | //@formatter:on 69 | } 70 | 71 | /** 72 | * PUT /users: update an existing user 73 | * 74 | * @param userDto entity to save 75 | * @return the ResponseEntity with status 200 (OK) and with body the updated user 76 | * or the ResponseEntity with status 404 (NOT FOUND) if the user was not found 77 | * @throws IllegalArgumentException when any given argument is invalid 78 | */ 79 | @PutMapping 80 | @Timed 81 | Mono> update(@Valid @RequestBody UserDto userDto) { 82 | LOGGER.debug("REST request update an existing user : {}", userDto); 83 | 84 | if (Objects.isNull(userDto.getId())) { 85 | throw new IllegalArgumentException("The provided user should have an id"); 86 | } 87 | 88 | //@formatter:off 89 | return userService.update(userDto) 90 | .map(updatedUser -> ResponseEntity.ok().body(updatedUser)); 91 | //@formatter:on 92 | } 93 | 94 | /** 95 | * DELETE /users/:userId: delete an existing user 96 | * 97 | * @param userId id of the user 98 | * @return the ResponseEntity with status 200 (OK) if deleted 99 | * or the ResponseEntity with status 404 (NOT FOUND) if the user was not found 100 | * @throws IllegalArgumentException when any given argument is invalid 101 | */ 102 | @DeleteMapping("/{userId}") 103 | @Timed 104 | Mono> deleteById(@PathVariable("userId") String userId) { 105 | LOGGER.debug("REST request to delete the user with id : {}", userId); 106 | 107 | if (StringUtils.isEmpty(userId)) { 108 | throw new IllegalArgumentException("The provided id is invalid"); 109 | } 110 | 111 | //@formatter:off 112 | return userService.deleteById(userId) 113 | .map(v -> ResponseEntity.ok().build()); 114 | //@formatter:on 115 | } 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/service/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.service; 17 | 18 | import java.util.UUID; 19 | 20 | import org.springframework.stereotype.Service; 21 | import org.springframework.transaction.annotation.Transactional; 22 | import org.springframework.util.Assert; 23 | 24 | import com.github.joumenharzli.cdc.command.domain.User; 25 | import com.github.joumenharzli.cdc.command.exception.EntityNotFoundException; 26 | import com.github.joumenharzli.cdc.command.repository.UserRepository; 27 | import com.github.joumenharzli.cdc.command.service.dto.UserDto; 28 | import com.github.joumenharzli.cdc.command.service.mapper.UserMapper; 29 | 30 | import lombok.RequiredArgsConstructor; 31 | import lombok.extern.slf4j.Slf4j; 32 | import reactor.core.publisher.Mono; 33 | import reactor.core.scheduler.Schedulers; 34 | 35 | /** 36 | * Implementation of {@link UserService} 37 | * 38 | * @author Joumen Harzli 39 | */ 40 | @Service 41 | @Transactional 42 | @RequiredArgsConstructor 43 | @Slf4j 44 | public class UserServiceImpl implements UserService { 45 | 46 | private final UserRepository userRepository; 47 | private final UserMapper userMapper; 48 | 49 | /** 50 | * Create a user 51 | * 52 | * @param userDto entity to create 53 | * @return the created user 54 | * @throws IllegalArgumentException when any given argument is invalid 55 | */ 56 | @Override 57 | public Mono create(UserDto userDto) { 58 | LOGGER.debug("Request to create user : {}", userDto); 59 | 60 | Assert.notNull(userDto, "User cannot be null"); 61 | Assert.isNull(userDto.getId(), "User should not have an id"); 62 | 63 | //@formatter:off 64 | return Mono.fromSupplier(() -> userMapper.toEntity(userDto)) 65 | .publishOn(Schedulers.parallel()) 66 | .doOnNext(userRepository::save) 67 | .map(userMapper::toDto); 68 | //@formatter:on 69 | } 70 | 71 | /** 72 | * Update a user 73 | * 74 | * @param userDto entity to update 75 | * @return the updated user 76 | * @throws EntityNotFoundException if no entity was found 77 | * @throws IllegalArgumentException when any given argument is invalid 78 | */ 79 | @Override 80 | public Mono update(UserDto userDto) { 81 | LOGGER.debug("Request update an existing user : {}", userDto); 82 | 83 | Assert.notNull(userDto, "User cannot be null"); 84 | Assert.hasText(userDto.getId(), "Id of the user cannot be null/empty"); 85 | 86 | //@formatter:off 87 | return findById(userDto.getId()).flatMap(u -> Mono.just(userMapper.toEntity(userDto))) 88 | .publishOn(Schedulers.parallel()) 89 | .doOnNext(userRepository::save) 90 | .map(userMapper::toDto); 91 | //@formatter:on 92 | } 93 | 94 | /** 95 | * Delete a user using his id 96 | * 97 | * @param userId id of the user to delete 98 | * @return an empty Mono if success 99 | * @throws EntityNotFoundException if no entity was found 100 | * @throws IllegalArgumentException when any given argument is invalid 101 | */ 102 | @Override 103 | public Mono deleteById(String userId) { 104 | LOGGER.debug("Request to delete user with id {}", userId); 105 | 106 | Assert.hasText(userId, "Id of the user cannot be null/empty"); 107 | 108 | //@formatter:off 109 | return this.findById(userId).publishOn(Schedulers.parallel()) 110 | .doOnNext(userRepository::delete) 111 | .then(); 112 | //@formatter:on 113 | } 114 | 115 | /** 116 | * Find user by his id 117 | * 118 | * @param id id of the user to find 119 | * @return the found user 120 | * @throws EntityNotFoundException if no entity was found 121 | */ 122 | private Mono findById(String id) { 123 | //@formatter:off 124 | return Mono.defer(() -> Mono.just(userRepository.findById(id) 125 | .orElseThrow(() -> new EntityNotFoundException(String.format("Entity with id %s was not found", id))))) 126 | .subscribeOn(Schedulers.elastic()); 127 | //@formatter:on 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /query/src/main/java/com/github/joumenharzli/cdc/query/web/error/RestExceptionTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.query.web.error; 17 | 18 | import java.util.List; 19 | 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | import org.springframework.context.MessageSource; 23 | import org.springframework.context.i18n.LocaleContextHolder; 24 | import org.springframework.http.HttpStatus; 25 | import org.springframework.validation.BindingResult; 26 | import org.springframework.validation.FieldError; 27 | import org.springframework.web.bind.MethodArgumentNotValidException; 28 | import org.springframework.web.bind.annotation.ControllerAdvice; 29 | import org.springframework.web.bind.annotation.ExceptionHandler; 30 | import org.springframework.web.bind.annotation.ResponseBody; 31 | import org.springframework.web.bind.annotation.ResponseStatus; 32 | 33 | import lombok.RequiredArgsConstructor; 34 | 35 | /** 36 | * Controller advice to translate the server side exceptions to client-friendly json structures. 37 | * 38 | * @author Joumen Harzli 39 | */ 40 | @ControllerAdvice 41 | @RequiredArgsConstructor 42 | public class RestExceptionTranslator { 43 | 44 | private static final Logger LOGGER = LoggerFactory.getLogger(RestExceptionTranslator.class); 45 | private static final String ERROR_MSG = "Resolved error with code {}"; 46 | 47 | private final MessageSource messageSource; 48 | 49 | /** 50 | * Handle validation errors 51 | * 52 | * @return validation error with the fields errors and a bad request 53 | */ 54 | @ResponseStatus(value = HttpStatus.BAD_REQUEST) 55 | @ExceptionHandler(value = MethodArgumentNotValidException.class) 56 | @ResponseBody 57 | public RestFieldsErrorsDto handleValidationExceptions(MethodArgumentNotValidException exception) { 58 | BindingResult result = exception.getBindingResult(); 59 | 60 | String errorCode = RestErrorConstants.ERR_VALIDATION_ERROR; 61 | LOGGER.error(ERROR_MSG, errorCode, exception); 62 | 63 | RestFieldsErrorsDto restFieldsErrors = new RestFieldsErrorsDto(errorCode, getLocalizedMessageFromErrorCode(errorCode)); 64 | 65 | List fieldErrors = result.getFieldErrors(); 66 | fieldErrors.forEach(fieldError -> 67 | restFieldsErrors.addError(new RestFieldErrorDto(fieldError.getField(), fieldError.getCode(), 68 | getLocalizedMessageFromFieldError(fieldError)))); 69 | 70 | return restFieldsErrors; 71 | 72 | } 73 | 74 | /** 75 | * Handle all types of errors 76 | * 77 | * @return internal server error 78 | */ 79 | @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) 80 | @ExceptionHandler(value = Exception.class) 81 | @ResponseBody 82 | public RestErrorDto handleAllExceptions(Exception exception) { 83 | String errorCode = RestErrorConstants.ERR_INTERNAL_SERVER_ERROR; 84 | LOGGER.error(ERROR_MSG, errorCode, exception); 85 | 86 | return new RestErrorDto(errorCode, getLocalizedMessageFromErrorCode(errorCode)); 87 | } 88 | 89 | /** 90 | * Get the correspondent localized message for a field error 91 | * 92 | * @param fieldError error that will be used for search 93 | * @return the localized message if found or the default one 94 | */ 95 | private String getLocalizedMessageFromFieldError(FieldError fieldError) { 96 | return messageSource.getMessage(fieldError, LocaleContextHolder.getLocale()); 97 | } 98 | 99 | /** 100 | * Get the correspondent localized message for an error code 101 | * 102 | * @param errorCode error that will be used for search 103 | * @return the localized message if found 104 | */ 105 | private String getLocalizedMessageFromErrorCode(String errorCode) { 106 | return getLocalizedMessageFromErrorCode(errorCode, new Object[]{}); 107 | } 108 | 109 | /** 110 | * Get the correspondent localized message for an error code 111 | * 112 | * @param errorCode error that will be used for search 113 | * @param arguments parameters that will be used when parsing the message 114 | * @return the localized message if found 115 | */ 116 | private String getLocalizedMessageFromErrorCode(String errorCode, Object[] arguments) { 117 | return messageSource.getMessage(errorCode, arguments, LocaleContextHolder.getLocale()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /command/src/main/java/com/github/joumenharzli/cdc/command/web/error/RestExceptionTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.command.web.error; 17 | 18 | import java.util.List; 19 | 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | import org.springframework.context.MessageSource; 23 | import org.springframework.context.i18n.LocaleContextHolder; 24 | import org.springframework.http.HttpStatus; 25 | import org.springframework.validation.BindingResult; 26 | import org.springframework.validation.FieldError; 27 | import org.springframework.web.bind.MethodArgumentNotValidException; 28 | import org.springframework.web.bind.annotation.ControllerAdvice; 29 | import org.springframework.web.bind.annotation.ExceptionHandler; 30 | import org.springframework.web.bind.annotation.ResponseBody; 31 | import org.springframework.web.bind.annotation.ResponseStatus; 32 | 33 | import lombok.RequiredArgsConstructor; 34 | 35 | /** 36 | * Controller advice to translate the server side exceptions to client-friendly json structures. 37 | * 38 | * @author Joumen Harzli 39 | */ 40 | @ControllerAdvice 41 | @RequiredArgsConstructor 42 | public class RestExceptionTranslator { 43 | 44 | private static final Logger LOGGER = LoggerFactory.getLogger(RestExceptionTranslator.class); 45 | private static final String ERROR_MSG = "Resolved error with code {}"; 46 | 47 | private final MessageSource messageSource; 48 | 49 | /** 50 | * Handle validation errors 51 | * 52 | * @return validation error with the fields errors and a bad request 53 | */ 54 | @ResponseStatus(value = HttpStatus.BAD_REQUEST) 55 | @ExceptionHandler(value = MethodArgumentNotValidException.class) 56 | @ResponseBody 57 | public RestFieldsErrorsDto handleValidationExceptions(MethodArgumentNotValidException exception) { 58 | BindingResult result = exception.getBindingResult(); 59 | 60 | String errorCode = RestErrorConstants.ERR_VALIDATION_ERROR; 61 | LOGGER.error(ERROR_MSG, errorCode, exception); 62 | 63 | RestFieldsErrorsDto restFieldsErrors = new RestFieldsErrorsDto(errorCode, getLocalizedMessageFromErrorCode(errorCode)); 64 | 65 | List fieldErrors = result.getFieldErrors(); 66 | fieldErrors.forEach(fieldError -> 67 | restFieldsErrors.addError(new RestFieldErrorDto(fieldError.getField(), fieldError.getCode(), 68 | getLocalizedMessageFromFieldError(fieldError)))); 69 | 70 | return restFieldsErrors; 71 | 72 | } 73 | 74 | /** 75 | * Handle all types of errors 76 | * 77 | * @return internal server error 78 | */ 79 | @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) 80 | @ExceptionHandler(value = Exception.class) 81 | @ResponseBody 82 | public RestErrorDto handleAllExceptions(Exception exception) { 83 | String errorCode = RestErrorConstants.ERR_INTERNAL_SERVER_ERROR; 84 | LOGGER.error(ERROR_MSG, errorCode, exception); 85 | 86 | return new RestErrorDto(errorCode, getLocalizedMessageFromErrorCode(errorCode)); 87 | } 88 | 89 | /** 90 | * Get the correspondent localized message for a field error 91 | * 92 | * @param fieldError error that will be used for search 93 | * @return the localized message if found or the default one 94 | */ 95 | private String getLocalizedMessageFromFieldError(FieldError fieldError) { 96 | return messageSource.getMessage(fieldError, LocaleContextHolder.getLocale()); 97 | } 98 | 99 | /** 100 | * Get the correspondent localized message for an error code 101 | * 102 | * @param errorCode error that will be used for search 103 | * @return the localized message if found 104 | */ 105 | private String getLocalizedMessageFromErrorCode(String errorCode) { 106 | return getLocalizedMessageFromErrorCode(errorCode, new Object[]{}); 107 | } 108 | 109 | /** 110 | * Get the correspondent localized message for an error code 111 | * 112 | * @param errorCode error that will be used for search 113 | * @param arguments parameters that will be used when parsing the message 114 | * @return the localized message if found 115 | */ 116 | private String getLocalizedMessageFromErrorCode(String errorCode, Object[] arguments) { 117 | return messageSource.getMessage(errorCode, arguments, LocaleContextHolder.getLocale()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /command/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.net.*; 21 | import java.io.*; 22 | import java.nio.channels.*; 23 | import java.util.Properties; 24 | 25 | public class MavenWrapperDownloader { 26 | 27 | /** 28 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 29 | */ 30 | private static final String DEFAULT_DOWNLOAD_URL = 31 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar"; 32 | 33 | /** 34 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 35 | * use instead of the default one. 36 | */ 37 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 38 | ".mvn/wrapper/maven-wrapper.properties"; 39 | 40 | /** 41 | * Path where the maven-wrapper.jar will be saved to. 42 | */ 43 | private static final String MAVEN_WRAPPER_JAR_PATH = 44 | ".mvn/wrapper/maven-wrapper.jar"; 45 | 46 | /** 47 | * Name of the property which should be used to override the default download url for the wrapper. 48 | */ 49 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 50 | 51 | public static void main(String args[]) { 52 | System.out.println("- Downloader started"); 53 | File baseDirectory = new File(args[0]); 54 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 55 | 56 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 57 | // wrapperUrl parameter. 58 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 59 | String url = DEFAULT_DOWNLOAD_URL; 60 | if(mavenWrapperPropertyFile.exists()) { 61 | FileInputStream mavenWrapperPropertyFileInputStream = null; 62 | try { 63 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 64 | Properties mavenWrapperProperties = new Properties(); 65 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 66 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 67 | } catch (IOException e) { 68 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 69 | } finally { 70 | try { 71 | if(mavenWrapperPropertyFileInputStream != null) { 72 | mavenWrapperPropertyFileInputStream.close(); 73 | } 74 | } catch (IOException e) { 75 | // Ignore ... 76 | } 77 | } 78 | } 79 | System.out.println("- Downloading from: : " + url); 80 | 81 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 82 | if(!outputFile.getParentFile().exists()) { 83 | if(!outputFile.getParentFile().mkdirs()) { 84 | System.out.println( 85 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 86 | } 87 | } 88 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 89 | try { 90 | downloadFileFromURL(url, outputFile); 91 | System.out.println("Done"); 92 | System.exit(0); 93 | } catch (Throwable e) { 94 | System.out.println("- Error downloading"); 95 | e.printStackTrace(); 96 | System.exit(1); 97 | } 98 | } 99 | 100 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 101 | URL website = new URL(urlString); 102 | ReadableByteChannel rbc; 103 | rbc = Channels.newChannel(website.openStream()); 104 | FileOutputStream fos = new FileOutputStream(destination); 105 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 106 | fos.close(); 107 | rbc.close(); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /query/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.net.*; 21 | import java.io.*; 22 | import java.nio.channels.*; 23 | import java.util.Properties; 24 | 25 | public class MavenWrapperDownloader { 26 | 27 | /** 28 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 29 | */ 30 | private static final String DEFAULT_DOWNLOAD_URL = 31 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar"; 32 | 33 | /** 34 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 35 | * use instead of the default one. 36 | */ 37 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 38 | ".mvn/wrapper/maven-wrapper.properties"; 39 | 40 | /** 41 | * Path where the maven-wrapper.jar will be saved to. 42 | */ 43 | private static final String MAVEN_WRAPPER_JAR_PATH = 44 | ".mvn/wrapper/maven-wrapper.jar"; 45 | 46 | /** 47 | * Name of the property which should be used to override the default download url for the wrapper. 48 | */ 49 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 50 | 51 | public static void main(String args[]) { 52 | System.out.println("- Downloader started"); 53 | File baseDirectory = new File(args[0]); 54 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 55 | 56 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 57 | // wrapperUrl parameter. 58 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 59 | String url = DEFAULT_DOWNLOAD_URL; 60 | if(mavenWrapperPropertyFile.exists()) { 61 | FileInputStream mavenWrapperPropertyFileInputStream = null; 62 | try { 63 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 64 | Properties mavenWrapperProperties = new Properties(); 65 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 66 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 67 | } catch (IOException e) { 68 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 69 | } finally { 70 | try { 71 | if(mavenWrapperPropertyFileInputStream != null) { 72 | mavenWrapperPropertyFileInputStream.close(); 73 | } 74 | } catch (IOException e) { 75 | // Ignore ... 76 | } 77 | } 78 | } 79 | System.out.println("- Downloading from: : " + url); 80 | 81 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 82 | if(!outputFile.getParentFile().exists()) { 83 | if(!outputFile.getParentFile().mkdirs()) { 84 | System.out.println( 85 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 86 | } 87 | } 88 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 89 | try { 90 | downloadFileFromURL(url, outputFile); 91 | System.out.println("Done"); 92 | System.exit(0); 93 | } catch (Throwable e) { 94 | System.out.println("- Error downloading"); 95 | e.printStackTrace(); 96 | System.exit(1); 97 | } 98 | } 99 | 100 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 101 | URL website = new URL(urlString); 102 | ReadableByteChannel rbc; 103 | rbc = Channels.newChannel(website.openStream()); 104 | FileOutputStream fos = new FileOutputStream(destination); 105 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 106 | fos.close(); 107 | rbc.close(); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /denormalizer/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.net.*; 21 | import java.io.*; 22 | import java.nio.channels.*; 23 | import java.util.Properties; 24 | 25 | public class MavenWrapperDownloader { 26 | 27 | /** 28 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 29 | */ 30 | private static final String DEFAULT_DOWNLOAD_URL = 31 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar"; 32 | 33 | /** 34 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 35 | * use instead of the default one. 36 | */ 37 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 38 | ".mvn/wrapper/maven-wrapper.properties"; 39 | 40 | /** 41 | * Path where the maven-wrapper.jar will be saved to. 42 | */ 43 | private static final String MAVEN_WRAPPER_JAR_PATH = 44 | ".mvn/wrapper/maven-wrapper.jar"; 45 | 46 | /** 47 | * Name of the property which should be used to override the default download url for the wrapper. 48 | */ 49 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 50 | 51 | public static void main(String args[]) { 52 | System.out.println("- Downloader started"); 53 | File baseDirectory = new File(args[0]); 54 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 55 | 56 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 57 | // wrapperUrl parameter. 58 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 59 | String url = DEFAULT_DOWNLOAD_URL; 60 | if(mavenWrapperPropertyFile.exists()) { 61 | FileInputStream mavenWrapperPropertyFileInputStream = null; 62 | try { 63 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 64 | Properties mavenWrapperProperties = new Properties(); 65 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 66 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 67 | } catch (IOException e) { 68 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 69 | } finally { 70 | try { 71 | if(mavenWrapperPropertyFileInputStream != null) { 72 | mavenWrapperPropertyFileInputStream.close(); 73 | } 74 | } catch (IOException e) { 75 | // Ignore ... 76 | } 77 | } 78 | } 79 | System.out.println("- Downloading from: : " + url); 80 | 81 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 82 | if(!outputFile.getParentFile().exists()) { 83 | if(!outputFile.getParentFile().mkdirs()) { 84 | System.out.println( 85 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 86 | } 87 | } 88 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 89 | try { 90 | downloadFileFromURL(url, outputFile); 91 | System.out.println("Done"); 92 | System.exit(0); 93 | } catch (Throwable e) { 94 | System.out.println("- Error downloading"); 95 | e.printStackTrace(); 96 | System.exit(1); 97 | } 98 | } 99 | 100 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 101 | URL website = new URL(urlString); 102 | ReadableByteChannel rbc; 103 | rbc = Channels.newChannel(website.openStream()); 104 | FileOutputStream fos = new FileOutputStream(destination); 105 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 106 | fos.close(); 107 | rbc.close(); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /denormalizer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 19 | 4.0.0 20 | 21 | denormalizer 22 | jar 23 | 24 | cdc :: denormalizer 25 | Denormalizer microservice for the users 26 | 27 | 28 | com.github.joumenharzli 29 | cdc 30 | 0.0.1-SNAPSHOT 31 | 32 | 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-actuator 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-webflux 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-logging 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-aop 53 | 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-data-elasticsearch 59 | 60 | 61 | 62 | org.springframework.kafka 63 | spring-kafka 64 | 65 | 66 | 67 | 68 | com.google.guava 69 | guava 70 | ${guava.version} 71 | 72 | 73 | 74 | io.vavr 75 | vavr 76 | ${vavr.version} 77 | 78 | 79 | 80 | org.apache.commons 81 | commons-lang3 82 | ${commons-lang.version} 83 | 84 | 85 | 86 | org.mapstruct 87 | mapstruct-jdk8 88 | ${mapstrcut.version} 89 | 90 | 91 | 92 | org.mapstruct 93 | mapstruct-processor 94 | ${mapstrcut.version} 95 | 96 | 97 | 98 | com.fasterxml.jackson.datatype 99 | jackson-datatype-jsr310 100 | 101 | 102 | 103 | org.projectlombok 104 | lombok 105 | true 106 | 107 | 108 | 109 | 110 | io.micrometer 111 | micrometer-registry-prometheus 112 | ${micrometer.version} 113 | 114 | 115 | 116 | org.springframework.boot 117 | spring-boot-devtools 118 | runtime 119 | 120 | 121 | 122 | org.springframework.boot 123 | spring-boot-starter-test 124 | test 125 | 126 | 127 | 128 | io.projectreactor 129 | reactor-test 130 | test 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /query/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 19 | 4.0.0 20 | 21 | query 22 | jar 23 | 24 | cdc :: query 25 | Query microservice for retrieving users 26 | 27 | 28 | com.github.joumenharzli 29 | cdc 30 | 0.0.1-SNAPSHOT 31 | 32 | 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-actuator 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-data-elasticsearch 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-webflux 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-aop 53 | 54 | 55 | 56 | 57 | io.vavr 58 | vavr 59 | ${vavr.version} 60 | 61 | 62 | 63 | org.apache.commons 64 | commons-lang3 65 | ${commons-lang.version} 66 | 67 | 68 | 69 | org.mapstruct 70 | mapstruct-jdk8 71 | ${mapstrcut.version} 72 | 73 | 74 | 75 | org.mapstruct 76 | mapstruct-processor 77 | ${mapstrcut.version} 78 | 79 | 80 | 81 | com.fasterxml.jackson.datatype 82 | jackson-datatype-jsr310 83 | 84 | 85 | 86 | org.projectlombok 87 | lombok 88 | true 89 | 90 | 91 | 92 | 93 | io.springfox 94 | springfox-swagger2 95 | ${swagger.version} 96 | 97 | 98 | 99 | io.springfox 100 | springfox-swagger-ui 101 | ${swagger.version} 102 | 103 | 104 | 105 | io.springfox 106 | springfox-bean-validators 107 | ${swagger.version} 108 | 109 | 110 | 111 | 112 | io.micrometer 113 | micrometer-registry-prometheus 114 | ${micrometer.version} 115 | 116 | 117 | 118 | org.springframework.boot 119 | spring-boot-devtools 120 | runtime 121 | 122 | 123 | 124 | org.springframework.boot 125 | spring-boot-starter-test 126 | test 127 | 128 | 129 | 130 | io.projectreactor 131 | reactor-test 132 | test 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 Joumen Harzli 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | # in compliance with the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License 10 | # is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | # or implied. See the License for the specific language governing permissions and limitations under 12 | # the License. 13 | # 14 | 15 | version: '3.6' 16 | services: 17 | 18 | user-command: 19 | build: command 20 | hostname: user-command 21 | container_name: user-command 22 | ports: 23 | - "8081:8081" 24 | depends_on: 25 | - mysql 26 | links: 27 | - mysql 28 | networks: 29 | - cdcrx 30 | environment: 31 | JAVA_ARGS: "-Xmx512M -Xms256M" 32 | 33 | user-denormalizer: 34 | build: denormalizer 35 | hostname: user-denormalizer 36 | container_name: user-denormalizer 37 | ports: 38 | - "8084:8084" 39 | depends_on: 40 | - kafka 41 | - elasticsearch 42 | links: 43 | - kafka 44 | - elasticsearch 45 | networks: 46 | - cdcrx 47 | environment: 48 | JAVA_ARGS: "-Xmx512M -Xms256M" 49 | 50 | user-query: 51 | build: query 52 | hostname: user-query 53 | container_name: user-query 54 | ports: 55 | - "8082:8082" 56 | depends_on: 57 | - elasticsearch 58 | links: 59 | - elasticsearch 60 | networks: 61 | - cdcrx 62 | environment: 63 | JAVA_ARGS: "-Xmx512M -Xms256M" 64 | 65 | mysql: 66 | build: mysql 67 | hostname: mysql 68 | container_name: mysql 69 | ports: 70 | - "3306:3306" 71 | networks: 72 | - cdcrx 73 | environment: 74 | - MYSQL_DATABASE=cdc 75 | - MYSQL_ROOT_PASSWORD=root 76 | - MYSQL_USER=cdc_user 77 | - MYSQL_PASSWORD=cdc_pass 78 | 79 | elasticsearch: 80 | image: docker.elastic.co/elasticsearch/elasticsearch:5.6.9 81 | hostname: elasticsearch 82 | container_name: elasticsearch 83 | ports: 84 | - "9200:9200" 85 | - "9300:9300" 86 | networks: 87 | - cdcrx 88 | environment: 89 | - cluster.name=es-cluster 90 | - bootstrap.memory_lock=true 91 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m" 92 | - discovery.type=single-node 93 | - xpack.security.enabled=false 94 | ulimits: 95 | memlock: 96 | soft: -1 97 | hard: -1 98 | 99 | zookeeper: 100 | image: confluentinc/cp-zookeeper:latest 101 | hostname: zookeeper 102 | container_name: zookeeper 103 | ports: 104 | - "32181:32181" 105 | networks: 106 | - cdcrx 107 | environment: 108 | ZOOKEEPER_CLIENT_PORT: 32181 109 | ZOOKEEPER_TICK_TIME: 2000 110 | 111 | kafka: 112 | image: confluentinc/cp-kafka:latest 113 | hostname: kafka 114 | container_name: kafka 115 | depends_on: 116 | - zookeeper 117 | ports: 118 | - "29092:29092" 119 | links: 120 | - zookeeper 121 | networks: 122 | - cdcrx 123 | environment: 124 | KAFKA_BROKER_ID: 1 125 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:32181 126 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092 127 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 128 | 129 | kafka-connect: 130 | build: connect 131 | hostname: kafka-connect 132 | container_name: kafka-connect 133 | links: 134 | - zookeeper 135 | - kafka 136 | - mysql 137 | ports: 138 | - '8083:8083' 139 | networks: 140 | - cdcrx 141 | environment: 142 | CONNECT_BOOTSTRAP_SERVERS: 'kafka:29092' 143 | CONNECT_ZOOKEEPER_CONNECT: 'zookeeper:32181' 144 | CONNECT_GROUP_ID: compose-connect-group 145 | CONNECT_CONFIG_STORAGE_TOPIC: kafka-connect-configs 146 | CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1 147 | CONNECT_OFFSET_FLUSH_INTERVAL_MS: 10000 148 | CONNECT_OFFSET_STORAGE_TOPIC: kafka-connect-offsets 149 | CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1 150 | CONNECT_STATUS_STORAGE_TOPIC: kafka-connect-status 151 | CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1 152 | CONNECT_KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter 153 | CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter 154 | CONNECT_INTERNAL_KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter 155 | CONNECT_INTERNAL_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter 156 | CONNECT_PLUGIN_PATH: /usr/share/java 157 | CONNECT_LOG4J_LOGGERS: org.apache.zookeeper=ERROR,org.I0Itec.zkclient=ERROR,org.reflections=ERROR 158 | CONNECT_PLUGIN_PATH: '/usr/share/java,/usr/local/share/jars' 159 | CONNECT_REST_HOST_NAME: 0.0.0.0 160 | CONNECT_REST_ADVERTISED_HOST_NAME: kafka-connect 161 | CONNECT_REST_PORT: 8083 162 | 163 | 164 | networks: 165 | cdcrx: 166 | driver: "bridge" 167 | -------------------------------------------------------------------------------- /denormalizer/src/main/java/com/github/joumenharzli/cdc/denormalizer/service/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Joumen Harzli 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | * 14 | */ 15 | 16 | package com.github.joumenharzli.cdc.denormalizer.service; 17 | 18 | import com.github.joumenharzli.cdc.denormalizer.domain.User; 19 | import com.github.joumenharzli.cdc.denormalizer.exception.EntityNotFoundException; 20 | import com.github.joumenharzli.cdc.denormalizer.repository.UserRepository; 21 | import com.github.joumenharzli.cdc.denormalizer.service.dto.AddressDto; 22 | import com.github.joumenharzli.cdc.denormalizer.service.dto.JobDto; 23 | import com.github.joumenharzli.cdc.denormalizer.service.dto.UserDto; 24 | import com.github.joumenharzli.cdc.denormalizer.service.mapper.AddressMapper; 25 | import com.github.joumenharzli.cdc.denormalizer.service.mapper.JobMapper; 26 | import com.github.joumenharzli.cdc.denormalizer.service.mapper.UserMapper; 27 | import lombok.RequiredArgsConstructor; 28 | import lombok.extern.slf4j.Slf4j; 29 | import org.apache.commons.lang.StringUtils; 30 | import org.springframework.stereotype.Service; 31 | import org.springframework.util.Assert; 32 | 33 | import java.util.function.Supplier; 34 | 35 | /** 36 | * User Service 37 | * 38 | * @author Joumen Harzli 39 | */ 40 | @Service 41 | @RequiredArgsConstructor 42 | @Slf4j 43 | public class UserServiceImpl implements UserService { 44 | 45 | private final UserRepository userRepository; 46 | private final UserMapper userMapper; 47 | private final AddressMapper addressMapper; 48 | private final JobMapper jobMapper; 49 | 50 | @Override 51 | public void save(UserDto userDto) { 52 | LOGGER.debug("Request to save user : {}", userDto); 53 | 54 | Assert.notNull(userDto, "User cannot be null"); 55 | 56 | userRepository.save(userMapper.toEntity(userDto)); 57 | } 58 | 59 | @Override 60 | public void delete(UserDto userDto) { 61 | LOGGER.debug("Request to delete the user : {}", userDto); 62 | 63 | Assert.notNull(userDto, "User cannot be null"); 64 | Assert.hasText(userDto.getId(), "User id cannot be null/empty"); 65 | 66 | userRepository.delete(findById(userDto.getId())); 67 | } 68 | 69 | @Override 70 | public void saveUserAddress(AddressDto addressDto) { 71 | LOGGER.debug("Request to save address {} of the user", addressDto); 72 | 73 | Assert.notNull(addressDto, "Address cannot be null"); 74 | 75 | String userId = addressDto.getUserId(); 76 | 77 | if (StringUtils.isEmpty(userId)) { 78 | LOGGER.debug("Request to save address {} of the user is ignored", addressDto); 79 | return; 80 | } 81 | 82 | User user = findById(userId); 83 | user.getAddresses().add(addressMapper.toEntity(addressDto)); 84 | userRepository.save(user); 85 | } 86 | 87 | @Override 88 | public void deleteUserAddress(AddressDto addressDto) { 89 | LOGGER.debug("Request to delete address {} of the user", addressDto); 90 | 91 | Assert.notNull(addressDto, "Address cannot be null"); 92 | 93 | String userId = addressDto.getUserId(); 94 | 95 | if (StringUtils.isEmpty(userId)) { 96 | LOGGER.debug("Request to remove address {} of the user is ignored", addressDto); 97 | return; 98 | } 99 | 100 | User user = findById(userId); 101 | user.getAddresses().remove(addressMapper.toEntity(addressDto)); 102 | userRepository.save(user); 103 | } 104 | 105 | @Override 106 | public void saveUserJob(JobDto jobDto) { 107 | LOGGER.debug("Request to save job {} of the user", jobDto); 108 | 109 | Assert.notNull(jobDto, "Job cannot be null"); 110 | 111 | String userId = jobDto.getUserId(); 112 | 113 | if (StringUtils.isEmpty(userId)) { 114 | LOGGER.debug("Request to save job {} of the user is ignored", jobDto); 115 | return; 116 | } 117 | 118 | User user = findById(userId); 119 | user.getJobs().add(jobMapper.toEntity(jobDto)); 120 | userRepository.save(user); 121 | } 122 | 123 | @Override 124 | public void deleteUserJob(JobDto jobDto) { 125 | LOGGER.debug("Request to delete job {} of the user", jobDto); 126 | 127 | Assert.notNull(jobDto, "Job cannot be null"); 128 | 129 | String userId = jobDto.getUserId(); 130 | 131 | if (StringUtils.isEmpty(userId)) { 132 | LOGGER.debug("Request to remove job {} of the user is ignored", jobDto); 133 | return; 134 | } 135 | 136 | User user = findById(userId); 137 | user.getJobs().remove(jobMapper.toEntity(jobDto)); 138 | userRepository.save(user); 139 | } 140 | 141 | private User findById(String id) { 142 | LOGGER.debug("Request to find the user with id : {}", id); 143 | 144 | return userRepository.findById(id) 145 | .orElseThrow(() -> new EntityNotFoundException(String.format("Entity with id %s was not found", id))); 146 | } 147 | 148 | 149 | } 150 | -------------------------------------------------------------------------------- /command/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 19 | 4.0.0 20 | 21 | command 22 | jar 23 | 24 | cdc :: command 25 | Command microservice for the users 26 | 27 | 28 | com.github.joumenharzli 29 | cdc 30 | 0.0.1-SNAPSHOT 31 | 32 | 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-actuator 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-data-jpa 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-webflux 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-logging 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-aop 58 | 59 | 60 | 61 | 62 | mysql 63 | mysql-connector-java 64 | ${mysql-connector-java.version} 65 | 66 | 67 | 68 | com.zaxxer 69 | HikariCP 70 | ${hikaricp.version} 71 | 72 | 73 | 74 | 75 | io.vavr 76 | vavr 77 | ${vavr.version} 78 | 79 | 80 | 81 | org.apache.commons 82 | commons-lang3 83 | ${commons-lang.version} 84 | 85 | 86 | 87 | org.mapstruct 88 | mapstruct-jdk8 89 | ${mapstrcut.version} 90 | 91 | 92 | 93 | org.mapstruct 94 | mapstruct-processor 95 | ${mapstrcut.version} 96 | 97 | 98 | 99 | com.fasterxml.jackson.datatype 100 | jackson-datatype-jsr310 101 | 102 | 103 | 104 | org.projectlombok 105 | lombok 106 | true 107 | 108 | 109 | 110 | 111 | io.springfox 112 | springfox-swagger2 113 | ${swagger.version} 114 | 115 | 116 | 117 | io.springfox 118 | springfox-swagger-ui 119 | ${swagger.version} 120 | 121 | 122 | 123 | io.springfox 124 | springfox-bean-validators 125 | ${swagger.version} 126 | 127 | 128 | 129 | 130 | io.micrometer 131 | micrometer-registry-prometheus 132 | ${micrometer.version} 133 | 134 | 135 | 136 | org.springframework.boot 137 | spring-boot-devtools 138 | runtime 139 | 140 | 141 | 142 | org.springframework.boot 143 | spring-boot-starter-test 144 | test 145 | 146 | 147 | 148 | io.projectreactor 149 | reactor-test 150 | test 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /query/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /command/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.0/maven-wrapper-0.4.0.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | --------------------------------------------------------------------------------