Processing Unified Live Streaming Events
Repository containing the application code for PULSE, an event-driven microservices platform for real-time transaction fraud risk assessment. Transactions are submitted via a REST API, scored by a rules-based processor, and persisted to a query store — all asynchronously through a message queue.
- Docker and Docker Compose (included with Docker Desktop)
- JDK 21
- Maven 3.9+
- kubectl (Kubernetes only)
- Kustomize (Kubernetes only)
- kind or minikube (Kubernetes only)
- pre-commit
pulse/
|-- .github/
| `-- workflows/
| |-- ci.yml // Lint and test on pull requests
| `-- cd.yml // Build and push images to Artifact Registry on merge to main
|-- keycloak/
| `-- pulse-realm.json // Keycloak realm config, auto-imported on startup
|-- kubernetes/
| |-- base/ // Shared Kubernetes manifests
| | |-- submitter/
| | |-- processor/
| | |-- projector/
| | `-- otel-collector/
| |-- overlays/
| | |-- local/ // Local overlay: in-cluster Postgres, Redis, Pub/Sub emulator
| | `-- pro/ // Production overlay: GCP Cloud SQL, Memorystore, Pub/Sub
|-- pulse-app/ // Maven multi-module project
| |-- common/
| | `-- model/ // Shared event models and topic definitions
| `-- services/
| |-- submitter/ // REST API for transaction submission (port 8080)
| |-- processor/ // Fraud risk scoring engine (event-driven, no public API)
| `-- projector/ // Query API and PostgreSQL persistence (port 8083)
|-- .pre-commit-config.yaml
|-- docker-compose.yml // Full local stack
`-- README.md // This file
This repository uses pre-commit to run KTLint formatting checks before committing changes. To install the hooks, run the following command in the root of the repository:
pre-commit installTo run the checks manually on all files:
pre-commit run --all-filesDocker Compose is the recommended way to run the full stack locally. It starts all three services along with Kafka, PostgreSQL, Redis, Keycloak, and Jaeger in a single command.
docker-compose up --buildThe first build compiles all services from source. Subsequent runs use the Docker layer cache. To run in the background, add -d. Keycloak takes around 90 seconds to become healthy on first start.
Once everything is running:
| Service | URL |
|---|---|
| Submitter API | http://localhost:8080 |
| Submitter Swagger UI | http://localhost:8080/swagger-ui.html |
| Projector API | http://localhost:8083 |
| Projector Swagger UI | http://localhost:8083/swagger-ui.html |
| Kafka UI | http://localhost:8081 |
| Keycloak admin console | http://localhost:8180 (admin / admin) |
| Jaeger UI | http://localhost:16686 |
To stop and remove containers:
docker-compose downTo also remove volumes (wipes database data):
docker-compose down -vThe local overlay runs on a local Kubernetes cluster and replaces all GCP-managed services with in-cluster equivalents (Pub/Sub emulator, PostgreSQL, Redis). Note that this overlay uses Pub/Sub for messaging rather than Kafka.
1. Build the service images
The local overlay sets imagePullPolicy: Never, so images must be present in your cluster's image store before deploying.
For Docker Desktop (locally-built images are available automatically):
docker build -f pulse-app/services/submitter/Dockerfile -t submitter:latest pulse-app
docker build -f pulse-app/services/processor/Dockerfile -t processor:latest pulse-app
docker build -f pulse-app/services/projector/Dockerfile -t projector:latest pulse-appFor kind (images must be loaded explicitly after building):
kind load docker-image submitter:latest
kind load docker-image processor:latest
kind load docker-image projector:latestFor minikube (build inside minikube's Docker daemon):
eval $(minikube docker-env)
docker build -f pulse-app/services/submitter/Dockerfile -t submitter:latest pulse-app
docker build -f pulse-app/services/processor/Dockerfile -t processor:latest pulse-app
docker build -f pulse-app/services/projector/Dockerfile -t projector:latest pulse-app2. Configure OAuth2
The local overlay requires an OAuth2 issuer. Fill in your provider (e.g. a standalone Keycloak instance) in kubernetes/overlays/local/config.env before deploying:
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://<keycloak-host>/realms/pulse
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI=http://<keycloak-host>/realms/pulse/protocol/openid-connect/certs3. Apply the manifests
kubectl apply -k kubernetes/overlays/local4. Verify the deployment
kubectl get pods -n pulseServices use startup probes with a 5-minute window. Once pods are Running, use kubectl port-forward to reach the APIs:
kubectl port-forward -n pulse svc/submitter 8080:8080
kubectl port-forward -n pulse svc/projector 8083:8083To tear down:
kubectl delete -k kubernetes/overlays/localTo build all modules:
mvn -f pulse-app/pom.xml clean packageTo run all tests (requires Docker for integration tests via Testcontainers):
mvn -f pulse-app/pom.xml verifyTo run linting only (KTLint and Detekt, skipping tests):
mvn -f pulse-app/pom.xml verify -Dmaven.test.skip=trueTo build a single service:
mvn -f pulse-app/services/submitter/pom.xml -am clean packageOn pull requests to main, the CI pipeline runs linting and all tests. On merge to main, the CD pipeline builds Docker images for all three services and pushes them to GCP Artifact Registry, tagged with both the commit SHA and latest.
The production Kubernetes overlay in kubernetes/overlays/pro/ is deployed to GKE and uses GCP-managed services (Cloud SQL, Pub/Sub, Memorystore, Cloud Trace, Identity Platform) in place of the local equivalents. Infrastructure for the production environment is managed separately in pulse-infra/.