commit 229944df7d9962f2d5ab5661a9008d17bcacbf4b Author: Shazin Sadakath Date: Sat Jul 16 08:57:25 2022 +0530 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..f593c5d --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4258c62 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..8c6ead2 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + 1657711974359 + + + + \ No newline at end of file diff --git a/cqrs-ms-user-command-service/Dockerfile b/cqrs-ms-user-command-service/Dockerfile new file mode 100644 index 0000000..ec52d9e --- /dev/null +++ b/cqrs-ms-user-command-service/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:17-alpine +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/cqrs-ms-user-command-service/pom.xml b/cqrs-ms-user-command-service/pom.xml new file mode 100644 index 0000000..4a65e5e --- /dev/null +++ b/cqrs-ms-user-command-service/pom.xml @@ -0,0 +1,84 @@ + + + + cqrs-microservice-demo + com.github.shazin + 1.0-SNAPSHOT + + 4.0.0 + jar + + cqrs-ms-user-command-service + + + 17 + 17 + com.github.shazin.cqrs.ms.user.command.Main + + + + + org.springframework.boot + spring-boot-starter-graphql + + + com.graphql-java + graphql-java-extended-scalars + 18.1 + + + org.springframework.boot + spring-boot-starter-web + + + com.github.shazin + cqrs-ms-user-domain + 1.0-SNAPSHOT + + + org.springframework.kafka + spring-kafka + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.hsqldb + hsqldb + 2.6.1 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.1.9.RELEASE + + + + repackage + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + com.github.shazin.cqrs.ms.user.command.Main + + + + + + + + \ No newline at end of file diff --git a/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/Main.java b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/Main.java new file mode 100644 index 0000000..b2ae3a8 --- /dev/null +++ b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/Main.java @@ -0,0 +1,15 @@ +package com.github.shazin.cqrs.ms.user.command; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@SpringBootApplication +@EnableJpaRepositories +public class Main { + + public static void main(String... args) { + SpringApplication.run(Main.class, args); + } + +} diff --git a/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/config/ScalarConfig.java b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/config/ScalarConfig.java new file mode 100644 index 0000000..0c3a85c --- /dev/null +++ b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/config/ScalarConfig.java @@ -0,0 +1,19 @@ +package com.github.shazin.cqrs.ms.user.command.config; + +import graphql.scalars.ExtendedScalars; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.graphql.execution.RuntimeWiringConfigurer; + +@Configuration +public class ScalarConfig implements RuntimeWiringConfigurer { + private static final Logger LOG = LoggerFactory.getLogger(ScalarConfig.class); + + @Override + public void configure(graphql.schema.idl.RuntimeWiring.Builder builder) { + LOG.trace("configure( builder: {} )", builder); + builder.scalar(ExtendedScalars.Date); + builder.scalar(ExtendedScalars.DateTime); + } +} diff --git a/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/controller/UserCommandController.java b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/controller/UserCommandController.java new file mode 100644 index 0000000..7b48e40 --- /dev/null +++ b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/controller/UserCommandController.java @@ -0,0 +1,38 @@ +package com.github.shazin.cqrs.ms.user.command.controller; + +import com.github.shazin.cqrs.ms.user.command.dto.UserInput; +import com.github.shazin.cqrs.ms.user.command.handler.UserEventHandler; +import com.github.shazin.cqrs.ms.user.dto.User; +import com.github.shazin.cqrs.ms.user.dto.UserCreateEvent; +import com.github.shazin.cqrs.ms.user.dto.UserDeleteEvent; +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.MutationMapping; +import org.springframework.stereotype.Controller; + +import java.util.UUID; + +@Controller +public class UserCommandController { + + private final UserEventHandler userEventHandler; + + public UserCommandController(UserEventHandler userService) { + this.userEventHandler = userService; + } + + @MutationMapping + public User createUser(@Argument UserInput user) { + User userCreated = new User(UUID.randomUUID().toString(), user.firstName(), user.lastName(), user.dateOfBirth(), user.identityNumber()); + UserCreateEvent userCreateEvent = new UserCreateEvent(userCreated); + userEventHandler.publishUserEvent(userCreateEvent); + return userCreated; + } + + @MutationMapping + public String deleteUser(@Argument String id) { + UserDeleteEvent userDeleteEvent = new UserDeleteEvent(id); + userEventHandler.publishUserEvent(userDeleteEvent); + return id; + } + +} diff --git a/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/dto/UserInput.java b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/dto/UserInput.java new file mode 100644 index 0000000..815fb9d --- /dev/null +++ b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/dto/UserInput.java @@ -0,0 +1,6 @@ +package com.github.shazin.cqrs.ms.user.command.dto; + +import java.io.Serializable; +import java.time.LocalDate; + +public record UserInput(String id, String firstName, String lastName, LocalDate dateOfBirth, String identityNumber) implements Serializable {} diff --git a/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/entity/UserCommand.java b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/entity/UserCommand.java new file mode 100644 index 0000000..5371c46 --- /dev/null +++ b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/entity/UserCommand.java @@ -0,0 +1,82 @@ +package com.github.shazin.cqrs.ms.user.command.entity; + +import javax.persistence.*; +import java.util.Date; +import java.util.Objects; +import java.util.UUID; + +@Entity +@Table(name = "cqrs_user_command") +public class UserCommand { + + @Id + private String id = UUID.randomUUID().toString(); + private String payload; + private Date createdDate; + private String createdBy; + private String type; + + public UserCommand() { + } + + public UserCommand(String id, String payload, Date createdDate, String createdBy, String type) { + this.id = id; + this.payload = payload; + this.createdDate = createdDate; + this.createdBy = createdBy; + this.type = type; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getPayload() { + return payload; + } + + public void setPayload(String payload) { + this.payload = payload; + } + + public Date getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Date createdDate) { + this.createdDate = createdDate; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserCommand that = (UserCommand) o; + return Objects.equals(id, that.id) && Objects.equals(payload, that.payload) && Objects.equals(createdDate, that.createdDate) && Objects.equals(createdBy, that.createdBy) && Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(id, payload, createdDate, createdBy, type); + } +} diff --git a/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/handler/UserEventHandler.java b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/handler/UserEventHandler.java new file mode 100644 index 0000000..5209bd3 --- /dev/null +++ b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/handler/UserEventHandler.java @@ -0,0 +1,7 @@ +package com.github.shazin.cqrs.ms.user.command.handler; + +import com.github.shazin.cqrs.ms.user.dto.UserEvent; + +public interface UserEventHandler { + public boolean publishUserEvent(UserEvent event); +} diff --git a/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/handler/impl/DefaultUserEventHandler.java b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/handler/impl/DefaultUserEventHandler.java new file mode 100644 index 0000000..a2ddded --- /dev/null +++ b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/handler/impl/DefaultUserEventHandler.java @@ -0,0 +1,47 @@ +package com.github.shazin.cqrs.ms.user.command.handler.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.shazin.cqrs.ms.user.command.entity.UserCommand; +import com.github.shazin.cqrs.ms.user.command.repo.UserCommandRepository; +import com.github.shazin.cqrs.ms.user.command.handler.UserEventHandler; +import com.github.shazin.cqrs.ms.user.dto.UserEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Component +public class DefaultUserEventHandler implements UserEventHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultUserEventHandler.class); + + private final KafkaTemplate kafkaTemplate; + private final String kafkaTopicName; + private final ObjectMapper objectMapper; + private final UserCommandRepository userCommandRepository; + + + public DefaultUserEventHandler(KafkaTemplate kafkaTemplate, @Value("${kafka.topic.name}") String kafkaTopicName, ObjectMapper objectMapper, UserCommandRepository userCommandRepository) { + this.kafkaTemplate = kafkaTemplate; + this.kafkaTopicName = kafkaTopicName; + this.objectMapper = objectMapper; + this.userCommandRepository = userCommandRepository; + } + + public boolean publishUserEvent(UserEvent event) { + try { + String payload = objectMapper.writeValueAsString(event); + this.kafkaTemplate.send(kafkaTopicName, payload); + this.userCommandRepository.save(new UserCommand(UUID.randomUUID().toString(), payload, event.createdDate(), event.createdBy(), event.getType())); + return true; + } catch (Exception e) { + LOGGER.error("Error while publishing user", e); + return false; + } + } + + +} diff --git a/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/repo/UserCommandRepository.java b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/repo/UserCommandRepository.java new file mode 100644 index 0000000..56eeae2 --- /dev/null +++ b/cqrs-ms-user-command-service/src/main/java/com/github/shazin/cqrs/ms/user/command/repo/UserCommandRepository.java @@ -0,0 +1,8 @@ +package com.github.shazin.cqrs.ms.user.command.repo; + +import com.github.shazin.cqrs.ms.user.command.entity.UserCommand; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserCommandRepository extends JpaRepository { + +} diff --git a/cqrs-ms-user-command-service/src/main/resources/META-INF/MANIFEST.MF b/cqrs-ms-user-command-service/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000..e372db1 --- /dev/null +++ b/cqrs-ms-user-command-service/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Start-Class: com.github.shazin.cqrs.ms.user.command.Main +Main-Class: org.springframework.boot.loader.JarLauncher \ No newline at end of file diff --git a/cqrs-ms-user-command-service/src/main/resources/application.yml b/cqrs-ms-user-command-service/src/main/resources/application.yml new file mode 100644 index 0000000..7719f7b --- /dev/null +++ b/cqrs-ms-user-command-service/src/main/resources/application.yml @@ -0,0 +1,6 @@ +spring: + kafka: + producer: + bootstrap-servers: kafka:9092 + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer \ No newline at end of file diff --git a/cqrs-ms-user-command-service/src/main/resources/graphql/schema.graphqls b/cqrs-ms-user-command-service/src/main/resources/graphql/schema.graphqls new file mode 100644 index 0000000..296d278 --- /dev/null +++ b/cqrs-ms-user-command-service/src/main/resources/graphql/schema.graphqls @@ -0,0 +1,27 @@ +scalar Date + +type User { + id: ID! + firstName: String + lastName: String + dateOfBirth: Date + identityNumber: Int +} + +input UserInput { + firstName: String + lastName: String + dateOfBirth: Date + identityNumber: Int +} + +type Mutation { + # restricted + createUser(user: UserInput!): User + deleteUser(id: ID!): ID +} + +type Query { + allUsers: [User] + findOne(id: ID!): User +} \ No newline at end of file diff --git a/cqrs-ms-user-domain/pom.xml b/cqrs-ms-user-domain/pom.xml new file mode 100644 index 0000000..dc2399e --- /dev/null +++ b/cqrs-ms-user-domain/pom.xml @@ -0,0 +1,19 @@ + + + + cqrs-microservice-demo + com.github.shazin + 1.0-SNAPSHOT + + 4.0.0 + + cqrs-ms-user-domain + + + 17 + 17 + + + \ No newline at end of file diff --git a/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/User.java b/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/User.java new file mode 100644 index 0000000..899d1d8 --- /dev/null +++ b/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/User.java @@ -0,0 +1,6 @@ +package com.github.shazin.cqrs.ms.user.dto; + +import java.io.Serializable; +import java.time.LocalDate; + +public record User(String id, String firstName, String lastName, LocalDate dateOfBirth, String identityNumber) implements Serializable {} diff --git a/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/UserCreateEvent.java b/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/UserCreateEvent.java new file mode 100644 index 0000000..590a381 --- /dev/null +++ b/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/UserCreateEvent.java @@ -0,0 +1,9 @@ +package com.github.shazin.cqrs.ms.user.dto; + +public record UserCreateEvent(User user) implements UserEvent { + + @Override + public String getType() { + return "CREATE"; + } +} diff --git a/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/UserDeleteEvent.java b/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/UserDeleteEvent.java new file mode 100644 index 0000000..8d9d6c6 --- /dev/null +++ b/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/UserDeleteEvent.java @@ -0,0 +1,9 @@ +package com.github.shazin.cqrs.ms.user.dto; + +public record UserDeleteEvent(String id) implements UserEvent { + + @Override + public String getType() { + return "DELETE"; + } +} diff --git a/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/UserEvent.java b/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/UserEvent.java new file mode 100644 index 0000000..ae770d9 --- /dev/null +++ b/cqrs-ms-user-domain/src/main/java/com/github/shazin/cqrs/ms/user/dto/UserEvent.java @@ -0,0 +1,18 @@ +package com.github.shazin.cqrs.ms.user.dto; + +import java.util.Date; + +public interface UserEvent { + + default String getType() { + return "CREATE"; + } + + default Date createdDate() { + return new Date(); + } + + default String createdBy() { + return "cqrs-ms-command"; + } +} diff --git a/cqrs-ms-user-query-service/Dockerfile b/cqrs-ms-user-query-service/Dockerfile new file mode 100644 index 0000000..ec52d9e --- /dev/null +++ b/cqrs-ms-user-query-service/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:17-alpine +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java","-jar","/app.jar"] \ No newline at end of file diff --git a/cqrs-ms-user-query-service/pom.xml b/cqrs-ms-user-query-service/pom.xml new file mode 100644 index 0000000..3c88653 --- /dev/null +++ b/cqrs-ms-user-query-service/pom.xml @@ -0,0 +1,83 @@ + + + + cqrs-microservice-demo + com.github.shazin + 1.0-SNAPSHOT + + 4.0.0 + + cqrs-ms-user-query-service + jar + + + 17 + 17 + com.github.shazin.cqrs.ms.user.query.Main + + + + + org.springframework.boot + spring-boot-starter-graphql + + + com.graphql-java + graphql-java-extended-scalars + 18.1 + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.github.shazin + cqrs-ms-user-domain + 1.0-SNAPSHOT + + + org.springframework.kafka + spring-kafka + + + mysql + mysql-connector-java + 8.0.29 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.1.9.RELEASE + + + + repackage + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + com.github.shazin.cqrs.ms.user.query.Main + + + + + + + + \ No newline at end of file diff --git a/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/Main.java b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/Main.java new file mode 100644 index 0000000..ee887db --- /dev/null +++ b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/Main.java @@ -0,0 +1,14 @@ +package com.github.shazin.cqrs.ms.user.query; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@SpringBootApplication +@EnableJpaRepositories +public class Main { + + public static void main(String... args) { + SpringApplication.run(Main.class, args); + } +} diff --git a/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/config/ScalarConfig.java b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/config/ScalarConfig.java new file mode 100644 index 0000000..9d0b8a5 --- /dev/null +++ b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/config/ScalarConfig.java @@ -0,0 +1,19 @@ +package com.github.shazin.cqrs.ms.user.query.config; + +import graphql.scalars.ExtendedScalars; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.graphql.execution.RuntimeWiringConfigurer; + +@Configuration +public class ScalarConfig implements RuntimeWiringConfigurer { + private static final Logger LOG = LoggerFactory.getLogger(ScalarConfig.class); + + @Override + public void configure(graphql.schema.idl.RuntimeWiring.Builder builder) { + LOG.trace("configure( builder: {} )", builder); + builder.scalar(ExtendedScalars.Date); + builder.scalar(ExtendedScalars.DateTime); + } +} diff --git a/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/controller/UserQueryController.java b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/controller/UserQueryController.java new file mode 100644 index 0000000..d736ce2 --- /dev/null +++ b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/controller/UserQueryController.java @@ -0,0 +1,29 @@ +package com.github.shazin.cqrs.ms.user.query.controller; + +import com.github.shazin.cqrs.ms.user.dto.User; +import com.github.shazin.cqrs.ms.user.query.repo.UserRepository; +import org.springframework.graphql.data.method.annotation.Argument; +import org.springframework.graphql.data.method.annotation.SchemaMapping; +import org.springframework.stereotype.Controller; + +import java.util.List; + +@Controller +public class UserQueryController { + + private final UserRepository userRepository; + + public UserQueryController(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @SchemaMapping(typeName = "Query", field = "allUsers") + public List allUsers() { + return userRepository.findAll().stream().map(userEntity -> new User(userEntity.getId(), userEntity.getFirstName(), userEntity.getLastName(), userEntity.getDateOfBirth(), userEntity.getIdentityNumber())).toList(); + } + + @SchemaMapping(typeName = "Query", field = "findOne") + public User findOne(@Argument String id) { + return userRepository.findById(id).map(userEntity -> new User(userEntity.getId(), userEntity.getFirstName(), userEntity.getLastName(), userEntity.getDateOfBirth(), userEntity.getIdentityNumber())).orElse(null); + } +} diff --git a/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/entity/UserEntity.java b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/entity/UserEntity.java new file mode 100644 index 0000000..4812606 --- /dev/null +++ b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/entity/UserEntity.java @@ -0,0 +1,81 @@ +package com.github.shazin.cqrs.ms.user.query.entity; + +import javax.persistence.*; +import java.time.LocalDate; +import java.util.Objects; +import java.util.UUID; + +@Entity +@Table(name = "cqrs_user") +public class UserEntity { + @Id + private String id = UUID.randomUUID().toString(); + private String firstName; + private String lastName; + private LocalDate dateOfBirth; + private String identityNumber; + + public UserEntity() { + } + + public UserEntity(String id, String firstName, String lastName, LocalDate dateOfBirth, String identityNumber) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.dateOfBirth = dateOfBirth; + this.identityNumber = identityNumber; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public LocalDate getDateOfBirth() { + return dateOfBirth; + } + + public void setDateOfBirth(LocalDate dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } + + public String getIdentityNumber() { + return identityNumber; + } + + public void setIdentityNumber(String identityNumber) { + this.identityNumber = identityNumber; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserEntity that = (UserEntity) o; + return Objects.equals(id, that.id) && Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName) && Objects.equals(dateOfBirth, that.dateOfBirth) && Objects.equals(identityNumber, that.identityNumber); + } + + @Override + public int hashCode() { + return Objects.hash(id, firstName, lastName, dateOfBirth, identityNumber); + } +} diff --git a/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/listener/UserEventListener.java b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/listener/UserEventListener.java new file mode 100644 index 0000000..e8c1eba --- /dev/null +++ b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/listener/UserEventListener.java @@ -0,0 +1,46 @@ +package com.github.shazin.cqrs.ms.user.query.listener; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.shazin.cqrs.ms.user.dto.User; +import com.github.shazin.cqrs.ms.user.dto.UserCreateEvent; +import com.github.shazin.cqrs.ms.user.dto.UserDeleteEvent; +import com.github.shazin.cqrs.ms.user.query.entity.UserEntity; +import com.github.shazin.cqrs.ms.user.query.repo.UserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.UUID; + +@Component +public class UserEventListener { + + private final static Logger LOGGER = LoggerFactory.getLogger(UserEventListener.class); + + private final ObjectMapper objectMapper; + private final UserRepository userRepository; + + public UserEventListener(ObjectMapper objectMapper, UserRepository userRepository) { + this.objectMapper = objectMapper; + this.userRepository = userRepository; + } + + @KafkaListener(topics = "${kafka.topic.name}") + public void consume(String message) { + try { + Map userEvent = objectMapper.readValue(message, Map.class); + if (userEvent.get("type").equals("CREATE")) { + UserCreateEvent userCreateEvent = objectMapper.readValue(message, UserCreateEvent.class); + User user = userCreateEvent.user(); + userRepository.save(new UserEntity(UUID.randomUUID().toString(), user.firstName(), user.lastName(), user.dateOfBirth(), user.identityNumber())); + } else if (userEvent.get("type").equals("DELETE")) { + UserDeleteEvent userDeleteEvent = objectMapper.readValue(message, UserDeleteEvent.class); + userRepository.deleteById(userDeleteEvent.id()); + } + } catch (Exception e) { + LOGGER.error("Error while handling message", e); + } + } +} diff --git a/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/repo/UserRepository.java b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/repo/UserRepository.java new file mode 100644 index 0000000..a8b6c5f --- /dev/null +++ b/cqrs-ms-user-query-service/src/main/java/com/github/shazin/cqrs/ms/user/query/repo/UserRepository.java @@ -0,0 +1,7 @@ +package com.github.shazin.cqrs.ms.user.query.repo; + +import com.github.shazin.cqrs.ms.user.query.entity.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { +} diff --git a/cqrs-ms-user-query-service/src/main/resources/META-INF/MANIFEST.MF b/cqrs-ms-user-query-service/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000..9cd10f2 --- /dev/null +++ b/cqrs-ms-user-query-service/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Start-Class: com.github.shazin.cqrs.ms.user.query.Main +Main-Class: org.springframework.boot.loader.JarLauncher \ No newline at end of file diff --git a/cqrs-ms-user-query-service/src/main/resources/application.yml b/cqrs-ms-user-query-service/src/main/resources/application.yml new file mode 100644 index 0000000..ac8f662 --- /dev/null +++ b/cqrs-ms-user-query-service/src/main/resources/application.yml @@ -0,0 +1,8 @@ +spring: + kafka: + consumer: + bootstrap-servers: kafka:9092 + group-id: cqrs-ms-user-query + auto-offset-reset: earliest + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer \ No newline at end of file diff --git a/cqrs-ms-user-query-service/src/main/resources/graphql/schema.graphqls b/cqrs-ms-user-query-service/src/main/resources/graphql/schema.graphqls new file mode 100644 index 0000000..1a8dca2 --- /dev/null +++ b/cqrs-ms-user-query-service/src/main/resources/graphql/schema.graphqls @@ -0,0 +1,14 @@ +scalar Date + +type User { + id: ID! + firstName: String + lastName: String + dateOfBirth: Date + identityNumber: Int +} + +type Query { + allUsers: [User] + findOne(id: ID!): User +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6245933 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,72 @@ +version: '2' + +networks: + cqrs-network: + driver: bridge + +services: + zookeeper: + image: 'bitnami/zookeeper:latest' + ports: + - "2181:2181" + networks: + - cqrs-network + environment: + - ALLOW_ANONYMOUS_LOGIN=yes + kafka: + image: 'bitnami/kafka:latest' + ports: + - "9092:9092" + depends_on: + - zookeeper + networks: + - cqrs-network + environment: + - ALLOW_PLAINTEXT_LISTENER=yes + - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 + db: + image: mysql + # NOTE: use of "mysql_native_password" is not recommended: https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html#upgrade-caching-sha2-password + # (this is just an example, not intended to be a production configuration) + command: --default-authentication-plugin=mysql_native_password + restart: always + environment: + - MYSQL_ROOT_PASSWORD=root + networks: + - cqrs-network + + cqrs-ms-user-command-service: + platform: linux/amd64 #Required for Mac M1 PRO + build: ./cqrs-ms-user-command-service + ports: + - "8081:8081" + environment: + - SERVER_PORT=8081 + - KAFKA_TOPIC_NAME=com.github.shazin.cqrs.ms.users.json + - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.hsqldb.jdbc.JDBCDriver + - SPRING_DATASOURCE_URL=jdbc:hsqldb:mem:testdb;DB_CLOSE_DELAY=-1 + - SPRING_DATASOURCE_USERNAME=sa + - SPRING_DATASOURCE_PASSWORD= + - SPRING_JPA_HIBERNATE_DDL_AUTO=create + depends_on: + - kafka + networks: + - cqrs-network + cqrs-ms-user-query-service: + platform: linux/amd64 #Required for Mac M1 PRO + build: ./cqrs-ms-user-query-service + ports: + - '8080:8080' + environment: + - KAFKA_TOPIC_NAME=com.github.shazin.cqrs.ms.users.json + - SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver + - SPRING_DATASOURCE_URL=jdbc:mysql://db/userdb?autoReconnect=true&useSSL=false&createDatabaseIfNotExist=true + - SPRING_DATASOURCE_USERNAME=root + - SPRING_DATASOURCE_PASSWORD=root + - SPRING_JPA_HIBERNATE_DDL_AUTO=create + - HIBERNATE_DIALECT=org.hibernate.dialect.MySQL8Dialect + depends_on: + - kafka + - db + networks: + - cqrs-network \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..046eab1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + com.github.shazin + cqrs-microservice-demo + 1.0-SNAPSHOT + + cqrs-ms-user-command-service + cqrs-ms-user-query-service + cqrs-ms-user-domain + + pom + + + org.springframework.boot + spring-boot-starter-parent + 2.7.0 + + + + + 17 + 17 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + \ No newline at end of file