mirror of
https://github.com/robindhole/Low-Level-Design.git
synced 2025-09-13 12:02:30 +00:00
Added LLD projects
This commit is contained in:
3
distributed-event-bus/.idea/.gitignore
generated
vendored
Normal file
3
distributed-event-bus/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
16
distributed-event-bus/.idea/compiler.xml
generated
Normal file
16
distributed-event-bus/.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<annotationProcessing>
|
||||
<profile name="Maven default annotation processors profile" enabled="true">
|
||||
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<module name="distributed-event-bus" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
<bytecodeTargetLevel>
|
||||
<module name="distributed-event-bus" target="11" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
20
distributed-event-bus/.idea/jarRepositories.xml
generated
Normal file
20
distributed-event-bus/.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Central Repository" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
13
distributed-event-bus/.idea/libraries/Maven__aopalliance_aopalliance_1_0.xml
generated
Normal file
13
distributed-event-bus/.idea/libraries/Maven__aopalliance_aopalliance_1_0.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: aopalliance:aopalliance:1.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/aopalliance/aopalliance/1.0/aopalliance-1.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/aopalliance/aopalliance/1.0/aopalliance-1.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/aopalliance/aopalliance/1.0/aopalliance-1.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
13
distributed-event-bus/.idea/libraries/Maven__com_google_code_gson_gson_2_8_6.xml
generated
Normal file
13
distributed-event-bus/.idea/libraries/Maven__com_google_code_gson_gson_2_8_6.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: com.google.code.gson:gson:2.8.6">
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.8.6/gson-2.8.6-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.8.6/gson-2.8.6-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
13
distributed-event-bus/.idea/libraries/Maven__com_google_guava_guava_16_0_1.xml
generated
Normal file
13
distributed-event-bus/.idea/libraries/Maven__com_google_guava_guava_16_0_1.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: com.google.guava:guava:16.0.1">
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/16.0.1/guava-16.0.1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/16.0.1/guava-16.0.1-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/16.0.1/guava-16.0.1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
13
distributed-event-bus/.idea/libraries/Maven__com_google_inject_guice_4_0.xml
generated
Normal file
13
distributed-event-bus/.idea/libraries/Maven__com_google_inject_guice_4_0.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: com.google.inject:guice:4.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.0/guice-4.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.0/guice-4.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.0/guice-4.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
13
distributed-event-bus/.idea/libraries/Maven__javax_inject_javax_inject_1.xml
generated
Normal file
13
distributed-event-bus/.idea/libraries/Maven__javax_inject_javax_inject_1.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: javax.inject:javax.inject:1">
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
13
distributed-event-bus/.idea/libraries/Maven__junit_junit_4_13.xml
generated
Normal file
13
distributed-event-bus/.idea/libraries/Maven__junit_junit_4_13.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: junit:junit:4.13">
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.13/junit-4.13.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.13/junit-4.13-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.13/junit-4.13-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
13
distributed-event-bus/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml
generated
Normal file
13
distributed-event-bus/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: org.hamcrest:hamcrest-core:1.3">
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
13
distributed-event-bus/.idea/misc.xml
generated
Normal file
13
distributed-event-bus/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MavenProjectsManager">
|
||||
<option name="originalFiles">
|
||||
<list>
|
||||
<option value="$PROJECT_DIR$/pom.xml" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
8
distributed-event-bus/.idea/modules.xml
generated
Normal file
8
distributed-event-bus/.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/distributed-event-bus.iml" filepath="$PROJECT_DIR$/distributed-event-bus.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
124
distributed-event-bus/.idea/uiDesigner.xml
generated
Normal file
124
distributed-event-bus/.idea/uiDesigner.xml
generated
Normal 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
distributed-event-bus/.idea/vcs.xml
generated
Normal file
6
distributed-event-bus/.idea/vcs.xml
generated
Normal 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>
|
8
distributed-event-bus/README.ME
Normal file
8
distributed-event-bus/README.ME
Normal file
@@ -0,0 +1,8 @@
|
||||
1) Multiple publishers and subscribers (Register from any class to eventbus)
|
||||
2) Causal ordering of topics
|
||||
3) Supports configurable retry attempts.
|
||||
4) Have a dead letter queue.
|
||||
5) Idempotency on event receiving
|
||||
6) Allow both pull and push models
|
||||
7) Allow subscribing from a timestamp or offset
|
||||
8) Allow preconditions for event subscription
|
22
distributed-event-bus/distributed-event-bus.iml
Normal file
22
distributed-event-bus/distributed-event-bus.iml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_11">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.13" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.google.inject:guice:4.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: javax.inject:javax.inject:1" level="project" />
|
||||
<orderEntry type="library" name="Maven: aopalliance:aopalliance:1.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.google.guava:guava:16.0.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.6" level="project" />
|
||||
</component>
|
||||
</module>
|
42
distributed-event-bus/pom.xml
Normal file
42
distributed-event-bus/pom.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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>interviewready.io</groupId>
|
||||
<artifactId>distributed-event-bus</artifactId>
|
||||
<version>1.0</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>11</source>
|
||||
<target>11</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
<version>4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.8.6</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
203
distributed-event-bus/src/main/java/EventBus.java
Normal file
203
distributed-event-bus/src/main/java/EventBus.java
Normal file
@@ -0,0 +1,203 @@
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import exceptions.RetryLimitExceededException;
|
||||
import exceptions.UnsubscribedPollException;
|
||||
import lib.KeyedExecutor;
|
||||
import models.Event;
|
||||
import models.FailureEvent;
|
||||
import models.Subscription;
|
||||
import util.Timer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Singleton
|
||||
public class EventBus {
|
||||
private final Map<String, List<Event>> topics;
|
||||
private final Map<String, Map<String, Integer>> eventIndexes;
|
||||
private final Map<String, ConcurrentSkipListMap<Long, String>> eventTimestamps;
|
||||
private final Map<String, Map<String, Subscription>> pullSubscriptions;
|
||||
private final Map<String, Map<String, Subscription>> pushSubscriptions;
|
||||
private final KeyedExecutor<String> eventExecutor;
|
||||
private final KeyedExecutor<String> broadcastExecutor;
|
||||
private EventBus deadLetterQueue;
|
||||
private final Timer timer;
|
||||
|
||||
@Inject
|
||||
public EventBus(final KeyedExecutor<String> eventExecutor, final KeyedExecutor<String> broadcastExecutor, final Timer timer) {
|
||||
this.topics = new ConcurrentHashMap<>();
|
||||
this.eventIndexes = new ConcurrentHashMap<>();
|
||||
this.eventTimestamps = new ConcurrentHashMap<>();
|
||||
this.pullSubscriptions = new ConcurrentHashMap<>();
|
||||
this.pushSubscriptions = new ConcurrentHashMap<>();
|
||||
this.eventExecutor = eventExecutor;
|
||||
this.broadcastExecutor = broadcastExecutor;
|
||||
this.timer = timer;
|
||||
}
|
||||
|
||||
public void setDeadLetterQueue(final EventBus deadLetterQueue) {
|
||||
this.deadLetterQueue = deadLetterQueue;
|
||||
}
|
||||
|
||||
public CompletionStage<Void> publish(final String topic, final Event event) {
|
||||
return eventExecutor.getThreadFor(topic, publishToBus(topic, event));
|
||||
}
|
||||
|
||||
private CompletionStage<Void> publishToBus(final String topic, final Event event) {
|
||||
if (eventIndexes.containsKey(topic) && eventIndexes.get(topic).containsKey(event.getId())) {
|
||||
return null;
|
||||
}
|
||||
topics.putIfAbsent(topic, new CopyOnWriteArrayList<>());
|
||||
eventIndexes.putIfAbsent(topic, new ConcurrentHashMap<>());
|
||||
eventIndexes.get(topic).put(event.getId(), topics.get(topic).size());
|
||||
eventTimestamps.putIfAbsent(topic, new ConcurrentSkipListMap<>());
|
||||
eventTimestamps.get(topic).put(timer.getCurrentTime(), event.getId());
|
||||
topics.get(topic).add(event);
|
||||
return notifyPushSubscribers(topic, event);
|
||||
}
|
||||
|
||||
private CompletionStage<Void> notifyPushSubscribers(String topic, Event event) {
|
||||
if (!pushSubscriptions.containsKey(topic)) {
|
||||
return CompletableFuture.completedStage(null);
|
||||
}
|
||||
final var subscribersForTopic = pushSubscriptions.get(topic);
|
||||
final var notifications = subscribersForTopic.values()
|
||||
.stream()
|
||||
.filter(subscription -> subscription.getPrecondition().test(event))
|
||||
.map(subscription -> executeEventHandler(event, subscription))
|
||||
.toArray(CompletableFuture[]::new);
|
||||
return CompletableFuture.allOf(notifications);
|
||||
}
|
||||
|
||||
private CompletionStage<Void> executeEventHandler(final Event event, Subscription subscription) {
|
||||
return broadcastExecutor.getThreadFor(subscription.getTopic() + subscription.getSubscriber(),
|
||||
doWithRetry(event, subscription.getEventHandler(),
|
||||
1, subscription.getNumberOfRetries())
|
||||
.exceptionally(throwable -> {
|
||||
if (deadLetterQueue != null) {
|
||||
deadLetterQueue.publish(subscription.getTopic(), new FailureEvent(event, throwable, timer.getCurrentTime()));
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
private CompletionStage<Void> doWithRetry(final Event event,
|
||||
final Function<Event, CompletionStage<Void>> task,
|
||||
final int coolDownIntervalInMillis,
|
||||
final int remainingTries) {
|
||||
return task.apply(event).handle((__, throwable) -> {
|
||||
if (throwable != null) {
|
||||
if (remainingTries == 1) {
|
||||
throw new RetryLimitExceededException(throwable);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(coolDownIntervalInMillis);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return doWithRetry(event, task, Math.max(coolDownIntervalInMillis * 2, 10), remainingTries - 1);
|
||||
} else {
|
||||
return CompletableFuture.completedFuture((Void) null);
|
||||
}
|
||||
}).thenCompose(Function.identity());
|
||||
}
|
||||
|
||||
|
||||
public CompletionStage<Event> poll(final String topic, final String subscriber) {
|
||||
return eventExecutor.getThreadFor(topic + subscriber, () -> pollBus(topic, subscriber));
|
||||
}
|
||||
|
||||
private Event pollBus(final String topic, final String subscriber) {
|
||||
var subscription = pullSubscriptions.getOrDefault(topic, new HashMap<>()).get(subscriber);
|
||||
if (subscription == null) {
|
||||
throw new UnsubscribedPollException();
|
||||
}
|
||||
for (var index = subscription.getCurrentIndex(); index.intValue() < topics.get(topic).size(); index.increment()) {
|
||||
var event = topics.get(topic).get(index.intValue());
|
||||
if (subscription.getPrecondition().test(event)) {
|
||||
index.increment();
|
||||
return event;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public CompletionStage<Void> subscribeToEventsAfter(final String topic, final String subscriber, final long timeStamp) {
|
||||
return eventExecutor.getThreadFor(topic + subscriber, () -> moveIndexAtTimestamp(topic, subscriber, timeStamp));
|
||||
}
|
||||
|
||||
private void moveIndexAtTimestamp(final String topic, final String subscriber, final long timeStamp) {
|
||||
final var closestEventAfter = eventTimestamps.get(topic).higherEntry(timeStamp);
|
||||
if (closestEventAfter == null) {
|
||||
pullSubscriptions.get(topic).get(subscriber).setCurrentIndex(eventIndexes.get(topic).size());
|
||||
} else {
|
||||
final var eventIndex = eventIndexes.get(topic).get(closestEventAfter.getValue());
|
||||
pullSubscriptions.get(topic).get(subscriber).setCurrentIndex(eventIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public CompletionStage<Void> subscribeToEventsAfter(final String topic, final String subscriber, final String eventId) {
|
||||
return eventExecutor.getThreadFor(topic + subscriber, () -> moveIndexAfterEvent(topic, subscriber, eventId));
|
||||
}
|
||||
|
||||
private void moveIndexAfterEvent(final String topic, final String subscriber, final String eventId) {
|
||||
if (eventId == null) {
|
||||
pullSubscriptions.get(topic).get(subscriber).setCurrentIndex(0);
|
||||
} else {
|
||||
final var eventIndex = eventIndexes.get(topic).get(eventId) + 1;
|
||||
pullSubscriptions.get(topic).get(subscriber).setCurrentIndex(eventIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public CompletionStage<Void> subscribeForPush(final String topic,
|
||||
final String subscriber,
|
||||
final Predicate<Event> precondition,
|
||||
final Function<Event, CompletionStage<Void>> handler,
|
||||
final int numberOfRetries) {
|
||||
return eventExecutor.getThreadFor(topic + subscriber,
|
||||
() -> subscribeForPushEvents(topic, subscriber, precondition, handler, numberOfRetries));
|
||||
}
|
||||
|
||||
private void subscribeForPushEvents(final String topic,
|
||||
final String subscriber,
|
||||
final Predicate<Event> precondition,
|
||||
final Function<Event, CompletionStage<Void>> handler,
|
||||
final int numberOfRetries) {
|
||||
addSubscriber(pushSubscriptions, subscriber, precondition, topic, handler, numberOfRetries);
|
||||
}
|
||||
|
||||
private void addSubscriber(final Map<String, Map<String, Subscription>> pullSubscriptions,
|
||||
final String subscriber,
|
||||
final Predicate<Event> precondition,
|
||||
final String topic,
|
||||
final Function<Event, CompletionStage<Void>> handler,
|
||||
final int numberOfRetries) {
|
||||
pullSubscriptions.putIfAbsent(topic, new ConcurrentHashMap<>());
|
||||
final var subscription = new Subscription(topic, subscriber, precondition, handler, numberOfRetries);
|
||||
subscription.setCurrentIndex(topics.getOrDefault(topic, new ArrayList<>()).size());
|
||||
pullSubscriptions.get(topic).put(subscriber, subscription);
|
||||
}
|
||||
|
||||
public CompletionStage<Void> subscribeForPull(final String topic, final String subscriber, final Predicate<Event> precondition) {
|
||||
return eventExecutor.getThreadFor(topic + subscriber, () -> subscribeForPullEvents(topic, subscriber, precondition));
|
||||
}
|
||||
|
||||
private void subscribeForPullEvents(final String topic, final String subscriber, final Predicate<Event> precondition) {
|
||||
addSubscriber(pullSubscriptions, subscriber, precondition, topic, null, 0);
|
||||
}
|
||||
|
||||
public CompletionStage<Void> unsubscribe(final String topic, final String subscriber) {
|
||||
return eventExecutor.getThreadFor(topic + subscriber, () -> unsubscribeFromTopic(topic, subscriber));
|
||||
}
|
||||
|
||||
private void unsubscribeFromTopic(final String topic, final String subscriber) {
|
||||
pushSubscriptions.getOrDefault(topic, new HashMap<>()).remove(subscriber);
|
||||
pullSubscriptions.getOrDefault(topic, new HashMap<>()).remove(subscriber);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,7 @@
|
||||
package exceptions;
|
||||
|
||||
public class RetryLimitExceededException extends RuntimeException {
|
||||
public RetryLimitExceededException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
package exceptions;
|
||||
|
||||
public class UnsubscribedPollException extends RuntimeException {
|
||||
}
|
31
distributed-event-bus/src/main/java/lib/KeyedExecutor.java
Normal file
31
distributed-event-bus/src/main/java/lib/KeyedExecutor.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package lib;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class KeyedExecutor<KEY> {
|
||||
private final Executor[] executorPool;
|
||||
|
||||
public KeyedExecutor(final int poolSize) {
|
||||
this.executorPool = new Executor[poolSize];
|
||||
for (int i = 0; i < poolSize; i++) {
|
||||
executorPool[i] = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
}
|
||||
|
||||
public CompletionStage<Void> getThreadFor(KEY key, Runnable task) {
|
||||
return CompletableFuture.runAsync(task, executorPool[Math.abs(key.hashCode() % executorPool.length)]);
|
||||
}
|
||||
|
||||
public <U> CompletionStage<U> getThreadFor(KEY key, Supplier<U> task) {
|
||||
return CompletableFuture.supplyAsync(task, executorPool[Math.abs(key.hashCode() % executorPool.length)]);
|
||||
}
|
||||
|
||||
public <U> CompletionStage<U> getThreadFor(KEY key, CompletionStage<U> task) {
|
||||
return CompletableFuture.supplyAsync(() -> task, executorPool[Math.abs(key.hashCode() % executorPool.length)]).thenCompose(Function.identity());
|
||||
}
|
||||
}
|
43
distributed-event-bus/src/main/java/models/Event.java
Normal file
43
distributed-event-bus/src/main/java/models/Event.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package models;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Event {
|
||||
private final String id;
|
||||
private final String publisher;
|
||||
private final EventType eventType;
|
||||
private final String description;
|
||||
private final long creationTime;
|
||||
|
||||
public Event(final String publisher,
|
||||
final EventType eventType,
|
||||
final String description,
|
||||
final long creationTime) {
|
||||
this.description = description;
|
||||
this.id = UUID.randomUUID().toString();
|
||||
this.publisher = publisher;
|
||||
this.eventType = eventType;
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getPublisher() {
|
||||
return publisher;
|
||||
}
|
||||
|
||||
public EventType getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
public long getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package models;
|
||||
|
||||
public enum EventType {
|
||||
PRIORITY, LOGGING, ERROR
|
||||
}
|
20
distributed-event-bus/src/main/java/models/FailureEvent.java
Normal file
20
distributed-event-bus/src/main/java/models/FailureEvent.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package models;
|
||||
|
||||
public class FailureEvent extends Event {
|
||||
private final Event event;
|
||||
private final Throwable throwable;
|
||||
|
||||
public FailureEvent(Event event, Throwable throwable, long failureTimestamp) {
|
||||
super("dead-letter-queue", EventType.ERROR, throwable.getMessage(), failureTimestamp);
|
||||
this.event = event;
|
||||
this.throwable = throwable;
|
||||
}
|
||||
|
||||
public Event getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
public Throwable getThrowable() {
|
||||
return throwable;
|
||||
}
|
||||
}
|
57
distributed-event-bus/src/main/java/models/Subscription.java
Normal file
57
distributed-event-bus/src/main/java/models/Subscription.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package models;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class Subscription {
|
||||
private final String topic;
|
||||
private final String subscriber;
|
||||
private final Predicate<Event> precondition;
|
||||
private final Function<Event, CompletionStage<Void>> eventHandler;
|
||||
private final int numberOfRetries;
|
||||
private final LongAdder currentIndex;
|
||||
|
||||
public Subscription(final String topic,
|
||||
final String subscriber,
|
||||
final Predicate<Event> precondition,
|
||||
final Function<Event, CompletionStage<Void>> eventHandler,
|
||||
final int numberOfRetries) {
|
||||
this.topic = topic;
|
||||
this.subscriber = subscriber;
|
||||
this.precondition = precondition;
|
||||
this.eventHandler = eventHandler;
|
||||
this.currentIndex = new LongAdder();
|
||||
this.numberOfRetries = numberOfRetries;
|
||||
}
|
||||
|
||||
public String getTopic() {
|
||||
return topic;
|
||||
}
|
||||
|
||||
public String getSubscriber() {
|
||||
return subscriber;
|
||||
}
|
||||
|
||||
public Predicate<Event> getPrecondition() {
|
||||
return precondition;
|
||||
}
|
||||
|
||||
public Function<Event, CompletionStage<Void>> getEventHandler() {
|
||||
return eventHandler;
|
||||
}
|
||||
|
||||
public LongAdder getCurrentIndex() {
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
public void setCurrentIndex(final int offset) {
|
||||
currentIndex.reset();
|
||||
currentIndex.add(offset);
|
||||
}
|
||||
|
||||
public int getNumberOfRetries() {
|
||||
return numberOfRetries;
|
||||
}
|
||||
}
|
10
distributed-event-bus/src/main/java/util/Timer.java
Normal file
10
distributed-event-bus/src/main/java/util/Timer.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package util;
|
||||
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class Timer {
|
||||
public long getCurrentTime() {
|
||||
return System.nanoTime();
|
||||
}
|
||||
}
|
340
distributed-event-bus/src/test/java/EventBusTest.java
Normal file
340
distributed-event-bus/src/test/java/EventBusTest.java
Normal file
@@ -0,0 +1,340 @@
|
||||
import com.google.gson.Gson;
|
||||
import exceptions.RetryLimitExceededException;
|
||||
import exceptions.UnsubscribedPollException;
|
||||
import lib.KeyedExecutor;
|
||||
import models.Event;
|
||||
import models.EventType;
|
||||
import models.FailureEvent;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import util.Timer;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
|
||||
// Causal ordering of topics
|
||||
|
||||
public class EventBusTest {
|
||||
public static final String TOPIC_1 = "topic-1";
|
||||
public static final String TOPIC_2 = "topic-2";
|
||||
public static final String PUBLISHER_1 = "publisher-1";
|
||||
public static final String SUBSCRIBER_1 = "subscriber-1";
|
||||
public static final String SUBSCRIBER_2 = "subscriber-2";
|
||||
private Timer timer;
|
||||
private KeyedExecutor<String> keyedExecutor;
|
||||
private KeyedExecutor<String> broadcastExecutor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
keyedExecutor = new KeyedExecutor<>(16);
|
||||
broadcastExecutor = new KeyedExecutor<>(16);
|
||||
timer = new Timer();
|
||||
}
|
||||
|
||||
private Event constructEvent(EventType priority, String description) {
|
||||
return new Event(PUBLISHER_1, priority, description, timer.getCurrentTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultBehavior() {
|
||||
final EventBus eventBus = new EventBus(keyedExecutor, broadcastExecutor, timer);
|
||||
eventBus.publish(TOPIC_1, constructEvent(EventType.LOGGING, "first event"));
|
||||
eventBus.subscribeForPull(TOPIC_1, SUBSCRIBER_1, (event) -> true).toCompletableFuture().join();
|
||||
Assert.assertNull(eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
|
||||
eventBus.publish(TOPIC_1, constructEvent(EventType.PRIORITY, "second event"));
|
||||
final Event secondEvent = eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
|
||||
Assert.assertEquals(EventType.PRIORITY, secondEvent.getEventType());
|
||||
Assert.assertEquals("second event", secondEvent.getDescription());
|
||||
|
||||
eventBus.subscribeToEventsAfter(TOPIC_1, SUBSCRIBER_1, null).toCompletableFuture().join();
|
||||
final Event firstEvent = eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
|
||||
Assert.assertEquals(EventType.LOGGING, firstEvent.getEventType());
|
||||
Assert.assertEquals("first event", firstEvent.getDescription());
|
||||
Assert.assertEquals(PUBLISHER_1, firstEvent.getPublisher());
|
||||
|
||||
final List<Event> eventCollector = new ArrayList<>();
|
||||
eventBus.subscribeForPush(TOPIC_1,
|
||||
SUBSCRIBER_2,
|
||||
(event) -> true,
|
||||
(event) -> CompletableFuture.runAsync(() -> eventCollector.add(event)),
|
||||
0).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, constructEvent(EventType.ERROR, "third event")).toCompletableFuture().join();
|
||||
|
||||
Assert.assertEquals(EventType.ERROR, eventCollector.get(0).getEventType());
|
||||
Assert.assertEquals("third event", eventCollector.get(0).getDescription());
|
||||
|
||||
eventBus.unsubscribe(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, constructEvent(EventType.LOGGING, "fourth event")).toCompletableFuture().join();
|
||||
Assert.assertTrue(eventBus.poll(TOPIC_1, SUBSCRIBER_1)
|
||||
.handle((__, throwable) -> throwable.getCause() instanceof UnsubscribedPollException)
|
||||
.toCompletableFuture().join());
|
||||
|
||||
eventCollector.clear();
|
||||
eventBus.unsubscribe(TOPIC_1, SUBSCRIBER_2).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, constructEvent(EventType.LOGGING, "fifth event")).toCompletableFuture().join();
|
||||
Assert.assertTrue(eventCollector.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexMove() {
|
||||
final EventBus eventBus = new EventBus(keyedExecutor, broadcastExecutor, timer);
|
||||
eventBus.subscribeForPull(TOPIC_1, SUBSCRIBER_1, (event) -> true).toCompletableFuture().join();
|
||||
final Event firstEvent = constructEvent(EventType.PRIORITY, "first event");
|
||||
final Event secondEvent = constructEvent(EventType.PRIORITY, "second event");
|
||||
final Event thirdEvent = constructEvent(EventType.PRIORITY, "third event");
|
||||
eventBus.publish(TOPIC_1, firstEvent).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, secondEvent).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, thirdEvent).toCompletableFuture().join();
|
||||
|
||||
eventBus.subscribeToEventsAfter(TOPIC_1, SUBSCRIBER_1, secondEvent.getId()).toCompletableFuture().join();
|
||||
final Event firstPoll = eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
Assert.assertEquals("third event", firstPoll.getDescription());
|
||||
Assert.assertNull(eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
|
||||
eventBus.subscribeToEventsAfter(TOPIC_1, SUBSCRIBER_1, null).toCompletableFuture().join();
|
||||
final Event secondPoll = eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
Assert.assertEquals("first event", secondPoll.getDescription());
|
||||
|
||||
eventBus.subscribeToEventsAfter(TOPIC_1, SUBSCRIBER_1, firstEvent.getId()).toCompletableFuture().join();
|
||||
final Event thirdPoll = eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
Assert.assertEquals("second event", thirdPoll.getDescription());
|
||||
|
||||
eventBus.subscribeToEventsAfter(TOPIC_1, SUBSCRIBER_1, thirdEvent.getId()).toCompletableFuture().join();
|
||||
Assert.assertNull(eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timestampMove() {
|
||||
final TestTimer timer = new TestTimer();
|
||||
final EventBus eventBus = new EventBus(keyedExecutor, broadcastExecutor, timer);
|
||||
eventBus.subscribeForPull(TOPIC_1, SUBSCRIBER_1, (event) -> true).toCompletableFuture().join();
|
||||
|
||||
final Event firstEvent = new Event(PUBLISHER_1, EventType.PRIORITY, "first event", timer.getCurrentTime());
|
||||
eventBus.publish(TOPIC_1, firstEvent).toCompletableFuture().join();
|
||||
timer.setCurrentTime(timer.getCurrentTime() + Duration.ofSeconds(10).toNanos());
|
||||
|
||||
final Event secondEvent = new Event(PUBLISHER_1, EventType.PRIORITY, "second event", timer.getCurrentTime());
|
||||
eventBus.publish(TOPIC_1, secondEvent).toCompletableFuture().join();
|
||||
timer.setCurrentTime(timer.getCurrentTime() + Duration.ofSeconds(10).toNanos());
|
||||
|
||||
final Event thirdEvent = new Event(PUBLISHER_1, EventType.PRIORITY, "third event", timer.getCurrentTime());
|
||||
eventBus.publish(TOPIC_1, thirdEvent).toCompletableFuture().join();
|
||||
|
||||
eventBus.subscribeToEventsAfter(TOPIC_1, SUBSCRIBER_1, secondEvent.getCreationTime() + Duration.ofSeconds(5).toNanos()).toCompletableFuture().join();
|
||||
final Event firstPoll = eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
Assert.assertEquals("third event", firstPoll.getDescription());
|
||||
Assert.assertNull(eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
|
||||
eventBus.subscribeToEventsAfter(TOPIC_1, SUBSCRIBER_1, 0).toCompletableFuture().join();
|
||||
final Event secondPoll = eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
Assert.assertEquals("first event", secondPoll.getDescription());
|
||||
|
||||
eventBus.subscribeToEventsAfter(TOPIC_1, SUBSCRIBER_1, firstEvent.getCreationTime() + Duration.ofSeconds(5).toNanos()).toCompletableFuture().join();
|
||||
final Event thirdPoll = eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
Assert.assertEquals("second event", thirdPoll.getDescription());
|
||||
|
||||
eventBus.subscribeToEventsAfter(TOPIC_1, SUBSCRIBER_1, thirdEvent.getCreationTime() + Duration.ofNanos(1).toNanos()).toCompletableFuture().join();
|
||||
Assert.assertNull(eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void idempotency() {
|
||||
final EventBus eventBus = new EventBus(keyedExecutor, broadcastExecutor, timer);
|
||||
eventBus.subscribeForPull(TOPIC_1, SUBSCRIBER_1, (event) -> true).toCompletableFuture().join();
|
||||
Event event1 = new Gson().fromJson("{\n" +
|
||||
" \"id\": \"event-5435\",\n" +
|
||||
" \"publisher\": \"random-publisher-1\",\n" +
|
||||
" \"eventType\": \"LOGGING\",\n" +
|
||||
" \"description\": \"random-event-1\",\n" +
|
||||
" \"creationTime\": 31884739810179363\n" +
|
||||
"}", Event.class);
|
||||
eventBus.publish(TOPIC_1, event1);
|
||||
|
||||
Event event2 = new Gson().fromJson("{\n" +
|
||||
" \"id\": \"event-5435\",\n" +
|
||||
" \"publisher\": \"random-publisher-2\",\n" +
|
||||
" \"eventType\": \"PRIORITY\",\n" +
|
||||
" \"description\": \"random-event-2\",\n" +
|
||||
" \"creationTime\": 31824735510179363\n" +
|
||||
"}", Event.class);
|
||||
eventBus.publish(TOPIC_1, event2);
|
||||
|
||||
|
||||
final Event firstEvent = eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
Assert.assertEquals(EventType.LOGGING, firstEvent.getEventType());
|
||||
Assert.assertEquals("random-event-1", firstEvent.getDescription());
|
||||
Assert.assertNull(eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsubscribePushEvents() {
|
||||
final EventBus eventBus = new EventBus(keyedExecutor, broadcastExecutor, timer);
|
||||
final List<Event> topic1 = new ArrayList<>(), topic2 = new ArrayList<>();
|
||||
eventBus.subscribeForPush(TOPIC_1, SUBSCRIBER_1, event -> true, event -> {
|
||||
topic1.add(event);
|
||||
return CompletableFuture.completedStage(null);
|
||||
}, 0).toCompletableFuture().join();
|
||||
eventBus.subscribeForPush(TOPIC_2, SUBSCRIBER_1, event -> true, event -> {
|
||||
topic2.add(event);
|
||||
return CompletableFuture.completedStage(null);
|
||||
}, 0).toCompletableFuture().join();
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
eventBus.publish(TOPIC_1, constructEvent(EventType.PRIORITY, UUID.randomUUID().toString())).toCompletableFuture().join();
|
||||
}
|
||||
eventBus.publish(TOPIC_2, constructEvent(EventType.PRIORITY, UUID.randomUUID().toString())).toCompletableFuture().join();
|
||||
eventBus.unsubscribe(TOPIC_1, SUBSCRIBER_1);
|
||||
Assert.assertEquals(3, topic1.size());
|
||||
Assert.assertEquals(1, topic2.size());
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
eventBus.publish(TOPIC_2, constructEvent(EventType.PRIORITY, UUID.randomUUID().toString())).toCompletableFuture().join();
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
eventBus.publish(TOPIC_1, constructEvent(EventType.PRIORITY, UUID.randomUUID().toString())).toCompletableFuture().join();
|
||||
}
|
||||
Assert.assertEquals(3, topic1.size());
|
||||
Assert.assertEquals(3, topic2.size());
|
||||
|
||||
eventBus.subscribeForPush(TOPIC_1, SUBSCRIBER_1, event -> true, event -> {
|
||||
topic1.add(event);
|
||||
return CompletableFuture.completedStage(null);
|
||||
}, 0).toCompletableFuture().join();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
eventBus.publish(TOPIC_1, constructEvent(EventType.PRIORITY, UUID.randomUUID().toString())).toCompletableFuture().join();
|
||||
}
|
||||
Assert.assertEquals(6, topic1.size());
|
||||
Assert.assertEquals(3, topic2.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsubscribePullEvents() {
|
||||
final EventBus eventBus = new EventBus(keyedExecutor, broadcastExecutor, timer);
|
||||
eventBus.subscribeForPull(TOPIC_1, SUBSCRIBER_1, event -> true).toCompletableFuture().join();
|
||||
eventBus.subscribeForPull(TOPIC_2, SUBSCRIBER_1, event -> true).toCompletableFuture().join();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
eventBus.publish(TOPIC_1, constructEvent(EventType.PRIORITY, UUID.randomUUID().toString())).toCompletableFuture().join();
|
||||
}
|
||||
eventBus.publish(TOPIC_2, constructEvent(EventType.PRIORITY, UUID.randomUUID().toString())).toCompletableFuture().join();
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Assert.assertNotNull(eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
}
|
||||
Assert.assertNotNull(eventBus.poll(TOPIC_2, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
|
||||
eventBus.unsubscribe(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
for (int i = 0; i < 2; i++) {
|
||||
eventBus.publish(TOPIC_2, constructEvent(EventType.PRIORITY, UUID.randomUUID().toString())).toCompletableFuture().join();
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
eventBus.publish(TOPIC_1, constructEvent(EventType.PRIORITY, UUID.randomUUID().toString())).toCompletableFuture().join();
|
||||
}
|
||||
|
||||
Assert.assertTrue(eventBus.poll(TOPIC_1, SUBSCRIBER_1)
|
||||
.handle((__, throwable) -> throwable.getCause() instanceof UnsubscribedPollException).toCompletableFuture().join());
|
||||
for (int i = 0; i < 2; i++) {
|
||||
Assert.assertNotNull(eventBus.poll(TOPIC_2, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
}
|
||||
|
||||
eventBus.subscribeForPull(TOPIC_1, SUBSCRIBER_1, event -> true).toCompletableFuture().join();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
eventBus.publish(TOPIC_1, constructEvent(EventType.PRIORITY, UUID.randomUUID().toString())).toCompletableFuture().join();
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Assert.assertNotNull(eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
}
|
||||
Assert.assertNull(eventBus.poll(TOPIC_2, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deadLetterQueue() {
|
||||
final EventBus eventBus = new EventBus(keyedExecutor, broadcastExecutor, timer);
|
||||
final EventBus dlq = new EventBus(new KeyedExecutor<>(3), new KeyedExecutor<>(3), new Timer());
|
||||
eventBus.setDeadLetterQueue(dlq);
|
||||
dlq.subscribeForPull(TOPIC_1, SUBSCRIBER_1, event -> event.getEventType().equals(EventType.ERROR));
|
||||
final AtomicLong attempts = new AtomicLong();
|
||||
final int maxTries = 5;
|
||||
eventBus.subscribeForPush(TOPIC_1, SUBSCRIBER_1, event -> true, event -> {
|
||||
attempts.incrementAndGet();
|
||||
return CompletableFuture.failedStage(new RuntimeException());
|
||||
}, maxTries).toCompletableFuture().join();
|
||||
final Event event = new Event(PUBLISHER_1, EventType.LOGGING, "random", timer.getCurrentTime());
|
||||
eventBus.publish(TOPIC_1, event).toCompletableFuture().join();
|
||||
Assert.assertEquals(5, attempts.intValue());
|
||||
final Event failureEvent = dlq.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
Assert.assertTrue(failureEvent instanceof FailureEvent);
|
||||
Assert.assertEquals(event.getId(), ((FailureEvent) failureEvent).getEvent().getId());
|
||||
Assert.assertEquals(EventType.ERROR, failureEvent.getEventType());
|
||||
Assert.assertTrue(((FailureEvent) failureEvent).getThrowable().getCause() instanceof RetryLimitExceededException);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retrySuccess() {
|
||||
final EventBus eventBus = new EventBus(keyedExecutor, broadcastExecutor, timer);
|
||||
final AtomicLong attempts = new AtomicLong();
|
||||
final int maxTries = 5;
|
||||
final List<Event> events = new ArrayList<>();
|
||||
eventBus.subscribeForPush(TOPIC_1, SUBSCRIBER_1, event -> true, event -> {
|
||||
if (attempts.incrementAndGet() == maxTries) {
|
||||
events.add(event);
|
||||
return CompletableFuture.completedStage(null);
|
||||
} else {
|
||||
return CompletableFuture.failedStage(new RuntimeException("TRY no: " + attempts.intValue()));
|
||||
}
|
||||
}, maxTries).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, new Event(PUBLISHER_1, EventType.LOGGING, "random", timer.getCurrentTime())).toCompletableFuture().join();
|
||||
|
||||
Assert.assertEquals(EventType.LOGGING, events.get(0).getEventType());
|
||||
Assert.assertEquals("random", events.get(0).getDescription());
|
||||
Assert.assertEquals(5, attempts.intValue());
|
||||
Assert.assertEquals(1, events.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preconditionCheckForPush() {
|
||||
final EventBus eventBus = new EventBus(keyedExecutor, broadcastExecutor, timer);
|
||||
final List<Event> events = new ArrayList<>();
|
||||
eventBus.subscribeForPush(TOPIC_1, SUBSCRIBER_1, event -> event.getDescription().contains("-1"), event -> {
|
||||
events.add(event);
|
||||
return CompletableFuture.completedStage(null);
|
||||
}, 0).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, new Event(PUBLISHER_1, EventType.LOGGING, "random-event-1", timer.getCurrentTime())).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, new Event(PUBLISHER_1, EventType.LOGGING, "random-event-2", timer.getCurrentTime())).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, new Event(PUBLISHER_1, EventType.LOGGING, "random-event-12", timer.getCurrentTime())).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, new Event(PUBLISHER_1, EventType.LOGGING, "random-event-21", timer.getCurrentTime())).toCompletableFuture().join();
|
||||
|
||||
Assert.assertEquals(events.size(), 2);
|
||||
Assert.assertEquals(EventType.LOGGING, events.get(0).getEventType());
|
||||
Assert.assertEquals("random-event-1", events.get(0).getDescription());
|
||||
Assert.assertEquals(EventType.LOGGING, events.get(1).getEventType());
|
||||
Assert.assertEquals("random-event-12", events.get(1).getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preconditionCheckForPull() {
|
||||
final EventBus eventBus = new EventBus(keyedExecutor, broadcastExecutor, timer);
|
||||
eventBus.subscribeForPull(TOPIC_1, SUBSCRIBER_1, event -> event.getDescription().contains("-1")).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, new Event(PUBLISHER_1, EventType.LOGGING, "random-event-1", timer.getCurrentTime())).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, new Event(PUBLISHER_1, EventType.LOGGING, "random-event-2", timer.getCurrentTime())).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, new Event(PUBLISHER_1, EventType.LOGGING, "random-event-12", timer.getCurrentTime())).toCompletableFuture().join();
|
||||
eventBus.publish(TOPIC_1, new Event(PUBLISHER_1, EventType.LOGGING, "random-event-21", timer.getCurrentTime())).toCompletableFuture().join();
|
||||
|
||||
final Event event1 = eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
Assert.assertEquals(EventType.LOGGING, event1.getEventType());
|
||||
Assert.assertEquals("random-event-1", event1.getDescription());
|
||||
final Event event2 = eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join();
|
||||
Assert.assertEquals(EventType.LOGGING, event2.getEventType());
|
||||
Assert.assertEquals("random-event-12", event2.getDescription());
|
||||
Assert.assertNull(eventBus.poll(TOPIC_1, SUBSCRIBER_1).toCompletableFuture().join());
|
||||
}
|
||||
}
|
18
distributed-event-bus/src/test/java/TestTimer.java
Normal file
18
distributed-event-bus/src/test/java/TestTimer.java
Normal file
@@ -0,0 +1,18 @@
|
||||
import util.Timer;
|
||||
|
||||
public class TestTimer extends Timer {
|
||||
private long currentTime;
|
||||
|
||||
public TestTimer() {
|
||||
this.currentTime = System.nanoTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCurrentTime() {
|
||||
return currentTime;
|
||||
}
|
||||
|
||||
public void setCurrentTime(final long currentTime) {
|
||||
this.currentTime = currentTime;
|
||||
}
|
||||
}
|
BIN
distributed-event-bus/target/classes/EventBus.class
Normal file
BIN
distributed-event-bus/target/classes/EventBus.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
distributed-event-bus/target/classes/lib/KeyedExecutor.class
Normal file
BIN
distributed-event-bus/target/classes/lib/KeyedExecutor.class
Normal file
Binary file not shown.
BIN
distributed-event-bus/target/classes/models/Event.class
Normal file
BIN
distributed-event-bus/target/classes/models/Event.class
Normal file
Binary file not shown.
BIN
distributed-event-bus/target/classes/models/EventType.class
Normal file
BIN
distributed-event-bus/target/classes/models/EventType.class
Normal file
Binary file not shown.
BIN
distributed-event-bus/target/classes/models/FailureEvent.class
Normal file
BIN
distributed-event-bus/target/classes/models/FailureEvent.class
Normal file
Binary file not shown.
BIN
distributed-event-bus/target/classes/models/Subscription.class
Normal file
BIN
distributed-event-bus/target/classes/models/Subscription.class
Normal file
Binary file not shown.
BIN
distributed-event-bus/target/classes/util/Timer.class
Normal file
BIN
distributed-event-bus/target/classes/util/Timer.class
Normal file
Binary file not shown.
BIN
distributed-event-bus/target/test-classes/EventBusTest.class
Normal file
BIN
distributed-event-bus/target/test-classes/EventBusTest.class
Normal file
Binary file not shown.
BIN
distributed-event-bus/target/test-classes/TestTimer.class
Normal file
BIN
distributed-event-bus/target/test-classes/TestTimer.class
Normal file
Binary file not shown.
Reference in New Issue
Block a user