A microservices-based bear ordering application built with Node.js, TypeScript, Express, Kafka/Amazon SQS, and MySQL.
┌─────────────────────────────────────────────────────────────────────────────────┐
│ USER │
│ (Web Browser) │
└─────────────────────────────────┬───────────────────────────────────────────────┘
│ HTTP
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ ORDER SERVICE (Port 3000) │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────────────┐ │
│ │ Static UI │ │ REST API │ │ Messaging Client │ │
│ │ (Storefront) │ │ POST /order │ │ - Publish: order-created │ │
│ │ │ │ GET /order/:id │ │ - Subscribe: order-updates │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────────────────────┘ │
└───────────┬─────────────────────┬───────────────────────┬───────────────────────┘
│ │ │
│ Save/Read Orders │ │ Publish: order-created
▼ │ ▼
┌───────────────────────┐ │ ┌─────────────────────────────────────┐
│ │ │ │ │
│ MySQL Database │ │ │ KAFKA / SQS │
│ │ │ │ (Message Broker) │
│ ┌─────────────────┐ │ │ │ │
│ │ orders table │ │ │ │ Topics: │
│ │ - order_id │ │ │ │ ├─ order-created ─────────────┐ │
│ │ - status │ │ │ │ │ │ │
│ │ - paid │ │ │ │ └─ order-updates ◄────────────┤ │
│ │ - stock │ │ │ │ │ │
│ │ - items (JSON) │ │ │ └────────────┬───────────────────┤────┘
│ │ - total │ │ │ │
│ └─────────────────┘ │ │ │
│ │ ▼ ▼
└───────────┬───────────┘ ┌─────────────────────┐ ┌─────────────────────┐
│ │ INVENTORY SERVICE │ │ PAYMENT SERVICE │
│ │ (Port 3002) │ │ (Port 3003) │
│ │ │ │ │
│ Read Order Items │ - Subscribe: │ │ - Subscribe: │
└──────────────────────────┤ order-created │ │ order-created │
│ │ │ │
│ - Check inventory │ │ - Process payment │
│ - Deduct stock │ │ (simulated) │
│ │ │ │
│ - Publish: │ │ - Publish: │
│ STOCK-OK or │ │ PAID │
│ STOCK-FAILED │ │ │
│ → order-updates │ │ → order-updates │
└─────────────────────┘ └─────────────────────┘
┌──────┐ ┌───────────────┐ ┌─────────┐ ┌───────────┐ ┌───────────────┐ ┌─────────────────┐
│ User │ │ Order Service │ │ MySQL │ │Kafka/SQS │ │Inventory Svc │ │ Payment Service │
└──┬───┘ └───────┬───────┘ └────┬────┘ └─────┬─────┘ └───────┬───────┘ └────────┬────────┘
│ │ │ │ │ │
│ 1. POST /order │ │ │ │ │
│ ────────────────► │ │ │ │
│ │ │ │ │ │
│ │ 2. Save Order │ │ │ │
│ │ ─────────────────► │ │ │
│ │ │ │ │ │
│ │ 3. Publish │ │ │ │
│ │ order-created │ │ │ │
│ │ ──────────────────────────────────► │ │
│ │ │ │ │ │
│ 4. Response │ │ │ │ │
│ ◄──────────────── │ │ │ │
│ (orderId) │ │ │ │ │
│ │ │ │ 5a. Deliver │ │
│ │ │ │ ───────────────────► │
│ │ │ │ │ │
│ │ │ │ 5b. Deliver │ │
│ │ │ │ ─────────────────────────────────────────►
│ │ │ │ │ │
│ │ │ 6. Read Items │ │ │
│ │ │ ◄─────────────────────────────────── │
│ │ │ │ │ │
│ │ │ │ │ 7a. STOCK-OK │
│ │ │ │ ◄─────────────────── │
│ │ │ │ │ │
│ │ │ │ │ 7b. PAID │
│ │ │ │ ◄─────────────────────────────────────────
│ │ │ │ │ │
│ │ 8. Receive │ │ │ │
│ │ order-updates │ │ │ │
│ │ ◄────────────────────────────────── │ │
│ │ │ │ │ │
│ │ 9. Update Order │ │ │ │
│ │ Status │ │ │ │
│ │ ─────────────────► │ │ │
│ │ │ │ │ │
│ 10. Poll Status │ │ │ │ │
│ ────────────────► │ │ │ │
│ │ │ │ │ │
│ 11. FINISHED │ │ │ │ │
│ ◄──────────────── │ │ │ │
│ │ │ │ │ │
The application consists of three microservices:
- Order Service (Port 3000): Handles order creation and status tracking
- Inventory Service (Port 3002): Manages inventory levels and stock updates
- Payment Service (Port 3003): Processes payments via messaging (Kafka or SQS)
The application supports two messaging backends:
- Kafka (default): Self-hosted Kafka for local development and GKE
- Amazon SQS: AWS-managed message queue service
You can switch between them using the MESSAGING_TYPE environment variable (kafka or sqs). See SQS Setup Guide for detailed AWS SQS configuration instructions.
- Node.js 20+
- Docker Desktop with Kubernetes enabled (or minikube/kind)
- kubectl configured
- GCP project with billing enabled
- GKE cluster created
- gcloud CLI installed and authenticated
- kubectl configured to use your GKE cluster
./install-dependencies.shThis installs npm dependencies for all services.
Start MySQL and Kafka in Kubernetes:
./start-dependencies-k8s.shThis script:
- Deploys MySQL and Kafka to your Kubernetes cluster
- Sets up port-forwards for local access:
- MySQL:
localhost:3306 - Kafka:
localhost:9092
- MySQL:
Note: Keep this terminal running to maintain port-forwards. Open a new terminal for the next steps.
Kafka Configuration:
- Kafka is deployed as a StatefulSet (not Deployment) for persistent storage
- Uses
apache/kafka:4.1.1image with KRaft mode (no Zookeeper) - Service name:
kafka(headless service for direct pod access) - Persistent storage: 1Gi PVC per pod
- Service discovery:
kafka:9092(same namespace) orkafka.bear-app.svc.cluster.local:9092(cross-namespace)
By default, services use Kafka. To use SQS instead:
export MESSAGING_TYPE=sqs
export AWS_REGION=us-east-1
export AWS_SQS_ORDER_CREATED_QUEUE_URL=https://sqs.us-east-1.amazonaws.com/ACCOUNT_ID/order-created-queue
export AWS_SQS_ORDER_UPDATES_QUEUE_URL=https://sqs.us-east-1.amazonaws.com/ACCOUNT_ID/order-updates-queueSee SQS_SETUP.md for detailed setup instructions.
In separate terminals, start each service:
# Terminal 1: Order Service
cd order-service
npm run dev
# Terminal 2: Inventory Service
cd inventory-service
npm run dev
# Terminal 3: Payment Service
cd payment-service
npm run dev- Order Service UI: http://localhost:3000
- Order Service API: http://localhost:3000/order
- Inventory Service API: http://localhost:3002/inventory
Stop dependencies:
./stop-dependencies-k8s.shFor GKE deployments, you can choose between Kafka and SQS:
Using Kafka (Default):
- Kafka is deployed as part of the base Kubernetes manifests
- No additional configuration needed
Using SQS:
- Follow the SQS Setup Guide to create queues and IAM roles
- Update
k8s/overlays/gke/kustomization.yaml:configMapGenerator: - name: bear-app-config literals: - MESSAGING_TYPE=sqs - AWS_REGION=us-east-1 - AWS_SQS_ORDER_CREATED_QUEUE_URL=https://sqs.us-east-1.amazonaws.com/ACCOUNT/order-created-queue - AWS_SQS_ORDER_UPDATES_QUEUE_URL=https://sqs.us-east-1.amazonaws.com/ACCOUNT/order-updates-queue
- Configure AWS credentials (see SQS_SETUP.md for options)
- Deploy and restart services
Set your GCP project:
gcloud config set project YOUR_PROJECT_IDConnect kubectl to your GKE cluster:
gcloud container clusters get-credentials CLUSTER_NAME --region REGION --project YOUR_PROJECT_IDEdit k8s/overlays/gke/kustomization.yaml and update the image references with your project ID:
images:
- name: order-service
newName: gcr.io/YOUR_PROJECT_ID/order-service
newTag: latest
- name: payment-service
newName: gcr.io/YOUR_PROJECT_ID/payment-service
newTag: latest
- name: inventory-service
newName: gcr.io/YOUR_PROJECT_ID/inventory-service
newTag: latest./deploy-to-k8s.sh gkeThis script:
- Builds Docker images for all services
- Pushes images to Google Container Registry
- Deploys all services and dependencies to GKE
- Configures LoadBalancer for order-service
Your deployments need authentication to pull images from GCR. You have two options:
This is the simplest approach - configure it once and forget about it:
./scripts/configure-gke-node-service-account.sh CLUSTER_NAME REGIONThen edit k8s/overlays/gke/kustomization.yaml and switch to no-secret mode:
patchesStrategicMerge:
# Comment out:
# - image-pull-secret-patch.yaml
# Uncomment:
- no-image-pull-secret-patch.yamlApply: kubectl apply -k k8s/overlays/gke
Benefits: No secrets to manage, more secure, works forever without maintenance.
If you prefer using secrets, the deploy script will automatically create the gcr-json-key secret if it doesn't exist. You can also create/refresh it manually:
./scripts/create-gke-image-pull-secret.shNote: The deploy script (./deploy-to-k8s.sh gke) will automatically check and create the secret if missing when using Option 2.
If you see "ImagePullBackOff" errors, either:
- Run
configure-gke-node-service-account.shand switch to Option 1, OR - Run
create-gke-image-pull-secret.shto refresh the secret (Option 2)
Get the external IP:
kubectl get service order-service -n bear-appAccess the application at: http://EXTERNAL_IP
Image Pull Errors:
If your pods fail with ImagePullBackOff or ErrImagePull errors:
Option 1: Using Node Service Account (Recommended)
- Configure node service account permissions:
./scripts/configure-gke-node-service-account.sh CLUSTER_NAME REGION
- Switch to no-secret mode in
k8s/overlays/gke/kustomization.yaml - Apply and restart:
kubectl apply -k k8s/overlays/gke kubectl rollout restart deployment -n bear-app
Option 2: Using Image Pull Secrets
- Verify the image pull secret exists:
kubectl get secret gcr-json-key -n bear-app
- If missing or expired, refresh it:
./scripts/create-gke-image-pull-secret.sh
- Restart the deployments:
kubectl rollout restart deployment -n bear-app
Check pod status:
kubectl get pods -n bear-app
kubectl describe pod <pod-name> -n bear-appkubectl delete namespace bear-appArgoCD provides GitOps-based continuous deployment for your bear-app. With ArgoCD, you can manage deployments through Git commits, with automatic synchronization and a web UI for monitoring.
- Kubernetes cluster (GKE or local)
- kubectl configured and connected to your cluster
- Git repository (your code is already in Git)
Install ArgoCD in your cluster:
./scripts/install-argocd.shThis script:
- Creates the
argocdnamespace - Installs ArgoCD components (server, application controller, repo server)
- Displays the initial admin password
Port-forward to access the ArgoCD UI:
kubectl port-forward svc/argocd-server -n argocd 8080:443Then open your browser to: https://localhost:8080
- Username:
admin - Password: (shown in the installation output, or get it with):
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d && echo
For GKE: You can expose ArgoCD via LoadBalancer:
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "LoadBalancer"}}'Then get the external IP:
kubectl get svc argocd-server -n argocdCreate ArgoCD Applications for your environments:
# Create Applications for both environments
./scripts/setup-argocd-apps.sh
# Or create for specific environment
./scripts/setup-argocd-apps.sh local
./scripts/setup-argocd-apps.sh gkeThis creates two Applications:
- bear-app-local: Points to
k8s/overlays/local(auto-sync enabled) - bear-app-gke: Points to
k8s/overlays/gke(auto-sync enabled, self-heal disabled)
Once Applications are created, ArgoCD will automatically sync from Git (if auto-sync is enabled).
Workflow:
- Make changes to your Kubernetes manifests in Git
- Commit and push to Git
- ArgoCD detects the changes and syncs to your cluster
- Monitor sync status in the ArgoCD UI
Manual Sync (if auto-sync is disabled):
- Via UI: Click "Sync" button in the Application
- Via CLI:
argocd app sync bear-app-gke - Via kubectl:
kubectl patch app bear-app-gke -n argocd --type merge -p '{"operation":{"initiatedBy":{"username":"admin"},"sync":{"revision":"master"}}}'
- Local Application: Auto-sync enabled with self-heal (good for development)
- GKE Application: Auto-sync enabled but self-heal disabled (safer for production)
You can modify sync policies in k8s/argocd/applications/*.yaml.
Deployments are synchronized in waves for proper ordering:
- Wave 0: Infrastructure (MySQL, Kafka StatefulSet)
- Wave 1: Dependencies (Kafka UI)
- Wave 2: Microservices (order-service, inventory-service, payment-service)
Note: Kafka is deployed as a StatefulSet (not Deployment) for persistent storage and stable network identities.
- Git as Single Source of Truth: All configuration in Git
- Automatic Synchronization: Changes in Git are automatically deployed
- Visual Monitoring: Web UI shows application health and sync status
- Easy Rollbacks: Revert Git commits to roll back deployments
- Audit Trail: All changes tracked through Git history
- Multi-Environment Management: Manage local and GKE from the same codebase
Current workflow (manual):
- Build and push images to GCR
- Update image tags in
k8s/overlays/gke/kustomization.yaml - Commit and push to Git
- ArgoCD automatically syncs the changes
-
Check Application status:
kubectl get applications -n argocd kubectl describe app bear-app-gke -n argocd
-
View sync logs in ArgoCD UI or via CLI:
argocd app get bear-app-gke
-
Check ArgoCD pods:
kubectl get pods -n argocd
-
Repository connection issues: Ensure your Git repository is accessible (public or credentials configured)
To remove ArgoCD:
kubectl delete namespace argocdNote: This will also remove all ArgoCD Applications. Your deployed resources will remain, but ArgoCD will no longer manage them.
GET /order/health- Health checkPOST /order- Create a new order{ "items": [ {"id": "cub", "name": "Cub", "price": 15.0, "qty": 1} ], "total": 15.0 }GET /order/:id- Get order statusGET /inventory- Proxy to inventory service
GET /inventory/health- Health checkGET /inventory- Get current inventory levels
To run unit tests locally:
# Unit tests for each service
cd order-service && npm test
cd inventory-service && npm test
cd payment-service && npm testSee SQS_SETUP.md for detailed SQS setup and testing procedures.
bear-app/
├── order-service/ # Order management service
├── inventory-service/ # Inventory management service
├── payment-service/ # Payment processing service
├── k8s/ # Kubernetes manifests
│ ├── base/ # Base configurations
│ ├── overlays/ # Environment-specific configs
│ │ ├── local/ # Local Kubernetes
│ │ └── gke/ # Google Kubernetes Engine
│ └── argocd/ # ArgoCD Application manifests
│ └── applications/ # Application definitions
├── scripts/ # Deployment scripts
│ ├── install-argocd.sh # Install ArgoCD
│ └── setup-argocd-apps.sh # Bootstrap ArgoCD Applications
├── install-dependencies.sh # Install all dependencies
├── start-dependencies-k8s.sh # Start dependencies in k8s
├── stop-dependencies-k8s.sh # Stop dependencies
├── deploy-to-k8s.sh # Deploy to Kubernetes (manual)
└── README.md # This file
Kafka is deployed as a StatefulSet (not Deployment) for the following benefits:
- Persistent Storage: Data survives pod restarts via PersistentVolumeClaims (1Gi per pod)
- Stable Network Identity: Pods have predictable names (
kafka-0,kafka-1, etc.) - Ordered Scaling: StatefulSets support controlled, ordered scaling
- Better for Stateful Workloads: Ideal for Kafka which requires stable storage and network identity
- Service Name:
kafka(changed fromkafka-service) - Service Type: Headless (
clusterIP: None) for direct pod-to-pod communication - Port: 9092 (controller port 9093 handled internally)
- Image:
apache/kafka:4.1.1(KRaft mode, no Zookeeper required)
Services connect to Kafka using:
- Same namespace:
kafka:9092 - Cross-namespace:
kafka.bear-app.svc.cluster.local:9092 - Direct pod access:
kafka-0.kafka.bear-app.svc.cluster.local:9092
- Storage Class:
standard-rwo(ReadWriteOnce) - Size: 1Gi per pod
- Persistence: Data persists across pod restarts and deletions
- Location:
/var/lib/kafka/datain the container
Kafka pod not starting:
# Check pod status
kubectl get pods -n bear-app -l app=kafka
# Check logs
kubectl logs -n bear-app kafka-0
# Check StatefulSet
kubectl describe statefulset kafka -n bear-app
# Check PVC
kubectl get pvc -n bear-app -l app=kafkaServices can't connect to Kafka:
# Verify service exists and is headless
kubectl get svc kafka -n bear-app
# Check service endpoints
kubectl get endpoints kafka -n bear-app
# Test DNS resolution from a pod
kubectl run -it --rm --restart=Never test-dns --image=busybox:1.36 -- nslookup kafka.bear-app.svc.cluster.local
# Verify ConfigMap has correct KAFKA_BROKER
kubectl get configmap bear-app-config -n bear-app -o yaml | grep KAFKA_BROKERKafka data persistence:
# Restart Kafka pod to test persistence
kubectl delete pod kafka-0 -n bear-app
# Wait for pod to restart
kubectl wait --for=condition=ready pod/kafka-0 -n bear-app --timeout=300s
# Verify pod restarted and data persists
kubectl logs -n bear-app kafka-0 --tail=10- Ensure dependencies are running:
kubectl get pods -n bear-app - Check port-forwards:
ps aux | grep "kubectl port-forward" - Verify MySQL and Kafka are accessible:
- MySQL:
mysql -h localhost -P 3306 -u root -ppassword -e "SELECT 1" - Kafka: Check logs with
kubectl logs -f statefulset/kafka -n bear-app - Kafka pod:
kubectl get pods -n bear-app -l app=kafka
- MySQL:
- Check pod status:
kubectl get pods -n bear-app - View logs:
kubectl logs -f deployment/order-service -n bear-app - Verify images are pushed:
gcloud container images list --project=YOUR_PROJECT_ID - Check service configuration:
kubectl describe service order-service -n bear-app
- Check ArgoCD Applications:
kubectl get applications -n argocd - View Application details:
kubectl describe app bear-app-gke -n argocd - Check ArgoCD pods:
kubectl get pods -n argocd - View ArgoCD server logs:
kubectl logs -f deployment/argocd-server -n argocd - Verify Git repository connection in ArgoCD UI
MIT