diff --git a/.dockerignore b/.dockerignore index 2eea525d..5da80745 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ -.env \ No newline at end of file +.env +rabbitmq_enabled_plugins \ No newline at end of file diff --git a/.github/workflows/package-ai-service.yaml b/.github/workflows/package-ai-service.yaml index 7d0d2ec2..7eb1b493 100644 --- a/.github/workflows/package-ai-service.yaml +++ b/.github/workflows/package-ai-service.yaml @@ -9,6 +9,10 @@ on: workflow_dispatch: +permissions: + contents: read + packages: write + jobs: publish-container-image: @@ -18,7 +22,7 @@ jobs: - name: Set environment variables id: set-variables run: | - echo "REPOSITORY=ghcr.io/azure-samples/aks-store-demo" >> "$GITHUB_OUTPUT" + echo "REPOSITORY=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" echo "IMAGE=ai-service" >> "$GITHUB_OUTPUT" echo "VERSION=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" echo "CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" @@ -41,8 +45,8 @@ jobs: uses: docker/login-action@v1 with: registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.CR_PAT }} + username: ${{ github.repository_owner }} + password: ${{ github.token }} - name: Build and push uses: docker/build-push-action@v2 diff --git a/.github/workflows/package-makeline-service.yaml b/.github/workflows/package-makeline-service.yaml index 1f06950b..a4bb7790 100644 --- a/.github/workflows/package-makeline-service.yaml +++ b/.github/workflows/package-makeline-service.yaml @@ -9,6 +9,10 @@ on: workflow_dispatch: +permissions: + contents: read + packages: write + jobs: publish-container-image: @@ -18,7 +22,7 @@ jobs: - name: Set environment variables id: set-variables run: | - echo "REPOSITORY=ghcr.io/azure-samples/aks-store-demo" >> "$GITHUB_OUTPUT" + echo "REPOSITORY=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" echo "IMAGE=makeline-service" >> "$GITHUB_OUTPUT" echo "VERSION=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" echo "CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" @@ -41,8 +45,8 @@ jobs: uses: docker/login-action@v1 with: registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.CR_PAT }} + username: ${{ github.repository_owner }} + password: ${{ github.token }} - name: Build and push uses: docker/build-push-action@v2 diff --git a/.github/workflows/package-order-service.yaml b/.github/workflows/package-order-service.yaml index 8a9b81bb..8ff8b43b 100644 --- a/.github/workflows/package-order-service.yaml +++ b/.github/workflows/package-order-service.yaml @@ -9,6 +9,10 @@ on: workflow_dispatch: +permissions: + contents: read + packages: write + jobs: publish-container-image: @@ -18,7 +22,7 @@ jobs: - name: Set environment variables id: set-variables run: | - echo "REPOSITORY=ghcr.io/azure-samples/aks-store-demo" >> "$GITHUB_OUTPUT" + echo "REPOSITORY=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" echo "IMAGE=order-service" >> "$GITHUB_OUTPUT" echo "VERSION=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" echo "CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" @@ -41,8 +45,8 @@ jobs: uses: docker/login-action@v1 with: registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.CR_PAT }} + username: ${{ github.repository_owner }} + password: ${{ github.token }} - name: Build and push uses: docker/build-push-action@v2 diff --git a/.github/workflows/package-product-service.yaml b/.github/workflows/package-product-service.yaml index c1a82021..20137739 100644 --- a/.github/workflows/package-product-service.yaml +++ b/.github/workflows/package-product-service.yaml @@ -9,6 +9,10 @@ on: workflow_dispatch: +permissions: + contents: read + packages: write + jobs: publish-container-image: @@ -18,7 +22,7 @@ jobs: - name: Set environment variables id: set-variables run: | - echo "REPOSITORY=ghcr.io/azure-samples/aks-store-demo" >> "$GITHUB_OUTPUT" + echo "REPOSITORY=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" echo "IMAGE=product-service" >> "$GITHUB_OUTPUT" echo "VERSION=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" echo "CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" @@ -41,8 +45,8 @@ jobs: uses: docker/login-action@v1 with: registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.CR_PAT }} + username: ${{ github.repository_owner }} + password: ${{ github.token }} - name: Build and push uses: docker/build-push-action@v2 diff --git a/.github/workflows/package-store-admin.yaml b/.github/workflows/package-store-admin.yaml index 559cd59d..c95b6c8b 100644 --- a/.github/workflows/package-store-admin.yaml +++ b/.github/workflows/package-store-admin.yaml @@ -9,6 +9,10 @@ on: workflow_dispatch: +permissions: + contents: read + packages: write + jobs: publish-container-image: @@ -18,7 +22,7 @@ jobs: - name: Set environment variables id: set-variables run: | - echo "REPOSITORY=ghcr.io/azure-samples/aks-store-demo" >> "$GITHUB_OUTPUT" + echo "REPOSITORY=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" echo "IMAGE=store-admin" >> "$GITHUB_OUTPUT" echo "VERSION=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" echo "CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" @@ -41,8 +45,8 @@ jobs: uses: docker/login-action@v1 with: registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.CR_PAT }} + username: ${{ github.repository_owner }} + password: ${{ github.token }} - name: Build and push uses: docker/build-push-action@v2 diff --git a/.github/workflows/package-store-front.yaml b/.github/workflows/package-store-front.yaml index 30b1237d..f39a61d0 100644 --- a/.github/workflows/package-store-front.yaml +++ b/.github/workflows/package-store-front.yaml @@ -9,6 +9,10 @@ on: workflow_dispatch: +permissions: + contents: read + packages: write + jobs: publish-container-image: @@ -18,7 +22,7 @@ jobs: - name: Set environment variables id: set-variables run: | - echo "REPOSITORY=ghcr.io/azure-samples/aks-store-demo" >> "$GITHUB_OUTPUT" + echo "REPOSITORY=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" echo "IMAGE=store-front" >> "$GITHUB_OUTPUT" echo "VERSION=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" echo "CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" @@ -41,8 +45,8 @@ jobs: uses: docker/login-action@v1 with: registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.CR_PAT }} + username: ${{ github.repository_owner }} + password: ${{ github.token }} - name: Build and push uses: docker/build-push-action@v2 diff --git a/.github/workflows/package-virtual-customer.yaml b/.github/workflows/package-virtual-customer.yaml index 00fd6f49..ffcca3db 100644 --- a/.github/workflows/package-virtual-customer.yaml +++ b/.github/workflows/package-virtual-customer.yaml @@ -9,6 +9,10 @@ on: workflow_dispatch: +permissions: + contents: read + packages: write + jobs: publish-container-image: @@ -18,7 +22,7 @@ jobs: - name: Set environment variables id: set-variables run: | - echo "REPOSITORY=ghcr.io/azure-samples/aks-store-demo" >> "$GITHUB_OUTPUT" + echo "REPOSITORY=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" echo "IMAGE=virtual-customer" >> "$GITHUB_OUTPUT" echo "VERSION=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" echo "CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" @@ -41,8 +45,8 @@ jobs: uses: docker/login-action@v1 with: registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.CR_PAT }} + username: ${{ github.repository_owner }} + password: ${{ github.token }} - name: Build and push uses: docker/build-push-action@v2 diff --git a/.github/workflows/package-virtual-worker.yaml b/.github/workflows/package-virtual-worker.yaml index 838d50d4..f78b1be8 100644 --- a/.github/workflows/package-virtual-worker.yaml +++ b/.github/workflows/package-virtual-worker.yaml @@ -9,6 +9,10 @@ on: workflow_dispatch: +permissions: + contents: read + packages: write + jobs: publish-container-image: @@ -18,7 +22,7 @@ jobs: - name: Set environment variables id: set-variables run: | - echo "REPOSITORY=ghcr.io/azure-samples/aks-store-demo" >> "$GITHUB_OUTPUT" + echo "REPOSITORY=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" echo "IMAGE=virtual-worker" >> "$GITHUB_OUTPUT" echo "VERSION=$(echo ${GITHUB_SHA} | cut -c1-7)" >> "$GITHUB_OUTPUT" echo "CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" @@ -41,8 +45,8 @@ jobs: uses: docker/login-action@v1 with: registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.CR_PAT }} + username: ${{ github.repository_owner }} + password: ${{ github.token }} - name: Build and push uses: docker/build-push-action@v2 diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..0ebca2de --- /dev/null +++ b/Makefile @@ -0,0 +1,103 @@ +IMAGE_VERSION ?= 0.0.1-beta + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +.PHONY: all +all: build load kustomize deploy ## Build all container images, load images into kind cluster, and deploy to kind cluster + +##@ Build images + +.PHONY: build +build: ./src/order-service/Dockerfile \ + ./src/makeline-service/Dockerfile \ + ./src/product-service/Dockerfile \ + ./src/store-front/Dockerfile \ + ./src/store-admin/Dockerfile \ + ./src/virtual-customer/Dockerfile \ + ./src/virtual-worker/Dockerfile \ + ## Build all images + docker build -t order-service:$(IMAGE_VERSION) ./src/order-service + docker build -t makeline-service:$(IMAGE_VERSION) ./src/makeline-service + docker build -t product-service:$(IMAGE_VERSION) ./src/product-service + docker build -t store-front:$(IMAGE_VERSION) ./src/store-front + docker build -t store-admin:$(IMAGE_VERSION) ./src/store-admin + docker build -t virtual-customer:$(IMAGE_VERSION) ./src/virtual-customer + docker build -t virtual-worker:$(IMAGE_VERSION) ./src/virtual-worker + +##@ Deploy to kind cluster + +.PHONY: load +load: build kind ## Load all locally built containers into kind cluster + $(KIND) load docker-image \ + order-service:$(IMAGE_VERSION) \ + makeline-service:$(IMAGE_VERSION) \ + product-service:$(IMAGE_VERSION) \ + store-front:$(IMAGE_VERSION) \ + store-admin:$(IMAGE_VERSION) \ + virtual-customer:$(IMAGE_VERSION) \ + virtual-worker:$(IMAGE_VERSION) + +.PHONY: manifest ## Create kustomization.yaml and set image versions to locally built images +manifest: kustomize aks-store-all-in-one.yaml + $(KUSTOMIZE) create --resources aks-store-all-in-one.yaml + $(KUSTOMIZE) edit set image ghcr.io/azure-samples/aks-store-demo/order-service=order-service:$(IMAGE_VERSION) + $(KUSTOMIZE) edit set image ghcr.io/azure-samples/aks-store-demo/makeline-service=makeline-service:$(IMAGE_VERSION) + $(KUSTOMIZE) edit set image ghcr.io/azure-samples/aks-store-demo/product-service=product-service:$(IMAGE_VERSION) + $(KUSTOMIZE) edit set image ghcr.io/azure-samples/aks-store-demo/store-front=store-front:$(IMAGE_VERSION) + $(KUSTOMIZE) edit set image ghcr.io/azure-samples/aks-store-demo/store-admin=store-admin:$(IMAGE_VERSION) + $(KUSTOMIZE) edit set image ghcr.io/azure-samples/aks-store-demo/virtual-customer=virtual-customer:$(IMAGE_VERSION) + $(KUSTOMIZE) edit set image ghcr.io/azure-samples/aks-store-demo/virtual-worker=virtual-worker:$(IMAGE_VERSION) + +.PHONY: deploy +deploy: manifest ## Deploy to cluster + kubectl apply -k . + +.PHONY: clean +clean: ## Delete kind cluster and kustomization.yaml + @if [ `kind get clusters | wc -l` -gt 0 ]; then \ + kind delete cluster; \ + fi + @rm -f kustomization.yaml + @rm -rf $(LOCALBIN) + +##@ Build Dependencies + +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +# tools +KUSTOMIZE ?= $(LOCALBIN)/kustomize +ENVTEST ?= $(LOCALBIN)/setup-envtest +KIND ?= $(LOCALBIN)/kind + +# tool versions +KUSTOMIZE_VERSION ?= v5.1.1 +KIND_VERSION ?= v0.20.0 + +# kustomize +KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. +$(KUSTOMIZE): $(LOCALBIN) + @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ + echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ + rm -rf $(LOCALBIN)/kustomize; \ + fi + test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } + +.PHONY: envtest +envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. +$(ENVTEST): $(LOCALBIN) + test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest + +.PHONY: kind +kind: $(KIND) ## Download kind locally if necessary and create a new cluster. If wrong version is installed, it will be overwritten. +$(KIND): $(LOCALBIN) + test -s $(LOCALBIN)/kind && $(LOCALBIN)/kind --version | grep -q $(KIND_VERSION) || \ + GOBIN=$(LOCALBIN) go install sigs.k8s.io/kind@$(KIND_VERSION) + @if [ `$(KIND) get clusters | wc -l` -eq 0 ]; then \ + $(KIND) create cluster; \ + fi \ No newline at end of file diff --git a/aks-store-all-in-one.yaml b/aks-store-all-in-one.yaml index 9ee05796..81651952 100644 --- a/aks-store-all-in-one.yaml +++ b/aks-store-all-in-one.yaml @@ -39,6 +39,14 @@ spec: app: mongodb type: ClusterIP --- +apiVersion: v1 +data: + rabbitmq_enabled_plugins: | + [rabbitmq_management,rabbitmq_prometheus,rabbitmq_amqp1_0]. +kind: ConfigMap +metadata: + name: rabbitmq-enabled-plugins +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -75,6 +83,17 @@ spec: limits: cpu: 250m memory: 256Mi + volumeMounts: + - name: rabbitmq-enabled-plugins + mountPath: /etc/rabbitmq/enabled_plugins + subPath: enabled_plugins + volumes: + - name: rabbitmq-enabled-plugins + configMap: + name: rabbitmq-enabled-plugins + items: + - key: rabbitmq_enabled_plugins + path: enabled_plugins --- apiVersion: v1 kind: Service @@ -114,8 +133,6 @@ spec: ports: - containerPort: 3000 env: - - name: ORDER_QUEUE_PROTOCOL - value: "amqp" - name: ORDER_QUEUE_HOSTNAME value: "rabbitmq" - name: ORDER_QUEUE_PORT @@ -124,6 +141,8 @@ spec: value: "username" - name: ORDER_QUEUE_PASSWORD value: "password" + - name: ORDER_QUEUE_NAME + value: "orders" - name: FASTIFY_ADDRESS value: "0.0.0.0" resources: @@ -180,11 +199,15 @@ spec: ports: - containerPort: 3001 env: - - name: ORDER_QUEUE_CONNECTION_STRING - value: "amqp://username:password@rabbitmq:5672/" + - name: ORDER_QUEUE_URI + value: "amqp://rabbitmq:5672" + - name: ORDER_QUEUE_USERNAME + value: "username" + - name: ORDER_QUEUE_PASSWORD + value: "password" - name: ORDER_QUEUE_NAME value: "orders" - - name: ORDER_DB_CONNECTION_STRING + - name: ORDER_DB_URI value: "mongodb://mongodb:27017" - name: ORDER_DB_NAME value: "orderdb" diff --git a/docker-compose.yml b/docker-compose.yml index dc20e419..5ed05ed1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,8 @@ services: interval: 30s timeout: 10s retries: 5 + volumes: + - ./rabbitmq_enabled_plugins:/etc/rabbitmq/enabled_plugins networks: - backend_services orderservice: @@ -42,11 +44,12 @@ services: timeout: 10s retries: 5 environment: - - ORDER_QUEUE_PROTOCOL=amqp - ORDER_QUEUE_HOSTNAME=rabbitmq - ORDER_QUEUE_PORT=5672 - ORDER_QUEUE_USERNAME=username - ORDER_QUEUE_PASSWORD=password + - ORDER_QUEUE_NAME=orders + - ORDER_QUEUE_RECONNECT_LIMIT=3 networks: - backend_services depends_on: @@ -64,9 +67,11 @@ services: timeout: 10s retries: 5 environment: - - ORDER_QUEUE_CONNECTION_STRING=amqp://username:password@rabbitmq:5672/ + - ORDER_QUEUE_URI=amqp://rabbitmq:5672 + - ORDER_QUEUE_USERNAME=username + - ORDER_QUEUE_PASSWORD=password - ORDER_QUEUE_NAME=orders - - ORDER_DB_CONNECTION_STRING=mongodb://mongodb:27017 + - ORDER_DB_URI=mongodb://mongodb:27017 - ORDER_DB_NAME=orderdb - ORDER_DB_COLLECTION_NAME=orders networks: diff --git a/rabbitmq_enabled_plugins b/rabbitmq_enabled_plugins new file mode 100644 index 00000000..bb83c7c2 --- /dev/null +++ b/rabbitmq_enabled_plugins @@ -0,0 +1 @@ +[rabbitmq_management,rabbitmq_prometheus,rabbitmq_amqp1_0]. \ No newline at end of file diff --git a/src/makeline-service/README.md b/src/makeline-service/README.md index 40d3b650..76534fd7 100644 --- a/src/makeline-service/README.md +++ b/src/makeline-service/README.md @@ -4,34 +4,125 @@ This is a Golang app that provides an API for processing orders. It is meant to It is a simple REST API written with the Gin framework that allows you to process orders from a RabbitMQ queue and send them to a MongoDB database. -## Running the app locally - -### Prerequisites +## Prerequisites - [Go](https://golang.org/doc/install) - [Docker](https://docs.docker.com/get-docker/) - [Docker Compose](https://docs.docker.com/compose/install/) - [MongoSH](https://docs.mongodb.com/mongodb-shell/install/) -### Running the app +## Message queue options -The app relies on RabbitMQ and MongoDB. Additionally, to simulate orders, you will need to run the [order-service](../order-service) with the [virtual-customer](../virtual-customer) app. A docker-compose file is provided to make this easy. +This app can connect to either RabbitMQ or Azure Service Bus using AMQP 1.0. To connect to either of these services, you will need to provide appropriate environment variables for connecting to the message queue. -To run the necessary services, clone the repo, open a terminal, and navigate to the `makeline-service` directory. Then run the following command: +### Option 1: RabbitMQ + +To run this against RabbitMQ. A docker-compose file is provided to make this easy. This will run RabbitMQ, the RabbitMQ Management UI, and enable the `rabbitmq_amqp1_0` plugin. The plugin is necessary to connect to RabbitMQ using AMQP 1.0. + +With the services running, open a new terminal and navigate to the `makeline-service` directory. + +Set the connection information for the RabbitMQ queue by running the following commands to set the environment variables: ```bash -docker compose up +export ORDER_QUEUE_URI=amqp://localhost +export ORDER_QUEUE_USERNAME=username +export ORDER_QUEUE_PASSWORD=password +export ORDER_QUEUE_NAME=orders +``` + +### Option 2: Azure Service Bus + +To run this against Azure Service Bus, you will need to create a Service Bus namespace and a queue. You can do this using the Azure CLI. + +```bash +az group create --name --location +az servicebus namespace create --name --resource-group +az servicebus queue create --name orders --namespace-name --resource-group +``` + +Once you have created the Service Bus namespace and queue, you will need to create a shared access policy with the **Listen** permission for the namespace. + +```bash +az servicebus namespace authorization-rule create --name listener --namespace-name --resource-group --queue-name orders --rights Listen ``` -With the services running, open a new terminal and navigate to the `makeline-service` directory. Then run the following commands: +Next, get the connection information for the Azure Service Bus queue and save the values to environment variables. ```bash -export ORDER_QUEUE_CONNECTION_STRING=amqp://username:password@localhost:5672/ +HOSTNAME=$(az servicebus namespace show --name --resource-group --query serviceBusEndpoint -o tsv | sed 's/https:\/\///;s/:443\///') + +PASSWORD=$(az servicebus namespace authorization-rule keys list --namespace-name --resource-group --queue-name orders --name listener --query primaryKey -o tsv) +``` + +Finally, set the environment variables. + +```bash +export ORDER_QUEUE_URI=$HOSTNAME +export ORDER_QUEUE_USERNAME=listener +export ORDER_QUEUE_PASSWORD=$PASSWORD export ORDER_QUEUE_NAME=orders -export ORDER_DB_CONNECTION_STRING=mongodb://localhost:27017 +``` + +> NOTE: If you are using Azure Service Bus, you will want your `order-service` to write orders to it instead of RabbitMQ. If that is the case, then you'll need to update the [`docker-compose.yml`](./docker-compose.yml) and modify the environment variables for the `orderservice` to include the proper connection info to connect to Azure Service Bus. Also you will need to add the `ORDER_QUEUE_TRANSPORT=tls` configuration to connect over TLS. + +## Database options + +You also have the option to write orders to either MongoDB or Azure CosmosDB. + +### Option 1: MongoDB + +If you are using a local MongoDB container, run the following commands: + +```bash +export ORDER_DB_URI=mongodb://localhost:27017 +export ORDER_DB_NAME=orderdb +export ORDER_DB_COLLECTION_NAME=orders +``` + +### Option 2: Azure CosmosDB + +To run this against Azure CosmosDB, you will need to create the CosmosDB account, the database, and collection. You can do this using the Azure CLI. + +```bash +az group create --name --location +az cosmosdb create --name --resource-group --kind MongoDB +az cosmosdb mongodb database create --account-name --name orderdb --resource-group +az cosmosdb mongodb collection create --account-name --database-name orderdb --name orders --resource-group +``` + +Next, get the connection information for the Azure Service Bus queue and save the values to environment variables. + +```bash +COSMOSDBNAME= +USERNAME= +PASSWORD=$(az cosmosdb keys list --name --resource-group --query primaryMasterKey -o tsv) +``` + +Finally, set the environment variables. + +```bash +export ORDER_DB_URI=mongodb://$COSMOSDBNAME.mongo.cosmos.azure.com:10255/?retryWrites=false export ORDER_DB_NAME=orderdb export ORDER_DB_COLLECTION_NAME=orders +export ORDER_DB_USERNAME=$USERNAME +export ORDER_DB_PASSWORD=$PASSWORD +``` + +> NOTE: With Azure CosmosDB, you must ensure the orderdb database and an unsharded orders collection exist before running the app. Otherwise you will get a "server selection error". + +## Running the app locally + +The app relies on RabbitMQ and MongoDB. Additionally, to simulate orders, you will need to run the [order-service](../order-service) with the [virtual-customer](../virtual-customer) app. A docker-compose file is provided to make this easy. + +To run the necessary services, clone the repo, open a terminal, and navigate to the `makeline-service` directory. Then run the following command: +```bash +docker compose up +``` + +Now you can run the following commands to start the application: + +```bash go get . go run . ``` @@ -77,3 +168,19 @@ db.orders.find() # get completed orders db.orders.findOne({status: 1}) ``` + +To view the orders in Azure CosmosDB using `mongosh`, open a terminal an run the following command: + +```bash +# connect to cosmosdb +mongosh -u $USERNAME -p $PASSWORD --tls --retryWrites=false mongodb://$COSMOSDBNAME.mongo.cosmos.azure.com:10255/orderdb + +# show collections and confirm orders exists +show collections + +# get the orders +db.orders.find() + +# get completed orders +db.orders.findOne({status: 1}) +``` \ No newline at end of file diff --git a/src/makeline-service/docker-compose.yml b/src/makeline-service/docker-compose.yml index 8015a647..cc9d385b 100644 --- a/src/makeline-service/docker-compose.yml +++ b/src/makeline-service/docker-compose.yml @@ -28,6 +28,8 @@ services: interval: 30s timeout: 10s retries: 5 + volumes: + - ../../rabbitmq_enabled_plugins:/etc/rabbitmq/enabled_plugins networks: - backend_services orderservice: @@ -42,11 +44,12 @@ services: timeout: 10s retries: 5 environment: - - ORDER_QUEUE_PROTOCOL=amqp - ORDER_QUEUE_HOSTNAME=rabbitmq - ORDER_QUEUE_PORT=5672 - ORDER_QUEUE_USERNAME=username - ORDER_QUEUE_PASSWORD=password + - ORDER_QUEUE_NAME=orders + - ORDER_QUEUE_RECONNECT_LIMIT=3 networks: - backend_services depends_on: diff --git a/src/makeline-service/go.mod b/src/makeline-service/go.mod index c8453561..19be807e 100644 --- a/src/makeline-service/go.mod +++ b/src/makeline-service/go.mod @@ -3,8 +3,9 @@ module aks-store-demo/makeline-service go 1.20 require ( + github.com/Azure/go-amqp v1.0.1 + github.com/gin-contrib/cors v1.4.0 github.com/gin-gonic/gin v1.9.1 - github.com/rabbitmq/amqp091-go v1.8.1 go.mongodb.org/mongo-driver v1.11.7 ) @@ -12,14 +13,12 @@ require ( github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/gin-contrib/cors v1.4.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/golang/snappy v0.0.1 // indirect - github.com/google/go-cmp v0.5.6 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect @@ -42,8 +41,6 @@ require ( golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/protobuf v1.30.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/src/makeline-service/go.sum b/src/makeline-service/go.sum index 66b773fb..6e380f64 100644 --- a/src/makeline-service/go.sum +++ b/src/makeline-service/go.sum @@ -1,3 +1,5 @@ +github.com/Azure/go-amqp v1.0.1 h1:Jf8OQCKzRDMZ3pCiH4onM7yrhl5curkRSGkRLTyP35o= +github.com/Azure/go-amqp v1.0.1/go.mod h1:+bg0x3ce5+Q3ahCEXnCsGG3ETpDQe3MEVnOuT2ywPwc= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= @@ -8,6 +10,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= @@ -36,8 +39,7 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -46,14 +48,13 @@ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= @@ -76,9 +77,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA= -github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -110,8 +110,6 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= go.mongodb.org/mongo-driver v1.11.7 h1:LIwYxASDLGUg/8wOhgOOZhX8tQa/9tgZPgzZoVqJvcs= go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -142,16 +140,13 @@ golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/src/makeline-service/main.go b/src/makeline-service/main.go index 6ccb0834..637db0e0 100644 --- a/src/makeline-service/main.go +++ b/src/makeline-service/main.go @@ -8,13 +8,12 @@ import ( "net/http" "os" "strconv" + "time" + amqp "github.com/Azure/go-amqp" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" - amqp "github.com/rabbitmq/amqp091-go" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" ) // Fetch orders from the order queue and store them in database @@ -22,9 +21,9 @@ func fetchOrders(c *gin.Context) { var orders []order // Get order queue connection string from environment variable - orderQueueConn := os.Getenv("ORDER_QUEUE_CONNECTION_STRING") - if orderQueueConn == "" { - log.Printf("ORDER_QUEUE_CONNECTION_STRING is not set") + orderQueueUri := os.Getenv("ORDER_QUEUE_URI") + if orderQueueUri == "" { + log.Printf("ORDER_QUEUE_URI is not set") c.AbortWithStatus(http.StatusInternalServerError) return } @@ -37,72 +36,81 @@ func fetchOrders(c *gin.Context) { return } - // Connect to order queue - conn, err := amqp.Dial(orderQueueConn) - if err != nil { - log.Printf("%s: %s", "Failed to connect to order queue", err) + // Get queue username from environment variable + orderQueueUsername := os.Getenv("ORDER_QUEUE_USERNAME") + if orderQueueName == "" { + log.Printf("ORDER_QUEUE_USERNAME is not set") c.AbortWithStatus(http.StatusInternalServerError) return } - defer conn.Close() - ch, err := conn.Channel() - if err != nil { - log.Printf("%s: %s", "Failed to open a channel", err) + // Get queue password from environment variable + orderQueuePassword := os.Getenv("ORDER_QUEUE_PASSWORD") + if orderQueuePassword == "" { + log.Printf("ORDER_QUEUE_PASSWORD is not set") c.AbortWithStatus(http.StatusInternalServerError) return } - defer ch.Close() - - // Peek into the queue to get the number of messages - queue, err := ch.QueueDeclarePassive( - orderQueueName, // name - false, // durable - false, // delete when unused - false, // exclusive - false, // no-wait - nil, // arguments - ) + + ctx := context.Background() + + // Connect to order queue + conn, err := amqp.Dial(ctx, orderQueueUri, &amqp.ConnOptions{ + SASLType: amqp.SASLTypePlain(orderQueueUsername, orderQueuePassword), + }) if err != nil { - log.Printf("Failed to declare queue: %s", err) + log.Printf("%s: %s", "Failed to connect to order queue", err) c.AbortWithStatus(http.StatusInternalServerError) return } + defer conn.Close() - numMessages := queue.Messages - log.Printf("Number of messages in the queue: %d\n", numMessages) - - // Get the number of messages to consume from an environment variable - numMessagesStr := os.Getenv("NUM_MESSAGES") - numMessagesEnv, err := strconv.Atoi(numMessagesStr) + session, err := conn.NewSession(ctx, nil) if err != nil { - log.Printf("NUM_MESSAGES is not set. Will read all messages from the queue\n") - } - - // If the numMessageEnv is set, use it, otherwise use the number of messages in the queue - if numMessagesEnv > 0 { - numMessages = numMessagesEnv + log.Printf("Unable to create a new session") } - // Consume the specified number of messages from the queue - for i := 0; i < numMessages; i++ { - msg, ok, err := ch.Get(orderQueueName, false) + { + // create a receiver + receiver, err := session.NewReceiver(ctx, orderQueueName, nil) if err != nil { - log.Printf("Failed to consume message: %s", err) + log.Printf("Creating receiver link: %s", err) c.AbortWithStatus(http.StatusInternalServerError) return } - if !ok { - log.Println("No message received") - } else { - log.Printf("Received: %s\n", msg.Body) + defer func() { + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + receiver.Close(ctx) + cancel() + }() + + for { + log.Printf("getting orders") + + ctx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + + // receive next message + msg, err := receiver.Receive(ctx, nil) + if err != nil { + if err.Error() == "context deadline exceeded" { + log.Printf("No more orders for you: %v", err.Error()) + break + } else { + c.AbortWithStatus(http.StatusInternalServerError) + return + } + } + + messageBody := string(msg.GetData()) + log.Printf("Message received: %s\n", messageBody) // Create a random string to use as the order key orderKey := strconv.Itoa(rand.Intn(100000)) - // Deserialize msg.Body to order and add to []order slice + // Deserialize msg data to order and add to []order slice var order order - err = json.Unmarshal(msg.Body, &order) + err = json.Unmarshal(msg.GetData(), &order) if err != nil { log.Printf("Failed to deserialize message: %s", err) @@ -119,62 +127,29 @@ func fetchOrders(c *gin.Context) { // Add order to []order slice orders = append(orders, order) - // Send an acknowledgement to remove the message from the queue - if err := msg.Ack(false); err != nil { - log.Printf("Failed to send acknowledgement: %s", err) - c.AbortWithStatus(http.StatusInternalServerError) - return + // accept message + if err = receiver.AcceptMessage(context.TODO(), msg); err != nil { + log.Printf("Failure accepting message: %s", err) + // remove the order from the slice so that we pick it up on the next run + orders = orders[:len(orders)-1] } } } - // Close the channel - if err := ch.Close(); err != nil { - log.Printf("Failed to close channel: %s", err) - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - // Close the connection - if err := conn.Close(); err != nil { - log.Printf("Failed to close connection: %s", err) - c.AbortWithStatus(http.StatusInternalServerError) - return - } - // Save orders to database - var ctx = context.TODO() - - // Get database connection string from environment variable - orderDBConn := os.Getenv("ORDER_DB_CONNECTION_STRING") - if orderDBConn == "" { - log.Printf("ORDER_DB_CONNECTION_STRING is not set") - c.AbortWithStatus(http.StatusInternalServerError) - } + ctx = context.TODO() - // get database name from environment variable - orderDBName := os.Getenv("ORDER_DB_NAME") - if orderDBName == "" { - log.Printf("ORDER_DB_NAME is not set") - c.AbortWithStatus(http.StatusInternalServerError) - } - - // get database collection from environment variable - orderDBCollection := os.Getenv("ORDER_DB_COLLECTION_NAME") - if orderDBCollection == "" { - log.Printf("ORDER_DB_COLLECTION_NAME is not set") - c.AbortWithStatus(http.StatusInternalServerError) - } - - clientOptions := options.Client().ApplyURI(orderDBConn) - mongoClient, err := mongo.Connect(ctx, clientOptions) + // Connect to MongoDB + collection, err := connectToMongoDB() if err != nil { - log.Printf("Failed to connect to database: %s", err) + log.Printf("Failed to connect to MongoDB: %s", err) c.AbortWithStatus(http.StatusInternalServerError) + return + } else { + log.Printf("Connected to MongoDB") } - // Get a handle for the orders collection - collection := mongoClient.Database(orderDBName).Collection(orderDBCollection) + defer collection.Database().Client().Disconnect(context.Background()) var ordersInterface []interface{} for _, o := range orders { @@ -189,6 +164,7 @@ func fetchOrders(c *gin.Context) { if err != nil { log.Printf("Failed to insert order: %s", err) c.AbortWithStatus(http.StatusInternalServerError) + return } log.Printf("Inserted %v documents into database\n", len(insertResult.InsertedIDs)) @@ -200,9 +176,17 @@ func fetchOrders(c *gin.Context) { if err != nil { log.Printf("Failed to find records: %s", err) c.AbortWithStatus(http.StatusInternalServerError) + return } defer cursor.Close(ctx) + // Check if there was an error during iteration + if err := cursor.Err(); err != nil { + log.Printf("Failed to find records: %s", err) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + // Iterate over the cursor and decode each document for cursor.Next(ctx) { var pendingOrder order @@ -214,13 +198,6 @@ func fetchOrders(c *gin.Context) { orders = append(orders, pendingOrder) } - // Check if there was an error during iteration - if err := cursor.Err(); err != nil { - log.Printf("Failed to iterate cursor: %s", err) - c.AbortWithStatus(http.StatusInternalServerError) - return - } - // Return the pending orders c.IndentedJSON(http.StatusOK, orders) } @@ -233,36 +210,17 @@ func getOrder(c *gin.Context) { // Read order from database var ctx = context.TODO() - // Get database connection string from environment variable - mongoConn := os.Getenv("ORDER_DB_CONNECTION_STRING") - if mongoConn == "" { - log.Printf("ORDER_DB_CONNECTION_STRING is not set") - c.AbortWithStatus(http.StatusInternalServerError) - } - - // get database name from environment variable - mongoDb := os.Getenv("ORDER_DB_NAME") - if mongoDb == "" { - log.Printf("ORDER_DB_NAME is not set") - c.AbortWithStatus(http.StatusInternalServerError) - } - - // get database collection from environment variable - mongoCollection := os.Getenv("ORDER_DB_COLLECTION_NAME") - if mongoCollection == "" { - log.Printf("ORDER_DB_COLLECTION_NAME is not set") - c.AbortWithStatus(http.StatusInternalServerError) - } - - clientOptions := options.Client().ApplyURI(mongoConn) - mongoClient, err := mongo.Connect(ctx, clientOptions) + // Connect to MongoDB + collection, err := connectToMongoDB() if err != nil { - log.Printf("Failed to connect to database: %s", err) + log.Printf("Failed to connect to MongoDB: %s", err) c.AbortWithStatus(http.StatusInternalServerError) + return + } else { + log.Printf("Connected to MongoDB") } - // Get a handle for the orders collection - collection := mongoClient.Database(mongoDb).Collection(mongoCollection) + defer collection.Database().Client().Disconnect(context.Background()) // Find the order by orderId singleResult := collection.FindOne(ctx, bson.M{"orderid": orderId}) @@ -289,36 +247,17 @@ func updateOrder(c *gin.Context) { // Read order from database var ctx = context.TODO() - // Get database connection string from environment variable - mongoConn := os.Getenv("ORDER_DB_CONNECTION_STRING") - if mongoConn == "" { - log.Printf("ORDER_DB_CONNECTION_STRING is not set") - c.AbortWithStatus(http.StatusInternalServerError) - } - - // get database name from environment variable - mongoDb := os.Getenv("ORDER_DB_NAME") - if mongoDb == "" { - log.Printf("ORDER_DB_NAME is not set") - c.AbortWithStatus(http.StatusInternalServerError) - } - - // get database collection from environment variable - mongoCollection := os.Getenv("ORDER_DB_COLLECTION_NAME") - if mongoCollection == "" { - log.Printf("ORDER_DB_COLLECTION_NAME is not set") - c.AbortWithStatus(http.StatusInternalServerError) - } - - clientOptions := options.Client().ApplyURI(mongoConn) - mongoClient, err := mongo.Connect(ctx, clientOptions) + // Connect to MongoDB + collection, err := connectToMongoDB() if err != nil { - log.Printf("Failed to connect to database: %s", err) + log.Printf("Failed to connect to MongoDB: %s", err) c.AbortWithStatus(http.StatusInternalServerError) + return + } else { + log.Printf("Connected to MongoDB") } - // Get a handle for the orders collection - collection := mongoClient.Database(mongoDb).Collection(mongoCollection) + defer collection.Database().Client().Disconnect(context.Background()) log.Printf("Updating order: %v", order) diff --git a/src/makeline-service/mongodb.go b/src/makeline-service/mongodb.go new file mode 100644 index 00000000..656c78bc --- /dev/null +++ b/src/makeline-service/mongodb.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + "crypto/tls" + "log" + "net/http" + "os" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +func connectToMongoDB() (*mongo.Collection, error) { + // Get database uri from environment variable + mongoUri := os.Getenv("ORDER_DB_URI") + if mongoUri == "" { + log.Printf("ORDER_DB_URI is not set") + return nil, http.ErrAbortHandler + } + + // get database name from environment variable + mongoDb := os.Getenv("ORDER_DB_NAME") + if mongoDb == "" { + log.Printf("ORDER_DB_NAME is not set") + return nil, http.ErrAbortHandler + } + + // get database collection name from environment variable + mongoCollection := os.Getenv("ORDER_DB_COLLECTION_NAME") + if mongoCollection == "" { + log.Printf("ORDER_DB_COLLECTION_NAME is not set") + return nil, http.ErrAbortHandler + } + + // get database username from environment variable + mongoUser := os.Getenv("ORDER_DB_USERNAME") + + // get database password from environment variable + mongoPassword := os.Getenv("ORDER_DB_PASSWORD") + + // create a context + ctx := context.Background() + + // create a mongo client + var clientOptions *options.ClientOptions + if mongoUser == "" && mongoPassword == "" { + clientOptions = options.Client().ApplyURI(mongoUri) + } else { + clientOptions = options.Client().ApplyURI(mongoUri). + SetAuth(options.Credential{ + Username: mongoUser, + Password: mongoPassword, + }). + SetTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + mongoClient, err := mongo.Connect(ctx, clientOptions) + if err != nil { + log.Printf("failed to connect to mongodb: %s", err) + return nil, err + } + + err = mongoClient.Ping(ctx, nil) + if err != nil { + log.Printf("failed to ping database: %s", err) + } else { + log.Printf("pong from database") + } + + // get a handle for the collection + collection := mongoClient.Database(mongoDb).Collection(mongoCollection) + + return collection, nil +} diff --git a/src/order-service/.dockerignore b/src/order-service/.dockerignore index 569ce539..41c1fd45 100644 --- a/src/order-service/.dockerignore +++ b/src/order-service/.dockerignore @@ -1,2 +1,3 @@ **/node_modules -**/dist \ No newline at end of file +**/dist +.env \ No newline at end of file diff --git a/src/order-service/README.md b/src/order-service/README.md index 2a3afefc..44b4cb02 100644 --- a/src/order-service/README.md +++ b/src/order-service/README.md @@ -2,20 +2,22 @@ This is a Fastify app that provides an API for submitting orders. It is meant to be used in conjunction with the [store-front](../store-front) app. -It is a simple REST API that allows you to add an order to a RabbitMQ queue. +It is a simple REST API that allows you to add an order to a message queue that supports the AMQP 1.0 protocol. -## Running the app locally - -### Prerequisites +## Prerequisites - [Node.js](https://nodejs.org/en/download/) - [Docker](https://docs.docker.com/get-docker/) - [Docker Compose](https://docs.docker.com/compose/install/) -### Running the app +## Message queue options + +This app can connect to either RabbitMQ or Azure Service Bus using AMQP 1.0. To connect to either of these services, you will need to provide appropriate environment variables for connecting to the message queue. + +### Option 1: RabbitMQ -The app relies on RabbitMQ. A docker-compose file is provided to make this easy. +To run this against RabbitMQ. A docker-compose file is provided to make this easy. This will run RabbitMQ, the RabbitMQ Management UI, and enable the `rabbitmq_amqp1_0` plugin. The plugin is necessary to connect to RabbitMQ using AMQP 1.0. To run the necessary services, clone the repo, open a terminal, and navigate to the `order-service` directory. Then run the following command: @@ -26,12 +28,65 @@ docker compose up With the services running, open a new terminal and navigate to the `order-service` directory. Then run the following commands: ```bash -export ORDER_QUEUE_PROTOCOL=amqp -export ORDER_QUEUE_HOSTNAME=localhost -export ORDER_QUEUE_PORT=5672 -export ORDER_QUEUE_USERNAME=username -export ORDER_QUEUE_PASSWORD=password +cat << EOF > .env +ORDER_QUEUE_HOSTNAME=localhost +ORDER_QUEUE_PORT=5672 +ORDER_QUEUE_USERNAME=username +ORDER_QUEUE_PASSWORD=password +ORDER_QUEUE_NAME=orders +EOF + +# load the environment variables +source .env +``` + +### Option 2: Azure Service Bus + +To run this against Azure Service Bus, you will need to create a Service Bus namespace and a queue. You can do this using the Azure CLI. + +```bash +az group create --name --location +az servicebus namespace create --name --resource-group +az servicebus queue create --name orders --namespace-name --resource-group +``` + +Once you have created the Service Bus namespace and queue, you will need to create a shared access policy with the **Send** permission for the queue. + +```bash +az servicebus queue authorization-rule create --name sender --namespace-name --resource-group --queue-name orders --rights Send +``` + +Next, get the connection information for the Azure Service Bus queue and save the values to environment variables. + +```bash +HOSTNAME=$(az servicebus namespace show --name --resource-group --query serviceBusEndpoint -o tsv | sed 's/https:\/\///;s/:443\///') + +PASSWORD=$(az servicebus queue authorization-rule keys list --namespace-name --resource-group --queue-name orders --name sender --query primaryKey -o tsv) +``` + +Finally, save the environment variables to a `.env` file. + +```bash +cat << EOF > .env +ORDER_QUEUE_HOSTNAME=$HOSTNAME +ORDER_QUEUE_HOST=$HOSTNAME +ORDER_QUEUE_PORT=5671 +ORDER_QUEUE_USERNAME=sender +ORDER_QUEUE_PASSWORD="$PASSWORD" +ORDER_QUEUE_TRANSPORT=tls +ORDER_QUEUE_RECONNECT_LIMIT=10 +ORDER_QUEUE_NAME=orders +EOF + +# load the environment variables +source .env +``` + +## Running the app locally +To run the app, run the following command: + +```bash npm install npm run dev ``` @@ -42,9 +97,12 @@ When the app is running, you should see output similar to the following: > order-service@1.0.0 dev > fastify start -w -l info -P app.js +[1687920999327] INFO (108877 on yubuntu): Server listening at http://[::1]:3000 [1687920999327] INFO (108877 on yubuntu): Server listening at http://127.0.0.1:3000 ``` +## Testing the API + Using the [`test-order-service.http`](./test-order-service.http) file in the root of the repo, you can test the API. However, you will need to use VS Code and have the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension installed. To view the order messages in RabbitMQ, open a browser and navigate to [http://localhost:15672](http://localhost:15672). Log in with the username and password you provided in the environment variables above. Then click on the **Queues** tab and click on your **orders** queue. After you've submitted a few orders, you should see the messages in the queue. \ No newline at end of file diff --git a/src/order-service/app.js b/src/order-service/app.js index cb296a16..301113a5 100644 --- a/src/order-service/app.js +++ b/src/order-service/app.js @@ -3,30 +3,8 @@ const path = require('path') const AutoLoad = require('@fastify/autoload') -const { - ORDER_QUEUE_PROTOCOL = 'amqp', - ORDER_QUEUE_HOSTNAME = 'localhost', - ORDER_QUEUE_PORT = 5672, - ORDER_QUEUE_USERNAME = 'username', - ORDER_QUEUE_PASSWORD = 'password' -} = process.env - module.exports = async function (fastify, opts) { - // Place here your custom code! - fastify.register(require('fastify-amqp'), { - protocol: ORDER_QUEUE_PROTOCOL, - hostname: ORDER_QUEUE_HOSTNAME, - port: ORDER_QUEUE_PORT, - username: ORDER_QUEUE_USERNAME, - password: ORDER_QUEUE_PASSWORD, - retry: { - retries: 10, - interval: 2000, - factor: 2, - minTimeout: 1000, - maxTimeout: 5000 - } - }) +// Place here your custom code! fastify.register(require('@fastify/cors'), { origin: '*' diff --git a/src/order-service/docker-compose.yml b/src/order-service/docker-compose.yml index 01a24813..6c63cbae 100644 --- a/src/order-service/docker-compose.yml +++ b/src/order-service/docker-compose.yml @@ -15,6 +15,8 @@ services: interval: 30s timeout: 10s retries: 5 + volumes: + - ../../rabbitmq_enabled_plugins:/etc/rabbitmq/enabled_plugins networks: - backend_services networks: diff --git a/src/order-service/package-lock.json b/src/order-service/package-lock.json index 3d4cb660..5536e5eb 100644 --- a/src/order-service/package-lock.json +++ b/src/order-service/package-lock.json @@ -13,32 +13,14 @@ "@fastify/cors": "^8.3.0", "@fastify/sensible": "^4.1.0", "fastify": "^4.0.0", - "fastify-amqp": "^1.1.0", "fastify-cli": "^4.4.0", - "fastify-plugin": "^3.0.0" + "fastify-plugin": "^3.0.0", + "rhea": "^3.0.2" }, "devDependencies": { "tap": "^16.1.0" } }, - "node_modules/@acuminous/bitsyntax": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz", - "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==", - "dependencies": { - "buffer-more-ints": "~1.0.0", - "debug": "^4.3.4", - "safe-buffer": "~5.1.2" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/@acuminous/bitsyntax/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -698,36 +680,6 @@ } } }, - "node_modules/amqplib": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz", - "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==", - "dependencies": { - "@acuminous/bitsyntax": "^0.1.2", - "buffer-more-ints": "~1.0.0", - "readable-stream": "1.x >=1.1.9", - "url-parse": "~1.5.10" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/amqplib/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/amqplib/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -938,11 +890,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/buffer-more-ints": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", - "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" - }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -1117,11 +1064,6 @@ "node": ">= 0.6" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1378,15 +1320,6 @@ "tiny-lru": "^11.0.1" } }, - "node_modules/fastify-amqp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fastify-amqp/-/fastify-amqp-1.1.0.tgz", - "integrity": "sha512-t3RBuz7KeJwkOlT3hPHxRDDDyZkSB4NUbOG08z9z39toKYqS0GSr5jUrq8vpuGpHTPtJqKFZaM862NcJ/ZMLMg==", - "dependencies": { - "amqplib": "^0.10.0", - "fastify-plugin": "^3.0.0" - } - }, "node_modules/fastify-cli": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/fastify-cli/-/fastify-cli-4.4.0.tgz", @@ -1844,11 +1777,6 @@ "node": ">=0.10.0" } }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, "node_modules/isbinaryfile": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", @@ -2770,11 +2698,6 @@ "node": ">=6" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -2847,11 +2770,6 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -2882,6 +2800,14 @@ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" }, + "node_modules/rhea": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rhea/-/rhea-3.0.2.tgz", + "integrity": "sha512-0G1ZNM9yWin8VLvTxyISKH6KfR6gl1TW/1+5yMKPf2r1efhkzTLze09iFtT2vpDjuWIVtSmXz8r18lk/dO8qwQ==", + "dependencies": { + "debug": "^4.3.3" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -5283,15 +5209,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/src/order-service/package.json b/src/order-service/package.json index 1f149a1c..ffe2fb7f 100644 --- a/src/order-service/package.json +++ b/src/order-service/package.json @@ -19,9 +19,9 @@ "@fastify/cors": "^8.3.0", "@fastify/sensible": "^4.1.0", "fastify": "^4.0.0", - "fastify-amqp": "^1.1.0", "fastify-cli": "^4.4.0", - "fastify-plugin": "^3.0.0" + "fastify-plugin": "^3.0.0", + "rhea": "^3.0.2" }, "devDependencies": { "tap": "^16.1.0" diff --git a/src/order-service/plugins/messagequeue.js b/src/order-service/plugins/messagequeue.js new file mode 100644 index 00000000..3825c024 --- /dev/null +++ b/src/order-service/plugins/messagequeue.js @@ -0,0 +1,40 @@ +'use strict' + +const fp = require('fastify-plugin') +const rhea = require('rhea') + +module.exports = fp(async function (fastify, opts) { + fastify.decorate('sendMessage', function (message) { + const body = message.toString() + console.log(`sending message ${body} to ${process.env.ORDER_QUEUE_NAME} on ${process.env.ORDER_QUEUE_HOSTNAME}`) + + const container = rhea.create_container() + var amqp_message = container.message; + + const connectOptions = { + hostname: process.env.ORDER_QUEUE_HOSTNAME, + host: process.env.ORDER_QUEUE_HOSTNAME, + port: process.env.ORDER_QUEUE_PORT, + username: process.env.ORDER_QUEUE_USERNAME, + password: process.env.ORDER_QUEUE_PASSWORD, + reconnect_limit: process.env.ORDER_QUEUE_RECONNECT_LIMIT || 0 + } + + if (process.env.ORDER_QUEUE_TRANSPORT !== undefined) { + connectOptions.transport = process.env.ORDER_QUEUE_TRANSPORT + } + + const connection = container.connect(connectOptions) + + container.once('sendable', function (context) { + const sender = context.sender; + sender.send({ + body: amqp_message.data_section(Buffer.from(body,'utf8')) + }); + sender.close(); + connection.close(); + }) + + connection.open_sender(process.env.ORDER_QUEUE_NAME) + }) +}) diff --git a/src/order-service/routes/root.js b/src/order-service/routes/root.js index 69d05f50..2e06f070 100644 --- a/src/order-service/routes/root.js +++ b/src/order-service/routes/root.js @@ -1,21 +1,18 @@ 'use strict' + module.exports = async function (fastify, opts) { fastify.post('/', async function (request, reply) { - const channel = this.amqp.channel - - const queue = 'orders' const msg = request.body - - channel.assertQueue(queue, { - durable: false - }) - - channel.sendToQueue(queue, Buffer.from(JSON.stringify(msg))) + fastify.sendMessage(Buffer.from(JSON.stringify(msg))) reply.code(201) }) fastify.get('/health', async function (request, reply) { return { status: 'ok' } }) + + fastify.get('/hugs', async function (request, reply) { + return { hugs: fastify.someSupport() } + }) } diff --git a/src/store-admin/docker-compose.yml b/src/store-admin/docker-compose.yml index 25323d58..0bd33fec 100644 --- a/src/store-admin/docker-compose.yml +++ b/src/store-admin/docker-compose.yml @@ -28,6 +28,8 @@ services: interval: 30s timeout: 10s retries: 5 + volumes: + - ../../rabbitmq_enabled_plugins:/etc/rabbitmq/enabled_plugins networks: - backend_services orderservice: @@ -42,11 +44,12 @@ services: timeout: 10s retries: 5 environment: - - ORDER_QUEUE_PROTOCOL=amqp - ORDER_QUEUE_HOSTNAME=rabbitmq - ORDER_QUEUE_PORT=5672 - ORDER_QUEUE_USERNAME=username - ORDER_QUEUE_PASSWORD=password + - ORDER_QUEUE_NAME=orders + - ORDER_QUEUE_RECONNECT_LIMIT=3 networks: - backend_services depends_on: @@ -64,9 +67,11 @@ services: timeout: 10s retries: 5 environment: - - ORDER_QUEUE_CONNECTION_STRING=amqp://username:password@rabbitmq:5672/ + - ORDER_QUEUE_URI=amqp://rabbitmq:5672 + - ORDER_QUEUE_USERNAME=username + - ORDER_QUEUE_PASSWORD=password - ORDER_QUEUE_NAME=orders - - ORDER_DB_CONNECTION_STRING=mongodb://mongodb:27017 + - ORDER_DB_URI=mongodb://mongodb:27017 - ORDER_DB_NAME=orderdb - ORDER_DB_COLLECTION_NAME=orders networks: diff --git a/src/store-front/docker-compose.yml b/src/store-front/docker-compose.yml index fa6502fb..5e3cb214 100644 --- a/src/store-front/docker-compose.yml +++ b/src/store-front/docker-compose.yml @@ -15,6 +15,8 @@ services: interval: 30s timeout: 10s retries: 5 + volumes: + - ../../rabbitmq_enabled_plugins:/etc/rabbitmq/enabled_plugins networks: - backend_services orderservice: @@ -29,11 +31,12 @@ services: timeout: 10s retries: 5 environment: - - ORDER_QUEUE_PROTOCOL=amqp - ORDER_QUEUE_HOSTNAME=rabbitmq - ORDER_QUEUE_PORT=5672 - ORDER_QUEUE_USERNAME=username - ORDER_QUEUE_PASSWORD=password + - ORDER_QUEUE_NAME=orders + - ORDER_QUEUE_RECONNECT_LIMIT=3 networks: - backend_services depends_on: diff --git a/src/virtual-customer/docker-compose.yml b/src/virtual-customer/docker-compose.yml index 1460b110..2f43a5fa 100644 --- a/src/virtual-customer/docker-compose.yml +++ b/src/virtual-customer/docker-compose.yml @@ -15,6 +15,8 @@ services: interval: 30s timeout: 10s retries: 5 + volumes: + - ../../rabbitmq_enabled_plugins:/etc/rabbitmq/enabled_plugins networks: - backend_services orderservice: @@ -29,11 +31,12 @@ services: timeout: 10s retries: 5 environment: - - ORDER_QUEUE_PROTOCOL=amqp - ORDER_QUEUE_HOSTNAME=rabbitmq - ORDER_QUEUE_PORT=5672 - ORDER_QUEUE_USERNAME=username - ORDER_QUEUE_PASSWORD=password + - ORDER_QUEUE_NAME=orders + - ORDER_QUEUE_RECONNECT_LIMIT=3 networks: - backend_services depends_on: diff --git a/src/virtual-worker/docker-compose.yml b/src/virtual-worker/docker-compose.yml index 16259a21..61fb6734 100644 --- a/src/virtual-worker/docker-compose.yml +++ b/src/virtual-worker/docker-compose.yml @@ -28,6 +28,8 @@ services: interval: 30s timeout: 10s retries: 5 + volumes: + - ../../rabbitmq_enabled_plugins:/etc/rabbitmq/enabled_plugins networks: - backend_services orderservice: @@ -42,11 +44,12 @@ services: timeout: 10s retries: 5 environment: - - ORDER_QUEUE_PROTOCOL=amqp - ORDER_QUEUE_HOSTNAME=rabbitmq - ORDER_QUEUE_PORT=5672 - ORDER_QUEUE_USERNAME=username - ORDER_QUEUE_PASSWORD=password + - ORDER_QUEUE_NAME=orders + - ORDER_QUEUE_RECONNECT_LIMIT=3 networks: - backend_services depends_on: @@ -64,9 +67,11 @@ services: timeout: 10s retries: 5 environment: - - ORDER_QUEUE_CONNECTION_STRING=amqp://username:password@rabbitmq:5672/ + - ORDER_QUEUE_URI=amqp://rabbitmq:5672 + - ORDER_QUEUE_USERNAME=username + - ORDER_QUEUE_PASSWORD=password - ORDER_QUEUE_NAME=orders - - ORDER_DB_CONNECTION_STRING=mongodb://mongodb:27017 + - ORDER_DB_URI=mongodb://mongodb:27017 - ORDER_DB_NAME=orderdb - ORDER_DB_COLLECTION_NAME=orders networks: