COFFEE HOUSE SPRING BOOT MICROSERVICES
- Three microservices to user to mimic the operation of a coffee house
- Coffee inventory service - holds information about inventory levels.
- Rest API used to get inventory information
- JMS messaging used to increase inventory from brewing, decrease inventory froom order allocation Coffee Inventory Failover Service - Service to provide a positive response if inventory service is not avaible.
- Coffee service - holds information about Coffee brewed, Rest API for Coffee CRUD opearations.
- JMS messaging to brew Coffee when inventory is low
- Scheduled job to brew Coffee
- Coffee order service - Holds information about veer orders
- RestAPI for Coffee order Operations
- JMS Messaging to allocate Coffee orders
- Tasting room service - sheduled job to order Coffee, which decreases inventory, which triggers brewing. Important to understand Coffee Order Service will generate orders, request allocations from inventory (reducing inventory) which triggers Coffee service to brew Coffee (adding to inventory)
- Coffee inventory service - holds information about inventory levels.
- Data is pesisted to postgreSQL via JPA/Hibernate
- JMS is used for messaging between services
In this example we're looking at four microservices that worked in orchestration. We can view coffee, add coffee, delete coffee, get coffee inventory, brew new coffee, and we can order coffee
Work order:
order-service
is generating orders (scheduled job to order coffee)- This is decrease inventory in
inventory-service
ifinventory-service
is fail, then we useinventory-failover
- If inventory is low, then coffee-service is brew new coffee and adding it to inventory
-
brewing-request - coffee-service send message every 5 second itself listen message.
If we have inventory on hand more than min needed, then we send message to brew that coffee.
-
new-inventory - coffee-service send, inventory-service listen.
After coffee-service brew new coffee it sends message to inventory-service to increase amount of this coffee.
-
validate-order - order-service send, coffee-service listen.
order-service create new order and send it to coffee-service to validate it. If all coffee in order founded by upc, then order is validate.
-
validate-order-result - coffee-service send, order-service listen.
If the order verification is successful, the coffee-service sends the result back to the order-service.
-
allocate-order - order-service send, inventory-service listen.
Performs the shipment of coffee to the customer by order
-
allocate-order-result - inventory-service send, order-service listen.
Get result about successfully allocation.
-
allocation-failure - inventory-service send, order-service listen.
Get result about failure allocation.
-
deallocate-order - order-service send, but have not any listener.
Needed to fix this
- coffee-service check the current inventory on hand for all coffee (brewingService.checkForLowInventory()).
- coffee-service Send rest api request to inventory-service, and get quantity on hand.
- coffee-service if the inventory on hand <= min inventory, coffee-service send message to brew more coffee in queue brewing-request
- coffee-service listen queue (BrewCoffeeListener. After get that message coffee-service begin brew coffee, increase quantity.
- coffee-service send new quantity in queue new-inventory
- inventory-service listen queue new-inventory in NewInventoryListener.listen(). Get new inventory object (coffee id with quantity) and save it in db (table coffee_inventory)
- order-service create new order with random coffee. tastingRoomService.createTastingRoomOrder(). Set order status = NEW
- order-service send event to state machine. CoffeeOrderManagerImpl.sendCoffeeOrderEvent()
- order-service state machine start event action in ValidateCoffeeOrder.execute() and send order to queue validate-order
- coffee-service CoffeeOrderValidationListener.listen() listen queue validate-order and send message to queue validate-order-result
- order-service listen queue validate-order-result (CoffeeOrderValidationResultListener.listen()) change order status from PENDING_VALIDATION to VALIDATED.
- order-service state machine start event action ALLOCATE_ORDER (AllocateCoffeeOrder.execute()) and send message (order) to queue allocate-order
- inventory-service listen queue allocate-order in AllocationListener.listen() and check whether there are enough quantity to allocate an order or not and send result to queue allocate-order-result
- order-service state machine set status ALLOCATED and save order to DB.
- java 17
- Docker
- Kubernetes
- Maven 4.0.0
- Open
./docker
folder and run commandsh createDockerImages.sh
- Try to use
GET http://localhost:9090/api/v1/coffee
- Install kubectl and minikube, and start it
minikube start
- Check node
kubectl get nodes
and contextkubectl config get-contexts
should be "minikube" - Run
minikube dashboard
and dashboard will open automatically in browser - Apply that commands:
kubectl apply -f postgres-deployment.yml kubectl apply -f postgres-service.yml kubectl apply -f jms-deployment.yml kubectl apply -f jms-service.yml kubectl apply -f inventory-deployment.yml kubectl apply -f inventory-service.yml kubectl apply -f inventory-failover-deployment.yml kubectl apply -f inventory-failover-service.yml kubectl apply -f coffee-service-deployment.yml kubectl apply -f coffee-service.yml kubectl apply -f order-service-deployment.yml kubectl apply -f order-service.yml
- Open kibana
http://localhost:5601
- Add dashboard for stream with name "filebeat". Kibana discover this stream automatically
Filebeat collect console logs from every services and shipping it to elastic. Kibana check elastic and get all logs
Services | port | debug port |
---|---|---|
coffee-service | 8080 | 9080 |
inventory-service | 8082 | 9082 |
inventory-failover | 8083 | 9083 |
order-service | 8081 | 9081 |
gateway | 9090 | 9099 |
- Eureka - is not used. Kubernetes is used for service discovery
- Spring cloud config - Not used. Kubernetes used to manage environment properties.
- Single PostgreSQL instance - One instance used. Just for simplicity, a production deployment should habe independent PostgreSQL database instances.
- Single github repo - Again for simplicity. Microservices typically would have independent soource code repository
- Discovery. If service more than one, they should discover each other.
- Fault tolerance. Resistance to crashes of one service. Fault tolerance - this is when one service crashes, resilience is when an atomic bomb falls on a data center
- Resilience. Resistance to the fall of the entire data center
- Configuration. Applications become ephemeral. They appear quickly and disappear quickly, and you need to configure them on the fly
- API Managment. When there are a lot of applications, they need to be managed. More precisely, applications expose the API to the outside and they need to be managed.
- Discovery - Eureka
- Fault tolerance - Spring Cloud Circuit breaker
- Resilience - Spring Cloud Load balancing
- Configuration - Spring cloud Config server
- API managment - Spring Cloud gateway
- Discovery - K8S
- Fault tolerance - Services + liveness / readiness
- Resilience - istio (Service Mesh Pattern)
- Configuration - ConfigMap + Secrets
- API managment - istio (Service Mesh Pattern)
-
Postgres
Create and apply deployment
kubectl create deployment postgres --image=postgres --dry-run=client -o=yaml > postgres-deployment.yml
Open file postgres-deployment.yml and add env block.
kubectl apply -f postgres-deployment.yml
Create and apply service
kubectl create service clusterip postgres --tcp=5432:5432 --dry-run=client -o=yaml > postgres-service.yml
kubectl apply -f postgres-service.yml
-
JMS
kubectl create deployment jms --image=vromero/activemq-artemis --dry-run=client -o=yaml > jms-deployment.yml
kubectl apply -f jms-deployment.yml
kubectl create service clusterip jms --tcp=8161:8161 --tcp=61616:61616 --dry-run=client -o=yaml > jms-service.yml
kubectl apply -f jms-service.yml
-
Inventory-service
kubectl create deployment inventory-service --image=hexhoc/kbe-coffeehouse-inventory-service --dry-run=client -o=yaml > inventory-deployment.yml
kubectl apply -f inventory-deployment.yml
kubectl create service clusterip inventory-service --tcp=8082:8082 --dry-run=client -o=yaml > inventory-service.yml
kubectl apply -f inventory-service.yml
-
Inventory-failover-service
kubectl create deployment inventory-failover --image=hexhoc/kbe-coffeehouse-inventory-failover --dry-run=client -o=yaml > inventory-failover-deployment.yml
kubectl apply -f inventory-failover-deployment.yml
kubectl create service clusterip inventory-failover --tcp=8083:8083 --dry-run=client -o=yaml > inventory-failover-service.yml
kubectl apply -f inventory-failover-service.yml
-
coffee-service
kubectl create deployment coffee-service --image=hexhoc/kbe-coffeehouse-coffee-service --dry-run=client -o=yaml > coffee-service-deployment.yml
kubectl apply -f coffee-service-deployment.yml
kubectl create service clusterip coffee-service --tcp=8080:8080 --tcp=9080:9080 --dry-run=client -o=yaml > coffee-service.yml
kubectl apply -f coffee-service.yml
-
order-service
kubectl create deployment order-service --image=hexhoc/kbe-coffeehouse-order-service --dry-run=client -o=yaml > order-service-deployment.yml
kubectl apply -f order-service-deployment.yml
kubectl create service clusterip order-service --tcp=8081:8081 --tcp=9081:9081 --dry-run=client -o=yaml > order-service.yml
kubectl apply -f order-service.yml
-
Check all deployment and services
kubectl get all
-
Add readiness and liveness check.
Each service depends on the other and must wait for all dependent services before starting. For that we are use kubernetes readiness and liveness block
add to each service properties file this:
management.endpoint.health.probes.enabled=true management.health.readinessstate.enabled=true management.health.livenessstate.enabled=true
add this block in each deployment:
readinessProbe: httpGet: port: 8080 path: /actuator/health/readiness livenessProbe: httpGet: port: 8080 path: /actuator/health/liveness
-
Add gracefull shutdown for every service
add to each service properties file this:
server.shutdown=graceful
add this block in each deployment:
lifecycle: preStop: exec: command: ["sh", "-c", "sleep 10"]