diff --git a/.github/workflows/cart-ci.yaml b/.github/workflows/cart-ci.yaml
index a35d87b1ae..d25a8e49bf 100644
--- a/.github/workflows/cart-ci.yaml
+++ b/.github/workflows/cart-ci.yaml
@@ -25,7 +25,7 @@ jobs:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- uses: ./.github/workflows/actions
- name: Run Maven Build Command
- run: mvn clean install -DskipTests -f cart
+ run: mvn clean install -DskipTests
- name: Run Maven Test
run: mvn test -f cart
- name: Unit Test Results
diff --git a/.github/workflows/order-ci.yaml b/.github/workflows/order-ci.yaml
index f9fb5f781e..0364eb8e0e 100644
--- a/.github/workflows/order-ci.yaml
+++ b/.github/workflows/order-ci.yaml
@@ -25,7 +25,7 @@ jobs:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- uses: ./.github/workflows/actions
- name: Run Maven Build Command
- run: mvn clean install -DskipTests -f order
+ run: mvn clean install -DskipTests
- name: Run Maven Test
run: mvn test -f order
- name: Unit Test Results
diff --git a/cart/pom.xml b/cart/pom.xml
index ef8316ecd1..cfdd5584ad 100644
--- a/cart/pom.xml
+++ b/cart/pom.xml
@@ -109,6 +109,12 @@
org.liquibase
liquibase-core
+
+ com.yas
+ saga
+ 0.0.1-SNAPSHOT
+ compile
+
diff --git a/cart/src/main/java/com/yas/cart/controller/CartController.java b/cart/src/main/java/com/yas/cart/controller/CartController.java
index e060fffbef..57dd0bf8a2 100644
--- a/cart/src/main/java/com/yas/cart/controller/CartController.java
+++ b/cart/src/main/java/com/yas/cart/controller/CartController.java
@@ -11,6 +11,8 @@
import jakarta.validation.constraints.NotEmpty;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -40,7 +42,7 @@ public ResponseEntity> listCartDetailByCustomerId(@PathVar
public ResponseEntity getLastCart(Principal principal) {
if (principal == null)
return ResponseEntity.ok(null);
- return ResponseEntity.ok(cartService.getLastCart());
+ return ResponseEntity.ok(cartService.getLastCart(principal.getName()));
}
@PostMapping(path = "/storefront/carts")
@@ -56,18 +58,21 @@ public ResponseEntity createCart(@Valid @RequestBody @NotEmpty
@PutMapping("cart-item")
public ResponseEntity updateCart(@Valid @RequestBody CartItemVm cartItemVm) {
- return new ResponseEntity<>(cartService.updateCartItems(cartItemVm), HttpStatus.OK);
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ return new ResponseEntity<>(cartService.updateCartItems(cartItemVm, auth.getName()), HttpStatus.OK);
}
@DeleteMapping("/storefront/cart-item")
public ResponseEntity removeCartItemByProductId(@RequestParam Long productId) {
- cartService.removeCartItemByProductId(productId);
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ cartService.removeCartItemByProductId(productId, auth.getName());
return ResponseEntity.noContent().build();
}
@DeleteMapping("/storefront/cart-item/multi-delete")
public ResponseEntity removeCartItemListByProductIdList(@RequestParam List productIds) {
- cartService.removeCartItemListByProductIdList(productIds);
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ cartService.removeCartItemListByProductIdList(productIds, auth.getName());
return ResponseEntity.noContent().build();
}
diff --git a/cart/src/main/java/com/yas/cart/repository/CartItemRepository.java b/cart/src/main/java/com/yas/cart/repository/CartItemRepository.java
index 3bb93e1088..97a3ab8486 100644
--- a/cart/src/main/java/com/yas/cart/repository/CartItemRepository.java
+++ b/cart/src/main/java/com/yas/cart/repository/CartItemRepository.java
@@ -5,6 +5,7 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@@ -16,8 +17,10 @@ public interface CartItemRepository extends JpaRepository {
Optional findByCartIdAndProductId(Long cartId, Long productId);
+ @Transactional
void deleteByCartIdAndProductId(Long cartId, Long productId);
+ @Transactional
void deleteByCartIdAndProductIdIn(Long cartId, List productIds);
@Query("select sum(ci.quantity) from CartItem ci where ci.cart.id = ?1")
diff --git a/cart/src/main/java/com/yas/cart/saga/HandlerDispatcherRegister.java b/cart/src/main/java/com/yas/cart/saga/HandlerDispatcherRegister.java
new file mode 100644
index 0000000000..3f4fc016df
--- /dev/null
+++ b/cart/src/main/java/com/yas/cart/saga/HandlerDispatcherRegister.java
@@ -0,0 +1,17 @@
+package com.yas.cart.saga;
+
+import com.yas.cart.saga.handler.CartItemCommandHandler;
+import io.eventuate.tram.commands.consumer.CommandDispatcher;
+import io.eventuate.tram.sagas.participant.SagaCommandDispatcherFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class HandlerDispatcherRegister {
+ @Bean
+ public CommandDispatcher consumerCommandDispatcher(CartItemCommandHandler target,
+ SagaCommandDispatcherFactory sagaCommandDispatcherFactory) {
+
+ return sagaCommandDispatcherFactory.make("cartItemCommandDispatcher", target.commandHandlerDefinitions());
+ }
+}
diff --git a/cart/src/main/java/com/yas/cart/saga/handler/CartItemCommandHandler.java b/cart/src/main/java/com/yas/cart/saga/handler/CartItemCommandHandler.java
new file mode 100644
index 0000000000..bd59435e6c
--- /dev/null
+++ b/cart/src/main/java/com/yas/cart/saga/handler/CartItemCommandHandler.java
@@ -0,0 +1,43 @@
+package com.yas.cart.saga.handler;
+
+import com.yas.cart.exception.BadRequestException;
+import com.yas.cart.exception.NotFoundException;
+import com.yas.cart.service.CartService;
+import com.yas.saga.cart.command.DeleteCartItemCommand;
+import com.yas.saga.cart.reply.DeleteCartItemFailure;
+import com.yas.saga.cart.reply.DeleteCartItemSuccess;
+import io.eventuate.tram.commands.consumer.CommandHandlers;
+import io.eventuate.tram.commands.consumer.CommandMessage;
+import io.eventuate.tram.messaging.common.Message;
+import io.eventuate.tram.sagas.participant.SagaCommandHandlersBuilder;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import static io.eventuate.tram.commands.consumer.CommandHandlerReplyBuilder.withFailure;
+import static io.eventuate.tram.commands.consumer.CommandHandlerReplyBuilder.withSuccess;
+
+@Component
+@RequiredArgsConstructor
+public class CartItemCommandHandler {
+
+ private final CartService cartService;
+
+ public CommandHandlers commandHandlerDefinitions() {
+ return SagaCommandHandlersBuilder
+ .fromChannel("cartService")
+ .onMessage(DeleteCartItemCommand.class, this::deleteCartItem)
+ .build();
+ }
+
+ private Message deleteCartItem(CommandMessage cm) {
+ DeleteCartItemCommand cmd = cm.getCommand();
+ try {
+ this.cartService.removeCartItemListByProductIdList(cmd.getProductIds(), cmd.getCustomerId());
+ String productIdsStr = StringUtils.join(cmd.getProductIds(), ",");
+ return withSuccess(new DeleteCartItemSuccess("Delete all card items of product ids [" + productIdsStr + "] success"));
+ } catch (BadRequestException | NotFoundException e) {
+ return withFailure(new DeleteCartItemFailure(e.getMessage()));
+ }
+ }
+}
diff --git a/cart/src/main/java/com/yas/cart/service/CartService.java b/cart/src/main/java/com/yas/cart/service/CartService.java
index be4415a75a..2ba674221c 100644
--- a/cart/src/main/java/com/yas/cart/service/CartService.java
+++ b/cart/src/main/java/com/yas/cart/service/CartService.java
@@ -8,7 +8,6 @@
import com.yas.cart.repository.CartRepository;
import com.yas.cart.utils.Constants;
import com.yas.cart.viewmodel.*;
-import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@@ -88,9 +87,8 @@ public CartGetDetailVm addToCart(List cartItemVms) {
return CartGetDetailVm.fromModel(cart);
}
- public CartGetDetailVm getLastCart() {
- Authentication auth = SecurityContextHolder.getContext().getAuthentication();
- return cartRepository.findByCustomerIdAndOrderIdIsNull(auth.getName())
+ public CartGetDetailVm getLastCart(String customerId) {
+ return cartRepository.findByCustomerIdAndOrderIdIsNull(customerId)
.stream().reduce((first, second) -> second)
.map(CartGetDetailVm::fromModel).orElse(CartGetDetailVm.fromModel(new Cart()));
}
@@ -103,8 +101,8 @@ private CartItem getCartItemByProductId(Set cartItems, Long productId)
return new CartItem();
}
- public CartItemPutVm updateCartItems(CartItemVm cartItemVm) {
- CartGetDetailVm currentCart = getLastCart();
+ public CartItemPutVm updateCartItems(CartItemVm cartItemVm, String customerId) {
+ CartGetDetailVm currentCart = getLastCart(customerId);
validateCart(currentCart, cartItemVm.productId());
@@ -124,11 +122,9 @@ public CartItemPutVm updateCartItems(CartItemVm cartItemVm) {
}
}
- @Transactional
- public void removeCartItemListByProductIdList(List productIdList) {
- CartGetDetailVm currentCart = getLastCart();
- productIdList.stream().forEach(id -> validateCart(currentCart, id));
-
+ public void removeCartItemListByProductIdList(List productIdList, String customerId) {
+ CartGetDetailVm currentCart = getLastCart(customerId);
+ productIdList.forEach(id -> validateCart(currentCart, id));
cartItemRepository.deleteByCartIdAndProductIdIn(currentCart.id(), productIdList);
}
@@ -145,8 +141,8 @@ private void validateCart(CartGetDetailVm cart, Long productId) {
}
@Transactional
- public void removeCartItemByProductId(Long productId) {
- CartGetDetailVm currentCart = getLastCart();
+ public void removeCartItemByProductId(Long productId, String customerId) {
+ CartGetDetailVm currentCart = getLastCart(customerId);
validateCart(currentCart, productId);
diff --git a/cart/src/main/resources/application.properties b/cart/src/main/resources/application.properties
index 6842d9ff6f..f70805f268 100644
--- a/cart/src/main/resources/application.properties
+++ b/cart/src/main/resources/application.properties
@@ -8,6 +8,11 @@ management.endpoints.web.exposure.include=prometheus
management.metrics.distribution.percentiles-histogram.http.server.requests=true
management.metrics.tags.application=${spring.application.name}
+eventuatelocal.kafka.bootstrap.servers=kafka:9092
+eventuatelocal.zookeeper.connection.string=zookeeper:2181
+eventuate.database.schema=eventuate
+spring.liquibase.parameters.eventualSlotName=cart
+
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://identity/realms/Yas
@@ -26,6 +31,9 @@ spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialec
# Hibernate ddl auto (none, create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = none
+# Disable open in view transaction
+spring.jpa.open-in-view=false
+
#Enable liquibase
spring.liquibase.enabled=true
diff --git a/cart/src/main/resources/db/changelog/db.changelog-master.yaml b/cart/src/main/resources/db/changelog/db.changelog-master.yaml
index 419ad29d64..d9ef4f57ba 100644
--- a/cart/src/main/resources/db/changelog/db.changelog-master.yaml
+++ b/cart/src/main/resources/db/changelog/db.changelog-master.yaml
@@ -2,4 +2,6 @@ databaseChangeLog:
- includeAll:
path: db/changelog/ddl/
- includeAll:
- path: db/changelog/data/
\ No newline at end of file
+ path: db/changelog/data/
+ - includeAll:
+ path: db/changelog/eventuate-dll/
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 2ae323ebfc..1f832ee01b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -220,10 +220,12 @@ services:
networks:
- yas-network
postgres:
- image: debezium/postgres:15-alpine
+ image: debezium/postgres:15-alpine-custom
+ build: ./docker/postgres
ports:
- "5432:5432"
volumes:
+ - ./docker/postgres/postgresql.conf.sample:/usr/share/postgresql/postgresql.conf.sample
- ./postgres_init.sql:/docker-entrypoint-initdb.d/postgres_init.sql
- postgres:/var/lib/postgresql/data
command: postgres -c 'max_connections=500'
@@ -261,7 +263,7 @@ services:
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
- KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://kafka:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
@@ -302,6 +304,52 @@ services:
networks:
- yas-network
+ eventuate-cdc:
+ image: eventuateio/eventuate-cdc-service:0.15.0.RELEASE
+ ports:
+ - "8099:8080"
+ depends_on:
+ - postgres
+ - kafka
+ - zookeeper
+ networks:
+ - yas-network
+ environment:
+ CDC_OPTS: "--debug"
+
+ EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
+ EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181
+
+ EVENTUATE_CDC_READER_READER1_TYPE: postgres-wal
+ EVENTUATE_CDC_READER_READER1_DATASOURCEURL: jdbc:postgresql://postgres/order
+ EVENTUATE_CDC_READER_READER1_MONITORINGSCHEMA: eventuate
+ EVENTUATE_CDC_READER_READER1_DATASOURCEUSERNAME: admin
+ EVENTUATE_CDC_READER_READER1_DATASOURCEPASSWORD: admin
+ EVENTUATE_CDC_READER_READER1_DATASOURCEDRIVERCLASSNAME: org.postgresql.Driver
+ EVENTUATE_CDC_READER_READER1_LEADERSHIPLOCKPATH: /eventuate/cdc/leader/order
+ EVENTUATE_CDC_READER_READER1_OFFSETSTORAGETOPICNAME: db.history.common
+ EVENTUATE_CDC_READER_READER1_OUTBOXID: 1
+ EVENTUATE_CDC_READER_READER1_POSTGRESREPLICATIONSLOTNAME: eventuate_slot_order
+
+ EVENTUATE_CDC_READER_READER2_TYPE: postgres-wal
+ EVENTUATE_CDC_READER_READER2_DATASOURCEURL: jdbc:postgresql://postgres/cart
+ EVENTUATE_CDC_READER_READER2_MONITORINGSCHEMA: eventuate
+ EVENTUATE_CDC_READER_READER2_DATASOURCEUSERNAME: admin
+ EVENTUATE_CDC_READER_READER2_DATASOURCEPASSWORD: admin
+ EVENTUATE_CDC_READER_READER2_DATASOURCEDRIVERCLASSNAME: org.postgresql.Driver
+ EVENTUATE_CDC_READER_READER2_LEADERSHIPLOCKPATH: /eventuate/cdc/leader/cart
+ EVENTUATE_CDC_READER_READER2_OFFSETSTORAGETOPICNAME: db.history.common
+ EVENTUATE_CDC_READER_READER2_OUTBOXID: 2
+ EVENTUATE_CDC_READER_READER2_POSTGRESREPLICATIONSLOTNAME: eventuate_slot_cart
+
+ EVENTUATE_CDC_PIPELINE_PIPELINE1_TYPE: eventuate-tram
+ EVENTUATE_CDC_PIPELINE_PIPELINE1_READER: reader1
+ EVENTUATE_CDC_PIPELINE_PIPELINE1_EVENTUATEDATABASESCHEMA: eventuate
+
+ EVENTUATE_CDC_PIPELINE_PIPELINE2_TYPE: eventuate-tram
+ EVENTUATE_CDC_PIPELINE_PIPELINE2_READER: reader2
+ EVENTUATE_CDC_PIPELINE_PIPELINE2_EVENTUATEDATABASESCHEMA: eventuate
+
networks:
yas-network:
driver: bridge
diff --git a/docker/postgres/Dockerfile b/docker/postgres/Dockerfile
new file mode 100644
index 0000000000..5254934b13
--- /dev/null
+++ b/docker/postgres/Dockerfile
@@ -0,0 +1,7 @@
+FROM debezium/postgres:15-alpine
+ENV WAL2JSON_TAG=wal2json_2_5
+RUN apk add --no-cache --virtual .debezium-build-deps gcc clang15 llvm15 git make musl-dev pkgconf \
+ && git clone https://github.com/eulerto/wal2json -b master --single-branch \
+ && (cd /wal2json && git checkout tags/$WAL2JSON_TAG -b $WAL2JSON_TAG && make && make install) \
+ && rm -rf wal2json \
+ && apk del .debezium-build-deps
\ No newline at end of file
diff --git a/docker/postgres/postgresql.conf.sample b/docker/postgres/postgresql.conf.sample
new file mode 100644
index 0000000000..3857e7fb3d
--- /dev/null
+++ b/docker/postgres/postgresql.conf.sample
@@ -0,0 +1,16 @@
+# LOGGING
+# log_min_error_statement = fatal
+# log_min_messages = DEBUG1
+
+# CONNECTION
+listen_addresses = '*'
+
+# MODULES
+shared_preload_libraries = 'decoderbufs,wal2json'
+
+# REPLICATION
+wal_level = logical # minimal, archive, hot_standby, or logical (change requires restart)
+max_wal_senders = 20 # max number of walsender processes (change requires restart)
+#wal_keep_segments = 4 # in logfile segments, 16MB each; 0 disables
+#wal_sender_timeout = 60s # in milliseconds; 0 disables
+max_replication_slots = 20 # max number of replication slots (change requires restart)
\ No newline at end of file
diff --git a/k8s/charts/cart/Chart.yaml b/k8s/charts/cart/Chart.yaml
index 9cefd83f01..1acc5c4771 100644
--- a/k8s/charts/cart/Chart.yaml
+++ b/k8s/charts/cart/Chart.yaml
@@ -15,7 +15,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 0.1.0
+version: 0.2.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
diff --git a/k8s/charts/cart/values.yaml b/k8s/charts/cart/values.yaml
index 52de1c1cd3..e4cb799ab3 100644
--- a/k8s/charts/cart/values.yaml
+++ b/k8s/charts/cart/values.yaml
@@ -10,3 +10,12 @@ backend:
enabled: true
host: api.yas.local.com
path: /cart
+ extraVolumes:
+ - name: cart-application-config
+ configMap:
+ name: cart-application-configmap
+ extraVolumeMounts:
+ - name: cart-application-config
+ mountPath: /opt/yas/cart
+ extraApplicationConfigPaths:
+ - /opt/yas/cart/cart-application.yaml
diff --git a/k8s/charts/eventuate-cdc/.helmignore b/k8s/charts/eventuate-cdc/.helmignore
new file mode 100644
index 0000000000..0e8a0eb36f
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/k8s/charts/eventuate-cdc/Chart.yaml b/k8s/charts/eventuate-cdc/Chart.yaml
new file mode 100644
index 0000000000..9a1c34871a
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: eventuate-cdc
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "1.16.0"
diff --git a/k8s/charts/eventuate-cdc/templates/NOTES.txt b/k8s/charts/eventuate-cdc/templates/NOTES.txt
new file mode 100644
index 0000000000..70d12d7f5d
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/templates/NOTES.txt
@@ -0,0 +1,22 @@
+1. Get the application URL by running these commands:
+{{- if .Values.ingress.enabled }}
+{{- range $host := .Values.ingress.hosts }}
+ {{- range .paths }}
+ http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
+ {{- end }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+ export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "eventuate-cdc.fullname" . }})
+ export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+ echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+ NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+ You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "eventuate-cdc.fullname" . }}'
+ export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "eventuate-cdc.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+ echo http://$SERVICE_IP:{{ .Values.service.port }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+ export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "eventuate-cdc.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+ export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
+ echo "Visit http://127.0.0.1:8080 to use your application"
+ kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
+{{- end }}
diff --git a/k8s/charts/eventuate-cdc/templates/_helpers.tpl b/k8s/charts/eventuate-cdc/templates/_helpers.tpl
new file mode 100644
index 0000000000..501fe99b9b
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "eventuate-cdc.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "eventuate-cdc.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "eventuate-cdc.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "eventuate-cdc.labels" -}}
+helm.sh/chart: {{ include "eventuate-cdc.chart" . }}
+{{ include "eventuate-cdc.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "eventuate-cdc.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "eventuate-cdc.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "eventuate-cdc.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "eventuate-cdc.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/k8s/charts/eventuate-cdc/templates/deployment.yaml b/k8s/charts/eventuate-cdc/templates/deployment.yaml
new file mode 100644
index 0000000000..6b58b5178f
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/templates/deployment.yaml
@@ -0,0 +1,73 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "eventuate-cdc.fullname" . }}
+ labels:
+ {{- include "eventuate-cdc.labels" . | nindent 4 }}
+ annotations:
+ reloader.stakater.com/search: "true"
+spec:
+ {{- if not .Values.autoscaling.enabled }}
+ replicas: {{ .Values.replicaCount }}
+ {{- end }}
+ selector:
+ matchLabels:
+ {{- include "eventuate-cdc.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ {{- with .Values.podAnnotations }}
+ annotations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ labels:
+ {{- include "eventuate-cdc.selectorLabels" . | nindent 8 }}
+ spec:
+ {{- with .Values.imagePullSecrets }}
+ imagePullSecrets:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ serviceAccountName: {{ include "eventuate-cdc.serviceAccountName" . }}
+ securityContext:
+ {{- toYaml .Values.podSecurityContext | nindent 8 }}
+ containers:
+ - name: {{ .Chart.Name }}
+ securityContext:
+ {{- toYaml .Values.securityContext | nindent 12 }}
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ env:
+ - name: SPRING_APPLICATION_NAME
+ value: eventuate-cdc
+ - name: LOGGING_PATTERN_LEVEL
+ value: application=${spring.application.name} traceId=%X{traceId:-} spanId=%X{spanId:-} level=%level
+ envFrom:
+ - secretRef:
+ name: eventuate-cdc-env-secret
+ ports:
+ - name: http
+ containerPort: {{ .Values.service.port }}
+ protocol: TCP
+ livenessProbe:
+ httpGet:
+ path: /actuator/health
+ port: http
+ readinessProbe:
+ httpGet:
+ path: /actuator/health
+ port: http
+ initialDelaySeconds: 15
+ periodSeconds: 5
+ resources:
+ {{- toYaml .Values.resources | nindent 12 }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.affinity }}
+ affinity:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.tolerations }}
+ tolerations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
diff --git a/k8s/charts/eventuate-cdc/templates/hpa.yaml b/k8s/charts/eventuate-cdc/templates/hpa.yaml
new file mode 100644
index 0000000000..b9db5b29e6
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/templates/hpa.yaml
@@ -0,0 +1,32 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: {{ include "eventuate-cdc.fullname" . }}
+ labels:
+ {{- include "eventuate-cdc.labels" . | nindent 4 }}
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: {{ include "eventuate-cdc.fullname" . }}
+ minReplicas: {{ .Values.autoscaling.minReplicas }}
+ maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+ metrics:
+ {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+ {{- end }}
+ {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+ - type: Resource
+ resource:
+ name: memory
+ target:
+ type: Utilization
+ averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+ {{- end }}
+{{- end }}
diff --git a/k8s/charts/eventuate-cdc/templates/ingress.yaml b/k8s/charts/eventuate-cdc/templates/ingress.yaml
new file mode 100644
index 0000000000..77bdefddeb
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/templates/ingress.yaml
@@ -0,0 +1,61 @@
+{{- if .Values.ingress.enabled -}}
+{{- $fullName := include "eventuate-cdc.fullname" . -}}
+{{- $svcPort := .Values.service.port -}}
+{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
+ {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
+ {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
+ {{- end }}
+{{- end }}
+{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1
+{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1beta1
+{{- else -}}
+apiVersion: extensions/v1beta1
+{{- end }}
+kind: Ingress
+metadata:
+ name: {{ $fullName }}
+ labels:
+ {{- include "eventuate-cdc.labels" . | nindent 4 }}
+ {{- with .Values.ingress.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
+ ingressClassName: {{ .Values.ingress.className }}
+ {{- end }}
+ {{- if .Values.ingress.tls }}
+ tls:
+ {{- range .Values.ingress.tls }}
+ - hosts:
+ {{- range .hosts }}
+ - {{ . | quote }}
+ {{- end }}
+ secretName: {{ .secretName }}
+ {{- end }}
+ {{- end }}
+ rules:
+ {{- range .Values.ingress.hosts }}
+ - host: {{ .host | quote }}
+ http:
+ paths:
+ {{- range .paths }}
+ - path: {{ .path }}
+ {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
+ pathType: {{ .pathType }}
+ {{- end }}
+ backend:
+ {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
+ service:
+ name: {{ $fullName }}
+ port:
+ number: {{ $svcPort }}
+ {{- else }}
+ serviceName: {{ $fullName }}
+ servicePort: {{ $svcPort }}
+ {{- end }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/k8s/charts/eventuate-cdc/templates/service.yaml b/k8s/charts/eventuate-cdc/templates/service.yaml
new file mode 100644
index 0000000000..9bb9ec62af
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/templates/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "eventuate-cdc.fullname" . }}
+ labels:
+ {{- include "eventuate-cdc.labels" . | nindent 4 }}
+spec:
+ type: {{ .Values.service.type }}
+ ports:
+ - port: {{ .Values.service.port }}
+ targetPort: http
+ protocol: TCP
+ name: http
+ selector:
+ {{- include "eventuate-cdc.selectorLabels" . | nindent 4 }}
diff --git a/k8s/charts/eventuate-cdc/templates/serviceaccount.yaml b/k8s/charts/eventuate-cdc/templates/serviceaccount.yaml
new file mode 100644
index 0000000000..bea90b30bb
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/templates/serviceaccount.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ include "eventuate-cdc.serviceAccountName" . }}
+ labels:
+ {{- include "eventuate-cdc.labels" . | nindent 4 }}
+ {{- with .Values.serviceAccount.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+{{- end }}
diff --git a/k8s/charts/eventuate-cdc/templates/servicemonitoring.yaml b/k8s/charts/eventuate-cdc/templates/servicemonitoring.yaml
new file mode 100644
index 0000000000..12052c0050
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/templates/servicemonitoring.yaml
@@ -0,0 +1,13 @@
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+ name: {{ include "eventuate-cdc.fullname" . }}
+ labels:
+ release: prometheus
+spec:
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: {{ include "eventuate-cdc.fullname" . }}
+ endpoints:
+ - port: 'http'
+ path: '/actuator/prometheus'
\ No newline at end of file
diff --git a/k8s/charts/eventuate-cdc/templates/tests/test-connection.yaml b/k8s/charts/eventuate-cdc/templates/tests/test-connection.yaml
new file mode 100644
index 0000000000..ea95818fd5
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/templates/tests/test-connection.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: "{{ include "eventuate-cdc.fullname" . }}-test-connection"
+ labels:
+ {{- include "eventuate-cdc.labels" . | nindent 4 }}
+ annotations:
+ "helm.sh/hook": test
+spec:
+ containers:
+ - name: wget
+ image: busybox
+ command: ['wget']
+ args: ['{{ include "eventuate-cdc.fullname" . }}:{{ .Values.service.port }}']
+ restartPolicy: Never
diff --git a/k8s/charts/eventuate-cdc/values.yaml b/k8s/charts/eventuate-cdc/values.yaml
new file mode 100644
index 0000000000..c1f012e2a7
--- /dev/null
+++ b/k8s/charts/eventuate-cdc/values.yaml
@@ -0,0 +1,82 @@
+# Default values for eventuate-cdc.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+ repository: eventuateio/eventuate-cdc-service
+ pullPolicy: IfNotPresent
+ # Overrides the image tag whose default is the chart appVersion.
+ tag: "0.15.0.RELEASE"
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+serviceAccount:
+ # Specifies whether a service account should be created
+ create: true
+ # Annotations to add to the service account
+ annotations: {}
+ # The name of the service account to use.
+ # If not set and create is true, a name is generated using the fullname template
+ name: ""
+
+podAnnotations: {}
+
+podSecurityContext: {}
+ # fsGroup: 2000
+
+securityContext: {}
+ # capabilities:
+ # drop:
+ # - ALL
+ # readOnlyRootFilesystem: true
+ # runAsNonRoot: true
+ # runAsUser: 1000
+
+service:
+ type: ClusterIP
+ port: 8080
+
+ingress:
+ enabled: false
+ className: ""
+ annotations: {}
+ # kubernetes.io/ingress.class: nginx
+ # kubernetes.io/tls-acme: "true"
+ hosts:
+ - host: chart-example.local
+ paths:
+ - path: /
+ pathType: ImplementationSpecific
+ tls: []
+ # - secretName: chart-example-tls
+ # hosts:
+ # - chart-example.local
+
+resources: {}
+ # We usually recommend not to specify default resources and to leave this as a conscious
+ # choice for the user. This also increases chances charts run on environments with little
+ # resources, such as Minikube. If you do want to specify resources, uncomment the following
+ # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+ # limits:
+ # cpu: 100m
+ # memory: 128Mi
+ # requests:
+ # cpu: 100m
+ # memory: 128Mi
+
+autoscaling:
+ enabled: false
+ minReplicas: 1
+ maxReplicas: 100
+ targetCPUUtilizationPercentage: 80
+ # targetMemoryUtilizationPercentage: 80
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
diff --git a/k8s/charts/order/Chart.yaml b/k8s/charts/order/Chart.yaml
index 543c0b0c9c..e95d954fc8 100644
--- a/k8s/charts/order/Chart.yaml
+++ b/k8s/charts/order/Chart.yaml
@@ -15,7 +15,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 0.1.0
+version: 0.2.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
diff --git a/k8s/charts/order/values.yaml b/k8s/charts/order/values.yaml
index 5b73f36e93..6b72445297 100644
--- a/k8s/charts/order/values.yaml
+++ b/k8s/charts/order/values.yaml
@@ -10,3 +10,12 @@ backend:
enabled: true
host: api.yas.local.com
path: /order
+ extraVolumes:
+ - name: order-application-config
+ configMap:
+ name: order-application-configmap
+ extraVolumeMounts:
+ - name: order-application-config
+ mountPath: /opt/yas/order
+ extraApplicationConfigPaths:
+ - /opt/yas/order/order-application.yaml
\ No newline at end of file
diff --git a/k8s/charts/yas-configuration/Chart.yaml b/k8s/charts/yas-configuration/Chart.yaml
index 33097903d6..03075dc8c6 100644
--- a/k8s/charts/yas-configuration/Chart.yaml
+++ b/k8s/charts/yas-configuration/Chart.yaml
@@ -15,7 +15,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 0.1.0
+version: 0.2.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
diff --git a/k8s/charts/yas-configuration/templates/eventuate-cdc-env.secret.yaml b/k8s/charts/yas-configuration/templates/eventuate-cdc-env.secret.yaml
new file mode 100644
index 0000000000..5debccbf5e
--- /dev/null
+++ b/k8s/charts/yas-configuration/templates/eventuate-cdc-env.secret.yaml
@@ -0,0 +1,26 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: eventuate-cdc-env-secret
+ annotations:
+ reloader.stakater.com/match: "true"
+type: Opaque
+stringData:
+ CDC_OPTS: "--debug"
+ EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: "kafka-cluster-kafka-brokers.kafka:9092"
+ EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: "zookeeper.zookeeper:2181"
+ {{- range $index, $config := .Values.eventuateCdcConfig }}
+ EVENTUATE_CDC_READER_READER{{ $index }}_TYPE: "postgres-wal"
+ EVENTUATE_CDC_READER_READER{{ $index }}_DATASOURCEURL: "jdbc:postgresql://postgresql.postgres/{{ $config.database }}"
+ EVENTUATE_CDC_READER_READER{{ $index }}_MONITORINGSCHEMA: "eventuate"
+ EVENTUATE_CDC_READER_READER{{ $index }}_DATASOURCEUSERNAME: "{{ $.Values.credentials.postgresql.username }}"
+ EVENTUATE_CDC_READER_READER{{ $index }}_DATASOURCEPASSWORD: "{{ $.Values.credentials.postgresql.password }}"
+ EVENTUATE_CDC_READER_READER{{ $index }}_DATASOURCEDRIVERCLASSNAME: "org.postgresql.Driver"
+ EVENTUATE_CDC_READER_READER{{ $index }}_LEADERSHIPLOCKPATH: "/eventuate/cdc/leader/eventuate/{{ $config.database }}"
+ EVENTUATE_CDC_READER_READER{{ $index }}_OFFSETSTORAGETOPICNAME: "db.history.common"
+ EVENTUATE_CDC_READER_READER{{ $index }}_OUTBOXID: "{{ $index }}"
+ EVENTUATE_CDC_READER_READER{{ $index }}_POSTGRESREPLICATIONSLOTNAME: "eventuate_slot_{{ $config.database }}"
+ EVENTUATE_CDC_PIPELINE_PIPELINE{{ $index }}_TYPE: "eventuate-tram"
+ EVENTUATE_CDC_PIPELINE_PIPELINE{{ $index }}_READER: "reader{{ $index }}"
+ EVENTUATE_CDC_PIPELINE_PIPELINE{{ $index }}_EVENTUATEDATABASESCHEMA: "eventuate"
+ {{- end }}
\ No newline at end of file
diff --git a/k8s/charts/yas-configuration/templates/yas-configurations.configmap.yaml b/k8s/charts/yas-configuration/templates/yas-configurations.configmap.yaml
index 4443f73444..db34f91db2 100644
--- a/k8s/charts/yas-configuration/templates/yas-configurations.configmap.yaml
+++ b/k8s/charts/yas-configuration/templates/yas-configurations.configmap.yaml
@@ -44,7 +44,7 @@ data:
customer-application.yaml: |
{{ toYaml .Values.customerApplicationConfig | nindent 4 }}
---
-# Configmap of search application config custo
+# Configmap of search application config custom
apiVersion: v1
kind: ConfigMap
metadata:
@@ -53,4 +53,26 @@ metadata:
reloader.stakater.com/match: "true"
data:
search-application.yaml: |
- {{ toYaml .Values.searchApplicationConfig | nindent 4 }}
\ No newline at end of file
+ {{ toYaml .Values.searchApplicationConfig | nindent 4 }}
+---
+# Configmap of cart application config custom
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: cart-application-configmap
+ annotations:
+ reloader.stakater.com/match: "true"
+data:
+ cart-application.yaml: |
+ {{ toYaml .Values.cartApplicationConfig | nindent 4 }}
+---
+# Configmap of order application config custom
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: order-application-configmap
+ annotations:
+ reloader.stakater.com/match: "true"
+data:
+ order-application.yaml: |
+ {{ toYaml .Values.orderApplicationConfig | nindent 4 }}
\ No newline at end of file
diff --git a/k8s/charts/yas-configuration/values.yaml b/k8s/charts/yas-configuration/values.yaml
index 5ec52d2f99..0d8485a769 100644
--- a/k8s/charts/yas-configuration/values.yaml
+++ b/k8s/charts/yas-configuration/values.yaml
@@ -201,6 +201,30 @@ searchApplicationConfig:
username: ${ELASTICSEARCH_USERNAME}
password: ${ELASTICSEARCH_PASSWORD}
+# Cart application config custom
+cartApplicationConfig:
+ eventuatelocal:
+ kafka:
+ bootstrap:
+ servers: kafka-cluster-kafka-brokers.kafka:9092
+ zookeeper:
+ connection:
+ string: kafka-cluster-zookeeper-nodes.kafka:2181
+
+# Order application config custom
+orderApplicationConfig:
+ eventuatelocal:
+ kafka:
+ bootstrap:
+ servers: kafka-cluster-kafka-brokers.kafka:9092
+ zookeeper:
+ connection:
+ string: kafka-cluster-zookeeper-nodes.kafka:2181
+
+eventuateCdcConfig:
+ - database: order
+ - database: cart
+
logbackConfig: |
@@ -210,7 +234,7 @@ logbackConfig: |
-
+
diff --git a/k8s/deploy/deploy-yas-applications.sh b/k8s/deploy/deploy-yas-applications.sh
index 139fed0b17..106100c9f0 100644
--- a/k8s/deploy/deploy-yas-applications.sh
+++ b/k8s/deploy/deploy-yas-applications.sh
@@ -49,3 +49,6 @@ for chart in {"cart","customer","inventory","location","media","order","payment"
--set backend.ingress.host="api.$DOMAIN"
sleep 60
done
+
+helm upgrade --install eventuate-cdc ../charts/eventuate-cdc \
+--namespace yas --create-namespace
\ No newline at end of file
diff --git a/k8s/deploy/postgres/postgresql/templates/postgresql.yaml b/k8s/deploy/postgres/postgresql/templates/postgresql.yaml
index 954be7629d..960709e1b4 100644
--- a/k8s/deploy/postgres/postgresql/templates/postgresql.yaml
+++ b/k8s/deploy/postgres/postgresql/templates/postgresql.yaml
@@ -16,6 +16,9 @@ spec:
version: {{ .Values.postgresqlVersion | quote }}
parameters:
max_connections: {{ .Values.maxConnections | quote }}
+ wal_level: logical
+ max_replication_slots: "20"
+ max_wal_senders: "20"
numberOfInstances: {{ .Values.replicas }}
volume:
size: {{ .Values.volumeSize }}
@@ -41,8 +44,53 @@ spec:
rating: {{ .Values.username }}
tax: {{ .Values.username }}
grafana: {{ .Values.username }}
+ patroni:
+ slots:
+ eventuate_slot_cart:
+ type: logical
+ database: cart
+ plugin: wal2json
+ eventuate_slot_customer:
+ type: logical
+ database: customer
+ plugin: wal2json
+ eventuate_slot_inventory:
+ type: logical
+ database: inventory
+ plugin: wal2json
+ eventuate_slot_location:
+ type: logical
+ database: location
+ plugin: wal2json
+ eventuate_slot_media:
+ type: logical
+ database: media
+ plugin: wal2json
+ eventuate_slot_order:
+ type: logical
+ database: order
+ plugin: wal2json
+ eventuate_slot_payment:
+ type: logical
+ database: payment
+ plugin: wal2json
+ eventuate_slot_product:
+ type: logical
+ database: product
+ plugin: wal2json
+ eventuate_slot_promotion:
+ type: logical
+ database: promotion
+ plugin: wal2json
+ eventuate_slot_rating:
+ type: logical
+ database: rating
+ plugin: wal2json
+ eventuate_slot_tax:
+ type: logical
+ database: tax
+ plugin: wal2json
allowedSourceRanges:
# IP ranges to access your cluster go here
-
resources:
{{ toYaml .Values.resources | nindent 4 }}
\ No newline at end of file
diff --git a/k8s/deploy/setup-cluster.sh b/k8s/deploy/setup-cluster.sh
index 28c1359050..6fdc513b01 100644
--- a/k8s/deploy/setup-cluster.sh
+++ b/k8s/deploy/setup-cluster.sh
@@ -133,4 +133,7 @@ helm upgrade --install grafana ./observability/grafana \
--set grafana.username="$GRAFANA_USERNAME" \
--set grafana.password="$GRAFANA_PASSWORD" \
--set postgresql.username="$POSTGRESQL_USERNAME" \
---set postgresql.password="$POSTGRESQL_PASSWORD"
\ No newline at end of file
+--set postgresql.password="$POSTGRESQL_PASSWORD"
+
+helm upgrade --install zookeeper ./zookeeper \
+ --namespace zookeeper --create-namespace
\ No newline at end of file
diff --git a/k8s/deploy/zookeeper/.helmignore b/k8s/deploy/zookeeper/.helmignore
new file mode 100644
index 0000000000..0e8a0eb36f
--- /dev/null
+++ b/k8s/deploy/zookeeper/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/k8s/deploy/zookeeper/Chart.yaml b/k8s/deploy/zookeeper/Chart.yaml
new file mode 100644
index 0000000000..fa5cc184f6
--- /dev/null
+++ b/k8s/deploy/zookeeper/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: zookeeper
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "3.8.2"
diff --git a/k8s/deploy/zookeeper/templates/NOTES.txt b/k8s/deploy/zookeeper/templates/NOTES.txt
new file mode 100644
index 0000000000..c86c53e0b8
--- /dev/null
+++ b/k8s/deploy/zookeeper/templates/NOTES.txt
@@ -0,0 +1,22 @@
+1. Get the application URL by running these commands:
+{{- if .Values.ingress.enabled }}
+{{- range $host := .Values.ingress.hosts }}
+ {{- range .paths }}
+ http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
+ {{- end }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+ export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "zookeeper.fullname" . }})
+ export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+ echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+ NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+ You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "zookeeper.fullname" . }}'
+ export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "zookeeper.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+ echo http://$SERVICE_IP:{{ .Values.service.port }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+ export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "zookeeper.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+ export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
+ echo "Visit http://127.0.0.1:8080 to use your application"
+ kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
+{{- end }}
diff --git a/k8s/deploy/zookeeper/templates/_helpers.tpl b/k8s/deploy/zookeeper/templates/_helpers.tpl
new file mode 100644
index 0000000000..64d8ce47e9
--- /dev/null
+++ b/k8s/deploy/zookeeper/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "zookeeper.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "zookeeper.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "zookeeper.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "zookeeper.labels" -}}
+helm.sh/chart: {{ include "zookeeper.chart" . }}
+{{ include "zookeeper.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "zookeeper.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "zookeeper.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "zookeeper.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "zookeeper.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/k8s/deploy/zookeeper/templates/deployment.yaml b/k8s/deploy/zookeeper/templates/deployment.yaml
new file mode 100644
index 0000000000..fa546a5cc9
--- /dev/null
+++ b/k8s/deploy/zookeeper/templates/deployment.yaml
@@ -0,0 +1,82 @@
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: {{ include "zookeeper.fullname" . }}
+ labels:
+ {{- include "zookeeper.labels" . | nindent 4 }}
+spec:
+ serviceName: {{ include "zookeeper.fullname" . }}
+ {{- if not .Values.autoscaling.enabled }}
+ replicas: {{ .Values.replicaCount }}
+ {{- end }}
+ selector:
+ matchLabels:
+ {{- include "zookeeper.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ {{- with .Values.podAnnotations }}
+ annotations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ labels:
+ {{- include "zookeeper.selectorLabels" . | nindent 8 }}
+ spec:
+ {{- with .Values.imagePullSecrets }}
+ imagePullSecrets:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ serviceAccountName: {{ include "zookeeper.serviceAccountName" . }}
+ securityContext:
+ {{- toYaml .Values.podSecurityContext | nindent 8 }}
+ containers:
+ - name: {{ .Chart.Name }}
+ securityContext:
+ {{- toYaml .Values.securityContext | nindent 12 }}
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ volumeMounts:
+ - name: {{ include "zookeeper.fullname" . }}-persistent-storage
+ mountPath: /usr/local/zookeeper-data
+ env:
+ - name: ZOOKEEPER_CLIENT_PORT
+ value: "2181"
+ - name: KAFKA_HEAP_OPTS
+ value: -Xmx64m
+ ports:
+ - name: zkport
+ containerPort: 2181
+ protocol: TCP
+ livenessProbe:
+ exec:
+ command:
+ - bash
+ - -c
+ - "(echo ruok | nc localhost 2181) || exit 1"
+ readinessProbe:
+ exec:
+ command:
+ - bash
+ - -c
+ - "(echo ruok | nc localhost 2181) || exit 1"
+ resources:
+ {{- toYaml .Values.resources | nindent 12 }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.affinity }}
+ affinity:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.tolerations }}
+ tolerations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ volumeClaimTemplates:
+ - metadata:
+ name: {{ include "zookeeper.fullname" . }}-persistent-storage
+ spec:
+ accessModes: [ "ReadWriteOnce" ]
+ resources:
+ requests:
+ storage: 1Gi
\ No newline at end of file
diff --git a/k8s/deploy/zookeeper/templates/hpa.yaml b/k8s/deploy/zookeeper/templates/hpa.yaml
new file mode 100644
index 0000000000..df40aae3bb
--- /dev/null
+++ b/k8s/deploy/zookeeper/templates/hpa.yaml
@@ -0,0 +1,32 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: {{ include "zookeeper.fullname" . }}
+ labels:
+ {{- include "zookeeper.labels" . | nindent 4 }}
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: {{ include "zookeeper.fullname" . }}
+ minReplicas: {{ .Values.autoscaling.minReplicas }}
+ maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+ metrics:
+ {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+ {{- end }}
+ {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+ - type: Resource
+ resource:
+ name: memory
+ target:
+ type: Utilization
+ averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+ {{- end }}
+{{- end }}
diff --git a/k8s/deploy/zookeeper/templates/ingress.yaml b/k8s/deploy/zookeeper/templates/ingress.yaml
new file mode 100644
index 0000000000..751e1fb0ed
--- /dev/null
+++ b/k8s/deploy/zookeeper/templates/ingress.yaml
@@ -0,0 +1,61 @@
+{{- if .Values.ingress.enabled -}}
+{{- $fullName := include "zookeeper.fullname" . -}}
+{{- $svcPort := .Values.service.port -}}
+{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
+ {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
+ {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
+ {{- end }}
+{{- end }}
+{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1
+{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1beta1
+{{- else -}}
+apiVersion: extensions/v1beta1
+{{- end }}
+kind: Ingress
+metadata:
+ name: {{ $fullName }}
+ labels:
+ {{- include "zookeeper.labels" . | nindent 4 }}
+ {{- with .Values.ingress.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
+ ingressClassName: {{ .Values.ingress.className }}
+ {{- end }}
+ {{- if .Values.ingress.tls }}
+ tls:
+ {{- range .Values.ingress.tls }}
+ - hosts:
+ {{- range .hosts }}
+ - {{ . | quote }}
+ {{- end }}
+ secretName: {{ .secretName }}
+ {{- end }}
+ {{- end }}
+ rules:
+ {{- range .Values.ingress.hosts }}
+ - host: {{ .host | quote }}
+ http:
+ paths:
+ {{- range .paths }}
+ - path: {{ .path }}
+ {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
+ pathType: {{ .pathType }}
+ {{- end }}
+ backend:
+ {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
+ service:
+ name: {{ $fullName }}
+ port:
+ number: {{ $svcPort }}
+ {{- else }}
+ serviceName: {{ $fullName }}
+ servicePort: {{ $svcPort }}
+ {{- end }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/k8s/deploy/zookeeper/templates/service.yaml b/k8s/deploy/zookeeper/templates/service.yaml
new file mode 100644
index 0000000000..6fc4f81f1e
--- /dev/null
+++ b/k8s/deploy/zookeeper/templates/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "zookeeper.fullname" . }}
+ labels:
+ {{- include "zookeeper.labels" . | nindent 4 }}
+spec:
+ type: {{ .Values.service.type }}
+ ports:
+ - port: {{ .Values.service.port }}
+ targetPort: zkport
+ protocol: TCP
+ name: zkport
+ selector:
+ {{- include "zookeeper.selectorLabels" . | nindent 4 }}
diff --git a/k8s/deploy/zookeeper/templates/serviceaccount.yaml b/k8s/deploy/zookeeper/templates/serviceaccount.yaml
new file mode 100644
index 0000000000..aee12d9bcb
--- /dev/null
+++ b/k8s/deploy/zookeeper/templates/serviceaccount.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ include "zookeeper.serviceAccountName" . }}
+ labels:
+ {{- include "zookeeper.labels" . | nindent 4 }}
+ {{- with .Values.serviceAccount.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+{{- end }}
diff --git a/k8s/deploy/zookeeper/templates/tests/test-connection.yaml b/k8s/deploy/zookeeper/templates/tests/test-connection.yaml
new file mode 100644
index 0000000000..6bfda6d840
--- /dev/null
+++ b/k8s/deploy/zookeeper/templates/tests/test-connection.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: "{{ include "zookeeper.fullname" . }}-test-connection"
+ labels:
+ {{- include "zookeeper.labels" . | nindent 4 }}
+ annotations:
+ "helm.sh/hook": test
+spec:
+ containers:
+ - name: wget
+ image: busybox
+ command: ['wget']
+ args: ['{{ include "zookeeper.fullname" . }}:{{ .Values.service.port }}']
+ restartPolicy: Never
diff --git a/k8s/deploy/zookeeper/values.yaml b/k8s/deploy/zookeeper/values.yaml
new file mode 100644
index 0000000000..bdd6aacb84
--- /dev/null
+++ b/k8s/deploy/zookeeper/values.yaml
@@ -0,0 +1,82 @@
+# Default values for zookeeper.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+ repository: zookeeper
+ pullPolicy: IfNotPresent
+ # Overrides the image tag whose default is the chart appVersion.
+ tag: ""
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+serviceAccount:
+ # Specifies whether a service account should be created
+ create: true
+ # Annotations to add to the service account
+ annotations: {}
+ # The name of the service account to use.
+ # If not set and create is true, a name is generated using the fullname template
+ name: ""
+
+podAnnotations: {}
+
+podSecurityContext: {}
+ # fsGroup: 2000
+
+securityContext: {}
+ # capabilities:
+ # drop:
+ # - ALL
+ # readOnlyRootFilesystem: true
+ # runAsNonRoot: true
+ # runAsUser: 1000
+
+service:
+ type: ClusterIP
+ port: 2181
+
+ingress:
+ enabled: false
+ className: ""
+ annotations: {}
+ # kubernetes.io/ingress.class: nginx
+ # kubernetes.io/tls-acme: "true"
+ hosts:
+ - host: chart-example.local
+ paths:
+ - path: /
+ pathType: ImplementationSpecific
+ tls: []
+ # - secretName: chart-example-tls
+ # hosts:
+ # - chart-example.local
+
+resources: {}
+ # We usually recommend not to specify default resources and to leave this as a conscious
+ # choice for the user. This also increases chances charts run on environments with little
+ # resources, such as Minikube. If you do want to specify resources, uncomment the following
+ # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+ # limits:
+ # cpu: 100m
+ # memory: 128Mi
+ # requests:
+ # cpu: 100m
+ # memory: 128Mi
+
+autoscaling:
+ enabled: false
+ minReplicas: 1
+ maxReplicas: 100
+ targetCPUUtilizationPercentage: 80
+ # targetMemoryUtilizationPercentage: 80
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
diff --git a/order/pom.xml b/order/pom.xml
index a62d48b013..74673038d7 100644
--- a/order/pom.xml
+++ b/order/pom.xml
@@ -52,7 +52,6 @@
springdoc-openapi-starter-webmvc-ui
${springdoc-openapi-starter-webmvc-ui.version}
-
org.springframework.boot
spring-boot-starter-test
@@ -117,6 +116,11 @@
keycloak-spring-security-adapter
${keycloak-spring-security-adapter.version}
+
+ com.yas
+ saga
+ 0.0.1-SNAPSHOT
+
diff --git a/order/src/main/java/com/yas/order/controller/OrderController.java b/order/src/main/java/com/yas/order/controller/OrderController.java
index c8ebcdfd75..baf0e332b6 100644
--- a/order/src/main/java/com/yas/order/controller/OrderController.java
+++ b/order/src/main/java/com/yas/order/controller/OrderController.java
@@ -1,27 +1,31 @@
package com.yas.order.controller;
import com.yas.order.model.enumeration.EOrderStatus;
+import com.yas.order.service.OrderSagaService;
import com.yas.order.service.OrderService;
import com.yas.order.viewmodel.order.*;
import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.time.ZonedDateTime;
import java.util.List;
@RestController
+@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
- public OrderController(OrderService orderService) {
- this.orderService = orderService;
- }
+ private final OrderSagaService orderSagaService;
@PostMapping("/storefront/orders")
public ResponseEntity createOrder(@Valid @RequestBody OrderPostVm orderPostVm) {
- return ResponseEntity.ok(orderService.createOrder(orderPostVm));
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ return ResponseEntity.ok(orderSagaService.createOrder(orderPostVm, auth.getName()));
}
@GetMapping("/storefront/orders/completed")
diff --git a/order/src/main/java/com/yas/order/saga/CreateOrderSaga.java b/order/src/main/java/com/yas/order/saga/CreateOrderSaga.java
new file mode 100644
index 0000000000..f72201e369
--- /dev/null
+++ b/order/src/main/java/com/yas/order/saga/CreateOrderSaga.java
@@ -0,0 +1,60 @@
+package com.yas.order.saga;
+
+import com.yas.order.saga.data.CreateOrderSagaData;
+import com.yas.order.service.CartService;
+import com.yas.order.service.OrderService;
+import com.yas.order.viewmodel.order.OrderItemPostVm;
+import com.yas.order.viewmodel.order.OrderPostVm;
+import com.yas.order.viewmodel.order.OrderVm;
+import com.yas.saga.cart.reply.DeleteCartItemFailure;
+import com.yas.saga.cart.reply.DeleteCartItemSuccess;
+import io.eventuate.tram.commands.consumer.CommandWithDestination;
+import io.eventuate.tram.sagas.orchestration.SagaDefinition;
+import io.eventuate.tram.sagas.simpledsl.SimpleSaga;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class CreateOrderSaga implements SimpleSaga {
+
+ private final OrderService orderService;
+ private final CartService cartService;
+
+ private final SagaDefinition sagaDefinition =
+ step()
+ .invokeLocal(this::createOrder)
+ .withCompensation(this::compensateOrder)
+ .step()
+ .invokeParticipant(this::deleteCartItem)
+ .onReply(DeleteCartItemSuccess.class, (data, reply) -> log.info(reply.getMessage()))
+ .onReply(DeleteCartItemFailure.class, (data, reply) -> log.warn(reply.getMessage()))
+ .build();
+
+ @Override
+ public SagaDefinition getSagaDefinition() {
+ return this.sagaDefinition;
+ }
+
+ private void createOrder(CreateOrderSagaData data) {
+ OrderVm orderVm = this.orderService.createOrder(data.getOrderPostVm());
+ data.setOrderVm(orderVm);
+ }
+
+ private CommandWithDestination deleteCartItem(CreateOrderSagaData data) {
+ OrderPostVm orderPostVm = data.getOrderPostVm();
+ List productIds = orderPostVm.orderItemPostVms()
+ .stream()
+ .map(OrderItemPostVm::productId)
+ .toList();
+ return this.cartService.deleteCartItem(productIds, data.getCustomerId());
+ }
+
+ private void compensateOrder(CreateOrderSagaData data) {
+ log.info("Compensate Order");
+ }
+}
diff --git a/order/src/main/java/com/yas/order/saga/data/CreateOrderSagaData.java b/order/src/main/java/com/yas/order/saga/data/CreateOrderSagaData.java
new file mode 100644
index 0000000000..7c2a7f88a8
--- /dev/null
+++ b/order/src/main/java/com/yas/order/saga/data/CreateOrderSagaData.java
@@ -0,0 +1,18 @@
+package com.yas.order.saga.data;
+
+import com.yas.order.viewmodel.order.OrderPostVm;
+import com.yas.order.viewmodel.order.OrderVm;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class CreateOrderSagaData {
+ private OrderPostVm orderPostVm;
+ private OrderVm orderVm;
+ private String customerId;
+}
diff --git a/order/src/main/java/com/yas/order/service/CartService.java b/order/src/main/java/com/yas/order/service/CartService.java
index 5f800c5e79..68445f2d74 100644
--- a/order/src/main/java/com/yas/order/service/CartService.java
+++ b/order/src/main/java/com/yas/order/service/CartService.java
@@ -1,48 +1,22 @@
package com.yas.order.service;
-import com.yas.order.config.ServiceUrlConfig;
-import com.yas.order.exception.BadRequestException;
-import com.yas.order.exception.NotFoundException;
-import com.yas.order.viewmodel.ResponeStatusVm;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.oauth2.jwt.Jwt;
+import com.yas.saga.cart.command.DeleteCartItemCommand;
+import io.eventuate.tram.commands.consumer.CommandWithDestination;
import org.springframework.stereotype.Service;
-import org.springframework.web.reactive.function.client.WebClient;
-import org.springframework.web.util.UriComponentsBuilder;
-import java.net.URI;
+
import java.util.List;
+import static io.eventuate.tram.commands.consumer.CommandWithDestinationBuilder.send;
+
@Service
public class CartService {
- private final WebClient webClient;
- private final ServiceUrlConfig serviceUrlConfig;
-
- public CartService(WebClient webClient, ServiceUrlConfig serviceUrlConfig) {
- this.webClient = webClient;
- this.serviceUrlConfig = serviceUrlConfig;
- }
-
- public Void deleteCartItemByProductId(List productIds) {
- final String jwt = ((Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getTokenValue();
- final URI url = UriComponentsBuilder
- .fromHttpUrl(serviceUrlConfig.cart())
- .path("/storefront/cart-item/multi-delete")
- .queryParam("productIds", productIds)
- .buildAndExpand()
- .toUri();
- return webClient.delete()
- .uri(url)
- .headers(h->h.setBearerAuth(jwt))
- .retrieve()
- .onStatus(
- HttpStatus.BAD_REQUEST::equals,
- response -> response.bodyToMono(String.class).map(BadRequestException::new))
- .onStatus(
- HttpStatus.INTERNAL_SERVER_ERROR::equals,
- response -> response.bodyToMono(String.class).map(BadRequestException::new))
- .bodyToMono(Void.class)
- .block();
+ public CommandWithDestination deleteCartItem(List productIds, String customerId) {
+ var deleteCartItemCommand = DeleteCartItemCommand.builder()
+ .customerId(customerId)
+ .productIds(productIds)
+ .build();
+ return send(deleteCartItemCommand)
+ .to("cartService")
+ .build();
}
}
\ No newline at end of file
diff --git a/order/src/main/java/com/yas/order/service/OrderSagaService.java b/order/src/main/java/com/yas/order/service/OrderSagaService.java
new file mode 100644
index 0000000000..69775f6d10
--- /dev/null
+++ b/order/src/main/java/com/yas/order/service/OrderSagaService.java
@@ -0,0 +1,28 @@
+package com.yas.order.service;
+
+import com.yas.order.saga.CreateOrderSaga;
+import com.yas.order.saga.data.CreateOrderSagaData;
+import com.yas.order.viewmodel.order.OrderPostVm;
+import com.yas.order.viewmodel.order.OrderVm;
+import io.eventuate.tram.sagas.orchestration.SagaInstanceFactory;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class OrderSagaService {
+
+ private final SagaInstanceFactory sagaInstanceFactory;
+
+ private final CreateOrderSaga createOrderSaga;
+
+ public OrderVm createOrder(OrderPostVm orderPostVm, String customerId) {
+
+ CreateOrderSagaData data = CreateOrderSagaData.builder()
+ .customerId(customerId)
+ .orderPostVm(orderPostVm)
+ .build();
+ sagaInstanceFactory.create(createOrderSaga, data);
+ return data.getOrderVm();
+ }
+}
diff --git a/order/src/main/java/com/yas/order/service/OrderService.java b/order/src/main/java/com/yas/order/service/OrderService.java
index 8d6b88f8ae..30b4a4262e 100644
--- a/order/src/main/java/com/yas/order/service/OrderService.java
+++ b/order/src/main/java/com/yas/order/service/OrderService.java
@@ -1,7 +1,6 @@
package com.yas.order.service;
import com.yas.order.exception.NotFoundException;
-import com.yas.order.model.Checkout;
import com.yas.order.model.Order;
import com.yas.order.model.OrderAddress;
import com.yas.order.model.OrderItem;
@@ -21,13 +20,15 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
-import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.time.ZonedDateTime;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@@ -38,7 +39,6 @@ public class OrderService {
private final CheckoutRepository checkoutRepository;
private final OrderRepository orderRepository;
private final OrderItemRepository orderItemRepository;
- private final CartService cartService;
private final ProductService productService;
public OrderVm createOrder(OrderPostVm orderPostVm) {
@@ -114,13 +114,6 @@ public OrderVm createOrder(OrderPostVm orderPostVm) {
//setOrderItems so that we able to return order with orderItems
order.setOrderItems(orderItems);
- // delete Item in Cart
- try {
- cartService.deleteCartItemByProductId(orderItems.stream().map(i -> i.getProductId()).toList());
- } catch (Exception ex) {
- log.error("Delete products in cart fail: " + ex.getMessage());
- }
-
// TO-DO: decrement inventory when inventory is complete
// ************
diff --git a/order/src/main/resources/application.properties b/order/src/main/resources/application.properties
index d75125aaa6..77a9003f10 100644
--- a/order/src/main/resources/application.properties
+++ b/order/src/main/resources/application.properties
@@ -8,6 +8,11 @@ management.endpoints.web.exposure.include=prometheus
management.metrics.distribution.percentiles-histogram.http.server.requests=true
management.metrics.tags.application=${spring.application.name}
+eventuatelocal.kafka.bootstrap.servers=kafka:9092
+eventuatelocal.zookeeper.connection.string=zookeeper:2181
+eventuate.database.schema=eventuate
+spring.liquibase.parameters.eventualSlotName=order
+
logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://identity/realms/Yas
@@ -28,6 +33,9 @@ spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialec
# Hibernate ddl auto (none, create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = none
+# Disable open in view transaction
+spring.jpa.open-in-view=false
+
#Enable liquibase
spring.liquibase.enabled=true
diff --git a/order/src/main/resources/db/changelog/db.changelog-master.yaml b/order/src/main/resources/db/changelog/db.changelog-master.yaml
index 419ad29d64..d9ef4f57ba 100644
--- a/order/src/main/resources/db/changelog/db.changelog-master.yaml
+++ b/order/src/main/resources/db/changelog/db.changelog-master.yaml
@@ -2,4 +2,6 @@ databaseChangeLog:
- includeAll:
path: db/changelog/ddl/
- includeAll:
- path: db/changelog/data/
\ No newline at end of file
+ path: db/changelog/data/
+ - includeAll:
+ path: db/changelog/eventuate-dll/
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000..ba3056b475
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+ com.yas
+ yas
+ 0.0.1-SNAPSHOT
+ pom
+ yas
+
+
+ cart
+
+ order
+
+ saga
+
+
+
\ No newline at end of file
diff --git a/saga/.gitignore b/saga/.gitignore
new file mode 100644
index 0000000000..549e00a2a9
--- /dev/null
+++ b/saga/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/saga/.mvn/wrapper/maven-wrapper.jar b/saga/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000000..cb28b0e37c
Binary files /dev/null and b/saga/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/saga/.mvn/wrapper/maven-wrapper.properties b/saga/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000000..462686e25d
--- /dev/null
+++ b/saga/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/saga/mvnw b/saga/mvnw
new file mode 100755
index 0000000000..66df285428
--- /dev/null
+++ b/saga/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/saga/mvnw.cmd b/saga/mvnw.cmd
new file mode 100644
index 0000000000..95ba6f54ac
--- /dev/null
+++ b/saga/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/saga/pom.xml b/saga/pom.xml
new file mode 100644
index 0000000000..bde99273f3
--- /dev/null
+++ b/saga/pom.xml
@@ -0,0 +1,81 @@
+
+
+ 4.0.0
+ com.yas
+ saga
+ 0.0.1-SNAPSHOT
+ saga
+ Saga model common yas project
+
+ 17
+ 17
+ 17
+ 1.18.26
+ 1.4.1.Final
+ 2023.0.RELEASE
+
+
+
+ org.projectlombok
+ lombok
+ ${org.lombok.version}
+ true
+
+
+ io.eventuate.tram.core
+ eventuate-tram-spring-optimistic-locking
+
+
+ io.eventuate.tram.core
+ eventuate-tram-spring-jdbc-kafka
+
+
+ io.eventuate.tram.sagas
+ eventuate-tram-sagas-spring-orchestration-simple-dsl-starter
+
+
+ io.eventuate.tram.sagas
+ eventuate-tram-sagas-spring-participant-starter
+
+
+
+
+
+ io.eventuate.platform
+ eventuate-platform-dependencies
+ ${eventuate-platform.version}
+ pom
+ import
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+ org.projectlombok
+ lombok
+ ${org.lombok.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+
+
+
+
+
diff --git a/saga/src/main/java/com/yas/saga/SagaConfiguration.java b/saga/src/main/java/com/yas/saga/SagaConfiguration.java
new file mode 100644
index 0000000000..1c28ba68ea
--- /dev/null
+++ b/saga/src/main/java/com/yas/saga/SagaConfiguration.java
@@ -0,0 +1,10 @@
+package com.yas.saga;
+
+import io.eventuate.tram.spring.optimisticlocking.OptimisticLockingDecoratorConfiguration;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+@Configuration
+@Import(OptimisticLockingDecoratorConfiguration.class)
+public class SagaConfiguration {
+}
diff --git a/saga/src/main/java/com/yas/saga/cart/command/DeleteCartItemCommand.java b/saga/src/main/java/com/yas/saga/cart/command/DeleteCartItemCommand.java
new file mode 100644
index 0000000000..39749a7b66
--- /dev/null
+++ b/saga/src/main/java/com/yas/saga/cart/command/DeleteCartItemCommand.java
@@ -0,0 +1,18 @@
+package com.yas.saga.cart.command;
+
+import io.eventuate.tram.commands.common.Command;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class DeleteCartItemCommand implements Command {
+ private List productIds;
+ private String customerId;
+}
diff --git a/saga/src/main/java/com/yas/saga/cart/reply/DeleteCartItemFailure.java b/saga/src/main/java/com/yas/saga/cart/reply/DeleteCartItemFailure.java
new file mode 100644
index 0000000000..cf30d3ba65
--- /dev/null
+++ b/saga/src/main/java/com/yas/saga/cart/reply/DeleteCartItemFailure.java
@@ -0,0 +1,14 @@
+package com.yas.saga.cart.reply;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class DeleteCartItemFailure {
+ private String message;
+}
diff --git a/saga/src/main/java/com/yas/saga/cart/reply/DeleteCartItemSuccess.java b/saga/src/main/java/com/yas/saga/cart/reply/DeleteCartItemSuccess.java
new file mode 100644
index 0000000000..aa3960778d
--- /dev/null
+++ b/saga/src/main/java/com/yas/saga/cart/reply/DeleteCartItemSuccess.java
@@ -0,0 +1,14 @@
+package com.yas.saga.cart.reply;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class DeleteCartItemSuccess {
+ private String message;
+}
diff --git a/saga/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/saga/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000000..dc2956999a
--- /dev/null
+++ b/saga/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,11 @@
+com.yas.saga.SagaConfiguration
+io.eventuate.tram.sagas.spring.participant.autoconfigure.SpringParticipantAutoConfiguration
+io.eventuate.tram.sagas.spring.orchestration.autoconfigure.SpringOrchestratorSimpleDslAutoConfiguration
+io.eventuate.tram.spring.messaging.autoconfigure.EventuateTramActiveMQMessageConsumerAutoConfiguration
+io.eventuate.tram.spring.messaging.autoconfigure.EventuateTramKafkaMessageConsumerAutoConfiguration
+io.eventuate.tram.spring.messaging.autoconfigure.EventuateTramRabbitMQMessageConsumerAutoConfiguration
+io.eventuate.tram.spring.messaging.autoconfigure.EventuateTramRedisMessageConsumerAutoConfiguration
+io.eventuate.tram.spring.messaging.autoconfigure.TramMessageProducerJdbcAutoConfiguration
+io.eventuate.tram.spring.messaging.common.TramMessagingCommonAutoConfiguration
+io.eventuate.tram.spring.commands.common.TramCommandsCommonAutoConfiguration
+io.eventuate.tram.spring.consumer.jdbc.TramConsumerJdbcAutoConfiguration
\ No newline at end of file
diff --git a/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0001.sql b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0001.sql
new file mode 100644
index 0000000000..f727fcd602
--- /dev/null
+++ b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0001.sql
@@ -0,0 +1,52 @@
+--liquibase formatted sql
+--changeset bangnguyen:eventuate-dll-0001
+--comment: init eventuate tables
+
+CREATE SCHEMA eventuate;
+
+DROP TABLE IF EXISTS eventuate.events CASCADE;
+DROP TABLE IF EXISTS eventuate.entities CASCADE;
+DROP TABLE IF EXISTS eventuate.snapshots CASCADE;
+DROP TABLE IF EXISTS eventuate.cdc_monitoring CASCADE;
+
+CREATE TABLE eventuate.events
+(
+ event_id VARCHAR(1000) PRIMARY KEY,
+ event_type VARCHAR(1000),
+ event_data VARCHAR(1000) NOT NULL,
+ entity_type VARCHAR(1000) NOT NULL,
+ entity_id VARCHAR(1000) NOT NULL,
+ triggering_event VARCHAR(1000),
+ metadata VARCHAR(1000),
+ published SMALLINT DEFAULT 0
+);
+
+CREATE INDEX events_idx ON eventuate.events (entity_type, entity_id, event_id);
+CREATE INDEX events_published_idx ON eventuate.events (published, event_id);
+
+CREATE TABLE eventuate.entities
+(
+ entity_type VARCHAR(1000),
+ entity_id VARCHAR(1000),
+ entity_version VARCHAR(1000) NOT NULL,
+ PRIMARY KEY (entity_type, entity_id)
+);
+
+CREATE INDEX entities_idx ON eventuate.entities (entity_type, entity_id);
+
+CREATE TABLE eventuate.snapshots
+(
+ entity_type VARCHAR(1000),
+ entity_id VARCHAR(1000),
+ entity_version VARCHAR(1000),
+ snapshot_type VARCHAR(1000) NOT NULL,
+ snapshot_json VARCHAR(1000) NOT NULL,
+ triggering_events VARCHAR(1000),
+ PRIMARY KEY (entity_type, entity_id, entity_version)
+);
+
+CREATE TABLE eventuate.cdc_monitoring
+(
+ reader_id VARCHAR(1000) PRIMARY KEY,
+ last_time BIGINT
+);
\ No newline at end of file
diff --git a/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0002.sql b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0002.sql
new file mode 100644
index 0000000000..d3c6cce717
--- /dev/null
+++ b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0002.sql
@@ -0,0 +1,34 @@
+--liquibase formatted sql
+--changeset bangnguyen:eventuate-dll-0002
+--comment: init eventuate tables
+
+DROP TABLE IF EXISTS eventuate.message CASCADE;
+DROP TABLE IF EXISTS eventuate.received_messages CASCADE;
+
+CREATE TABLE eventuate.message
+(
+ id VARCHAR(1000) PRIMARY KEY,
+ destination TEXT NOT NULL,
+ headers TEXT NOT NULL,
+ payload TEXT NOT NULL,
+ published SMALLINT DEFAULT 0,
+ message_partition SMALLINT,
+ creation_time BIGINT
+);
+
+CREATE INDEX message_published_idx ON eventuate.message (published, id);
+
+CREATE TABLE eventuate.received_messages
+(
+ consumer_id VARCHAR(1000),
+ message_id VARCHAR(1000),
+ creation_time BIGINT,
+ published SMALLINT DEFAULT 0,
+ PRIMARY KEY (consumer_id, message_id)
+);
+
+CREATE TABLE eventuate.offset_store
+(
+ client_name VARCHAR(255) NOT NULL PRIMARY KEY,
+ serialized_offset VARCHAR(255)
+);
\ No newline at end of file
diff --git a/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0003.sql b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0003.sql
new file mode 100644
index 0000000000..34479d1b7e
--- /dev/null
+++ b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0003.sql
@@ -0,0 +1,9 @@
+--liquibase formatted sql
+--changeset bangnguyen:eventuate-dll-0003
+--comment: init eventuate tables
+
+ALTER TABLE eventuate.message DROP COLUMN payload;
+ALTER TABLE eventuate.message ADD COLUMN payload JSON;
+
+ALTER TABLE eventuate.message DROP COLUMN headers;
+ALTER TABLE eventuate.message ADD COLUMN headers JSON;
\ No newline at end of file
diff --git a/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0004.sql b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0004.sql
new file mode 100644
index 0000000000..e0f89e18a9
--- /dev/null
+++ b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0004.sql
@@ -0,0 +1,71 @@
+--liquibase formatted sql
+--changeset bangnguyen:eventuate-dll-0004
+--comment: init eventuate tables
+
+CREATE SEQUENCE eventuate.message_table_id_sequence START 1;
+
+select setval('eventuate.message_table_id_sequence', (ROUND(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000))::BIGINT);
+
+CREATE TABLE eventuate.new_message
+(
+ dbid BIGINT NOT NULL DEFAULT nextval('eventuate.message_table_id_sequence') PRIMARY KEY,
+ id VARCHAR(1000),
+ destination TEXT NOT NULL,
+ headers TEXT NOT NULL,
+ payload TEXT NOT NULL,
+ published SMALLINT DEFAULT 0,
+ message_partition SMALLINT,
+ creation_time BIGINT
+);
+
+ALTER SEQUENCE eventuate.message_table_id_sequence OWNED BY eventuate.new_message.dbid;
+
+INSERT INTO eventuate.new_message (id, destination, headers, payload, published, message_partition, creation_time)
+SELECT id, destination, headers, payload, published, message_partition, creation_time
+FROM eventuate.message
+ORDER BY id;
+
+DROP TABLE eventuate.message;
+
+ALTER TABLE eventuate.new_message
+ RENAME TO message;
+
+CREATE SEQUENCE eventuate.events_table_id_sequence START 1;
+
+select setval('eventuate.events_table_id_sequence', (ROUND(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000))::BIGINT);
+
+CREATE TABLE eventuate.new_events
+(
+ id BIGINT NOT NULL DEFAULT nextval('eventuate.events_table_id_sequence') PRIMARY KEY,
+ event_id VARCHAR(1000),
+ event_type VARCHAR(1000),
+ event_data VARCHAR(1000) NOT NULL,
+ entity_type VARCHAR(1000) NOT NULL,
+ entity_id VARCHAR(1000) NOT NULL,
+ triggering_event VARCHAR(1000),
+ metadata VARCHAR(1000),
+ published SMALLINT DEFAULT 0
+);
+
+ALTER SEQUENCE eventuate.events_table_id_sequence OWNED BY eventuate.new_events.id;
+
+INSERT INTO eventuate.new_events (event_id, event_type, event_data, entity_type, entity_id, triggering_event, metadata,
+ published)
+SELECT event_id,
+ event_type,
+ event_data,
+ entity_type,
+ entity_id,
+ triggering_event,
+ metadata,
+ published
+FROM eventuate.events
+ORDER BY event_id;
+
+DROP TABLE eventuate.events;
+
+ALTER TABLE eventuate.new_events
+ RENAME TO events;
+
+CREATE INDEX events_idx ON eventuate.events (entity_type, entity_id, id);
+CREATE INDEX events_published_idx ON eventuate.events (published, id);
\ No newline at end of file
diff --git a/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0005.sql b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0005.sql
new file mode 100644
index 0000000000..4fda04ba3d
--- /dev/null
+++ b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0005.sql
@@ -0,0 +1,48 @@
+--liquibase formatted sql
+--changeset bangnguyen:eventuate-dll-0006
+--comment: init eventuate tables
+
+DROP Table IF Exists eventuate.saga_instance_participants;
+DROP Table IF Exists eventuate.saga_instance;
+DROP Table IF Exists eventuate.saga_lock_table;
+DROP Table IF Exists eventuate.saga_stash_table;
+
+CREATE TABLE eventuate.saga_instance_participants
+(
+ saga_type VARCHAR(255) NOT NULL,
+ saga_id VARCHAR(100) NOT NULL,
+ destination VARCHAR(100) NOT NULL,
+ resource VARCHAR(100) NOT NULL,
+ PRIMARY KEY (saga_type, saga_id, destination, resource)
+);
+
+CREATE TABLE eventuate.saga_instance
+(
+ saga_type VARCHAR(255) NOT NULL,
+ saga_id VARCHAR(100) NOT NULL,
+ state_name VARCHAR(100) NOT NULL,
+ last_request_id VARCHAR(100),
+ end_state BOOLEAN,
+ compensating BOOLEAN,
+ failed BOOLEAN,
+ saga_data_type TEXT NOT NULL,
+ saga_data_json TEXT NOT NULL,
+ PRIMARY KEY (saga_type, saga_id)
+);
+
+create table eventuate.saga_lock_table
+(
+ target VARCHAR(100) PRIMARY KEY,
+ saga_type VARCHAR(255) NOT NULL,
+ saga_Id VARCHAR(100) NOT NULL
+);
+
+create table eventuate.saga_stash_table
+(
+ message_id VARCHAR(100) PRIMARY KEY,
+ target VARCHAR(100) NOT NULL,
+ saga_type VARCHAR(255) NOT NULL,
+ saga_id VARCHAR(100) NOT NULL,
+ message_headers VARCHAR(1000) NOT NULL,
+ message_payload VARCHAR(1000) NOT NULL
+);
\ No newline at end of file
diff --git a/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0006.sql b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0006.sql
new file mode 100644
index 0000000000..2d63bc4b6d
--- /dev/null
+++ b/saga/src/main/resources/db/changelog/eventuate-dll/changelog-0006.sql
@@ -0,0 +1,13 @@
+--liquibase formatted sql
+--changeset bangnguyen:eventuate-dll-0006
+--comment: create replication slot
+DO
+ '
+ DECLARE
+ eventuate_slot VARCHAR := ''eventuate_slot_${eventualSlotName}'';
+ BEGIN
+ IF NOT EXISTS (SELECT * FROM pg_replication_slots WHERE slot_name = eventuate_slot) THEN
+ PERFORM pg_create_logical_replication_slot(eventuate_slot, ''wal2json'');
+ END IF;
+ END
+ ';
\ No newline at end of file