Initial Commit

This commit is contained in:
Shazin Sadakath
2022-07-16 08:57:25 +05:30
commit 229944df7d
37 changed files with 1055 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@@ -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

10
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/cqrs-ms-user-command-service/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/cqrs-ms-user-domain/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/cqrs-ms-user-query-service/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

14
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_18" default="true" project-jdk-name="18" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

124
.idea/uiDesigner.xml generated Normal file
View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

36
.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="e6a1a082-a92b-4b39-8cf5-54bfeaec8d12" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProjectId" id="2Bt4JHzPmeWy2BoJ5aklkpOabB7" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true"
}
}]]></component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="e6a1a082-a92b-4b39-8cf5-54bfeaec8d12" name="Changes" comment="" />
<created>1657711974359</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1657711974359</updated>
</task>
<servers />
</component>
</project>

View File

@@ -0,0 +1,4 @@
FROM openjdk:17-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cqrs-microservice-demo</artifactId>
<groupId>com.github.shazin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>cqrs-ms-user-command-service</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<start-class>com.github.shazin.cqrs.ms.user.command.Main</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-scalars</artifactId>
<version>18.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.shazin</groupId>
<artifactId>cqrs-ms-user-domain</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hsqldb/hsqldb -->
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.6.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.9.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.github.shazin.cqrs.ms.user.command.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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 {}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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<String, String> kafkaTemplate;
private final String kafkaTopicName;
private final ObjectMapper objectMapper;
private final UserCommandRepository userCommandRepository;
public DefaultUserEventHandler(KafkaTemplate<String, String> 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;
}
}
}

View File

@@ -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<UserCommand, String> {
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cqrs-microservice-demo</artifactId>
<groupId>com.github.shazin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cqrs-ms-user-domain</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>

View File

@@ -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 {}

View File

@@ -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";
}
}

View File

@@ -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";
}
}

View File

@@ -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";
}
}

View File

@@ -0,0 +1,4 @@
FROM openjdk:17-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cqrs-microservice-demo</artifactId>
<groupId>com.github.shazin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cqrs-ms-user-query-service</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<start-class>com.github.shazin.cqrs.ms.user.query.Main</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-extended-scalars</artifactId>
<version>18.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.github.shazin</groupId>
<artifactId>cqrs-ms-user-domain</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.9.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.github.shazin.cqrs.ms.user.query.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<User> 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<String, String> 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);
}
}
}

View File

@@ -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<UserEntity, String> {
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

72
docker-compose.yml Normal file
View File

@@ -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

40
pom.xml Normal file
View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.shazin</groupId>
<artifactId>cqrs-microservice-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>cqrs-ms-user-command-service</module>
<module>cqrs-ms-user-query-service</module>
<module>cqrs-ms-user-domain</module>
</modules>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>