├── .github └── workflows │ ├── README.md │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cloudbuild.yaml ├── docs ├── development-principles.md └── img │ ├── architecture-diagram.png │ ├── hipster-shop-frontend-1.png │ └── hipster-shop-frontend-2.png ├── hack ├── README.md ├── license_header.txt ├── make-docker-images.sh ├── make-release-artifacts.sh └── make-release.sh ├── istio-manifests ├── frontend-gateway.yaml ├── frontend.yaml └── whitelist-egress-googleapis.yaml ├── kubernetes-manifests ├── README.md ├── adservice.yaml ├── cartservice.yaml ├── checkoutservice.yaml ├── currencyservice.yaml ├── emailservice.yaml ├── frontend.yaml ├── loadgenerator.yaml ├── paymentservice.yaml ├── productcatalogservice.yaml ├── recommendationservice.yaml ├── redis.yaml └── shippingservice.yaml ├── pb ├── demo.proto └── grpc │ └── health │ └── v1 │ └── health.proto ├── release ├── istio-manifests.yaml └── kubernetes-manifests.yaml ├── skaffold.yaml ├── src ├── .gitignore ├── adservice │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── build.gradle │ ├── genproto.sh │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ │ └── main │ │ ├── java │ │ └── hipstershop │ │ │ ├── AdService.java │ │ │ └── AdServiceClient.java │ │ ├── proto │ │ └── demo.proto │ │ └── resources │ │ └── log4j2.xml ├── cartservice │ ├── .gitignore │ ├── CartServiceImpl.cs │ ├── Dockerfile │ ├── HealthImpl.cs │ ├── Program.cs │ ├── cartservice.csproj │ ├── cartstore │ │ ├── LocalCartStore.cs │ │ └── RedisCartStore.cs │ ├── genproto.bat │ ├── genproto.sh │ ├── grpc_generated │ │ ├── Demo.cs │ │ └── DemoGrpc.cs │ ├── interfaces │ │ └── ICartStore.cs │ └── scripts │ │ ├── build_image.bat │ │ ├── docker_setup.bat │ │ └── run_redis_emulator_windows.bat ├── checkoutservice │ ├── .dockerignore │ ├── Dockerfile │ ├── Gopkg.lock │ ├── Gopkg.toml │ ├── README.md │ ├── genproto.sh │ ├── genproto │ │ └── demo.pb.go │ ├── main.go │ └── money │ │ ├── money.go │ │ └── money_test.go ├── currencyservice │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── client.js │ ├── data │ │ └── currency_conversion.json │ ├── genproto.sh │ ├── package-lock.json │ ├── package.json │ ├── proto │ │ ├── demo.proto │ │ └── grpc │ │ │ └── health │ │ │ └── v1 │ │ │ └── health.proto │ └── server.js ├── emailservice │ ├── Dockerfile │ ├── demo_pb2.py │ ├── demo_pb2_grpc.py │ ├── email_client.py │ ├── email_server.py │ ├── genproto.sh │ ├── logger.py │ ├── requirements.in │ ├── requirements.txt │ └── templates │ │ └── confirmation.html ├── frontend │ ├── .dockerignore │ ├── .gitkeep │ ├── Dockerfile │ ├── Gopkg.lock │ ├── Gopkg.toml │ ├── README.md │ ├── genproto.sh │ ├── genproto │ │ └── demo.pb.go │ ├── handlers.go │ ├── main.go │ ├── middleware.go │ ├── money │ │ ├── money.go │ │ └── money_test.go │ ├── rpc.go │ ├── static │ │ └── img │ │ │ └── products │ │ │ ├── air-plant.jpg │ │ │ ├── barista-kit.jpg │ │ │ ├── camera-lens.jpg │ │ │ ├── camp-mug.jpg │ │ │ ├── city-bike.jpg │ │ │ ├── credits.txt │ │ │ ├── film-camera.jpg │ │ │ ├── record-player.jpg │ │ │ ├── terrarium.jpg │ │ │ └── typewriter.jpg │ └── templates │ │ ├── ad.html │ │ ├── cart.html │ │ ├── error.html │ │ ├── footer.html │ │ ├── header.html │ │ ├── home.html │ │ ├── order.html │ │ ├── product.html │ │ └── recommendations.html ├── loadgenerator │ ├── Dockerfile │ ├── loadgen.sh │ ├── locustfile.py │ ├── requirements.in │ └── requirements.txt ├── paymentservice │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── charge.js │ ├── genproto.sh │ ├── index.js │ ├── package-lock.json │ ├── package.json │ ├── proto │ │ ├── demo.proto │ │ └── grpc │ │ │ └── health │ │ │ └── v1 │ │ │ └── health.proto │ └── server.js ├── productcatalogservice │ ├── .dockerignore │ ├── Dockerfile │ ├── Gopkg.lock │ ├── Gopkg.toml │ ├── README.md │ ├── genproto.sh │ ├── genproto │ │ └── demo.pb.go │ ├── products.json │ ├── server.go │ └── server_test.go ├── recommendationservice │ ├── .gitignore │ ├── Dockerfile │ ├── client.py │ ├── demo_pb2.py │ ├── demo_pb2_grpc.py │ ├── genproto.sh │ ├── logger.py │ ├── recommendation_server.py │ ├── requirements.in │ └── requirements.txt └── shippingservice │ ├── .dockerignore │ ├── Dockerfile │ ├── Gopkg.lock │ ├── Gopkg.toml │ ├── README.md │ ├── genproto.sh │ ├── genproto │ └── demo.pb.go │ ├── main.go │ ├── quote.go │ ├── shippingservice_test.go │ └── tracker.go └── tests └── cartservice ├── .gitignore ├── CartServiceTests.cs ├── cartservice.tests.csproj └── cartservice.tests.csproj.user /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # GitHub Actions Workflows 2 | 3 | ## Setup 4 | - workloads run using [GitHub self-hosted runners](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners) 5 | - project admins maintain a private Google Compute Engine VM for running tests 6 | - VM should be at least n1-standard-4 with 50GB persistent disk 7 | - instructions for setting up the VM can be found in repo settings under "Actions" 8 | - ⚠️ WARNING: VM should be set up with no GCP service account 9 | - external contributors could contribute malicious PRs to run code on our test VM. Ensure no service accounts or other secrets exist on the VM 10 | - An empty GCP project should be used for extra security 11 | - to set up dependencies, run the following commands: 12 | ``` 13 | # install kubectl 14 | sudo apt-get install kubectl 15 | 16 | # install kind 17 | curl -Lo ./kind "https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(uname)-amd64" && \ 18 | chmod +x ./kind && \ 19 | sudo mv ./kind /usr/local/bin 20 | 21 | # install skaffold 22 | curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64 && \ 23 | chmod +x skaffold && \ 24 | sudo mv skaffold /usr/local/bin 25 | 26 | # install docker 27 | sudo apt install apt-transport-https ca-certificates curl gnupg2 software-properties-common && \ 28 | curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add - && \ 29 | sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable" && \ 30 | sudo apt update && \ 31 | sudo apt install docker-ce && \ 32 | sudo usermod -aG docker ${USER} 33 | 34 | # logout and back on 35 | exit 36 | ``` 37 | - ensure GitHub Actions runs as background service: 38 | ``` 39 | sudo ∼/actions-runner/svc.sh install 40 | sudo ∼/actions-runner/svc.sh start 41 | ``` 42 | 43 | 44 | --- 45 | ## Workflows 46 | 47 | ### ci.yaml 48 | 49 | #### Triggers 50 | - commits pushed to master 51 | - PRs to master 52 | - PRs to release/ branches 53 | 54 | #### Actions 55 | - ensures kind cluster is running 56 | - builds all containers in src/ 57 | - deploys local containers to kind 58 | - ensures all pods reach ready state 59 | - ensures HTTP request to frontend returns HTTP status 200 60 | - deploys manifests from /releases 61 | - ensures all pods reach ready state 62 | - ensures HTTP request to frontend returns HTTP status 200 63 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "Continuous Integration" 2 | on: 3 | push: 4 | # run on pushes to master or release/* 5 | branches: 6 | - master 7 | - release/* 8 | pull_request: 9 | # run on pull requests targeting master 10 | branches: 11 | - master 12 | jobs: 13 | run-tests: 14 | runs-on: self-hosted 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setup Cluster 18 | run: | 19 | set -x 20 | kind delete cluster || true 21 | kind create cluster 22 | kubectl get nodes 23 | - name: Deploy From Source 24 | run: | 25 | skaffold run 26 | - name: Wait For Pods 27 | timeout-minutes: 20 28 | run: | 29 | set -x 30 | kubectl wait --for=condition=available --timeout=500s deployment/adservice 31 | kubectl wait --for=condition=available --timeout=500s deployment/cartservice 32 | kubectl wait --for=condition=available --timeout=500s deployment/checkoutservice 33 | kubectl wait --for=condition=available --timeout=500s deployment/currencyservice 34 | kubectl wait --for=condition=available --timeout=500s deployment/emailservice 35 | kubectl wait --for=condition=available --timeout=500s deployment/frontend 36 | kubectl wait --for=condition=available --timeout=500s deployment/loadgenerator 37 | kubectl wait --for=condition=available --timeout=500s deployment/paymentservice 38 | kubectl wait --for=condition=available --timeout=500s deployment/productcatalogservice 39 | kubectl wait --for=condition=available --timeout=500s deployment/recommendationservice 40 | kubectl wait --for=condition=available --timeout=500s deployment/shippingservice 41 | - name: Smoke Test 42 | timeout-minutes: 5 43 | run: | 44 | set -x 45 | RESULT=" " 46 | while [[ "$RESULT" != " HTTP/1.1 200 OK" ]]; do 47 | sleep 1 48 | RESULT=$(kubectl exec deployments/frontend -- sh -c "wget --spider -S "http://frontend" 2>&1 | grep 'HTTP/'") 49 | echo "front end response: $RESULT" 50 | done 51 | if [[ "$RESULT" != " HTTP/1.1 200 OK" ]]; then 52 | exit 1 53 | fi 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | pkg/ 3 | .DS_Store 4 | *.pyc 5 | *.swp 6 | *~ 7 | .vscode/ 8 | .vs/ 9 | .idea 10 | .skaffold-*.yaml 11 | .kubernetes-manifests-*/ 12 | .project 13 | .eclipse.buildship.core.prefs -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Development Principles (for Googlers) 7 | 8 | There are a few principles for developing or refactoring the service 9 | implementations. Read the [Development Principles 10 | Guide](./docs/development-principles.md). 11 | 12 | ## Contributor License Agreement 13 | 14 | Contributions to this project must be accompanied by a Contributor License 15 | Agreement. You (or your employer) retain the copyright to your contribution; 16 | this simply gives us permission to use and redistribute your contributions as 17 | part of the project. Head over to to see 18 | your current agreements on file or to sign a new one. 19 | 20 | You generally only need to submit a CLA once, so if you've already submitted one 21 | (even if it was for a different project), you probably don't need to do it 22 | again. 23 | 24 | ## Code reviews 25 | 26 | All submissions, including submissions by project members, require review. We 27 | use GitHub pull requests for this purpose. Consult 28 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 29 | information on using pull requests. 30 | 31 | ## Community Guidelines 32 | 33 | This project follows [Google's Open Source Community 34 | Guidelines](https://opensource.google.com/conduct/). 35 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # This configuration file is used to build and deploy the app into a 2 | # GKE cluster using Google Cloud Build. 3 | # 4 | # PREREQUISITES: 5 | # - Cloud Build service account must have role: "Kubernetes Engine Developer" 6 | 7 | # USAGE: 8 | # GCP zone and GKE target cluster must be specified as substitutions 9 | # Example invocation: 10 | # `gcloud builds submit --config=cloudbuild.yaml --substitutions=_ZONE=us-central1-b,_CLUSTER=demo-app-staging .` 11 | 12 | steps: 13 | - id: 'Deploy application to cluster' 14 | name: 'gcr.io/k8s-skaffold/skaffold:v0.20.0' 15 | entrypoint: 'bash' 16 | args: 17 | - '-c' 18 | - > 19 | gcloud container clusters get-credentials --zone=$_ZONE $_CLUSTER; 20 | skaffold run -f=skaffold.yaml --default-repo=gcr.io/$PROJECT_ID; 21 | 22 | # Add more power, and more time, for heavy Skaffold build 23 | timeout: '3600s' 24 | options: 25 | machineType: 'N1_HIGHCPU_8' -------------------------------------------------------------------------------- /docs/development-principles.md: -------------------------------------------------------------------------------- 1 | # Development Principles 2 | 3 | > **Note:** This document outlines guidances behind some development decisions 4 | > behind the Hipster Shop demo application. 5 | 6 | ### Minimal configuration 7 | 8 | Running the demo locally or on GCP should require minimal to no 9 | configuration unless absolutely necessary to run critical parts of the demo. 10 | 11 | Configuration that takes multiple steps, especially such as creating service 12 | accounts should be avoided. 13 | 14 | ### App must work well outside GCP 15 | 16 | Demo application should work reasonably well when it is not deployed to GCP 17 | services. The experience of running the application locally or on GCP should 18 | be close. 19 | 20 | For example: 21 | - OpenCensus prints the traces to stdout when it cannot connect to GCP. 22 | - Stackdriver Debugging tries connecting to GCP multiple times, eventually gives 23 | up. 24 | 25 | ### Running on GCP must not reduce functionality 26 | 27 | Running the demo on the GCP must not reduce/lose any of the capabilities 28 | developers have when running locally. 29 | 30 | For example: Logs should still be printed to stdout/stderr even though logs are 31 | uploaded to Stackdriver Logging when on GCP, so that developers can use "kubectl 32 | logs" to diagnose each container. 33 | 34 | ### Microservice implementations should not be complex 35 | 36 | Each service should provide a minimal implementation and try to avoid 37 | unnecessary code and logic that's not executed. 38 | 39 | Keep in mind that any service implementation is a decent example of “a GRPC 40 | application that runs on Kubernetes”. Keeping the source code short and 41 | navigable will serve this purpose. 42 | 43 | It is okay to have intentional inefficiencies in the code as they help 44 | illustrate the capabilities of profiling and diagnostics offerings. 45 | -------------------------------------------------------------------------------- /docs/img/architecture-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/docs/img/architecture-diagram.png -------------------------------------------------------------------------------- /docs/img/hipster-shop-frontend-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/docs/img/hipster-shop-frontend-1.png -------------------------------------------------------------------------------- /docs/img/hipster-shop-frontend-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/docs/img/hipster-shop-frontend-2.png -------------------------------------------------------------------------------- /hack/README.md: -------------------------------------------------------------------------------- 1 | ## `hack/` 2 | 3 | This directory provides scripts for building and pushing Docker images, and tagging new demo 4 | releases. 5 | 6 | ### env variables 7 | 8 | - `TAG` - git release tag / Docker tag. 9 | - `REPO_PREFIX` - Docker repo prefix to push images. Format: `$user/$project`. Resulting images will be of the 10 | format `$user/$project/$svcname:$tag` (where `svcname` = `adservice`, `cartservice`, 11 | etc.) 12 | 13 | ### scripts 14 | 15 | 1. `./make-docker-images.sh`: builds and pushes images to the specified Docker repository. 16 | 2. `./make-release-artifacts.sh`: generates a combined YAML file with image $TAG at: 17 | `./release/kubernetes-manifests/demo.yaml`. 18 | 3. `./make-release.sh`: runs scripts 1 and 2, then runs `git tag` / pushes updated manifests to master. 19 | -------------------------------------------------------------------------------- /hack/license_header.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /hack/make-docker-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Builds and pushes docker image for each demo microservice. 18 | 19 | set -euo pipefail 20 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 21 | 22 | log() { echo "$1" >&2; } 23 | 24 | TAG="${TAG:?TAG env variable must be specified}" 25 | REPO_PREFIX="${REPO_PREFIX:?REPO_PREFIX env variable must be specified}" 26 | 27 | while IFS= read -d $'\0' -r dir; do 28 | # build image 29 | svcname="$(basename "${dir}")" 30 | image="${REPO_PREFIX}/$svcname:$TAG" 31 | ( 32 | cd "${dir}" 33 | log "Building: ${image}" 34 | docker build -t "${image}" . 35 | 36 | log "Pushing: ${image}" 37 | docker push "${image}" 38 | ) 39 | done < <(find "${SCRIPTDIR}/../src" -mindepth 1 -maxdepth 1 -type d -print0) 40 | 41 | log "Successfully built and pushed all images." 42 | -------------------------------------------------------------------------------- /hack/make-release-artifacts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script compiles manifest files with the image tags and places them in 18 | # /release/... 19 | 20 | set -euo pipefail 21 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 22 | [[ -n "${DEBUG:-}" ]] && set -x 23 | 24 | log() { echo "$1" >&2; } 25 | 26 | TAG="${TAG:?TAG env variable must be specified}" 27 | REPO_PREFIX="${REPO_PREFIX:?REPO_PREFIX env variable must be specified}" 28 | OUT_DIR="${OUT_DIR:-${SCRIPTDIR}/../release}" 29 | 30 | print_license_header() { 31 | cat "${SCRIPTDIR}/license_header.txt" 32 | echo 33 | } 34 | 35 | print_autogenerated_warning() { 36 | cat< "${k8s_manifests_file}" 94 | log "Written ${k8s_manifests_file}" 95 | 96 | istio_manifests_file="${OUT_DIR}/istio-manifests.yaml" 97 | mk_istio_manifests > "${istio_manifests_file}" 98 | log "Written ${istio_manifests_file}" 99 | } 100 | 101 | main 102 | -------------------------------------------------------------------------------- /hack/make-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This script creates a new release by: 18 | # - 1. building/pushing images 19 | # - 2. injecting tags into YAML manifests 20 | # - 3. creating a new git tag 21 | # - 4. pushing the tag/commit to master. 22 | 23 | set -euo pipefail 24 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 25 | [[ -n "${DEBUG:-}" ]] && set -x 26 | 27 | log() { echo "$1" >&2; } 28 | fail() { log "$1"; exit 1; } 29 | 30 | TAG="${TAG:?TAG env variable must be specified}" 31 | REPO_PREFIX="${REPO_PREFIX:?REPO_PREFIX env variable must be specified e.g. gcr.io\/google-samples\/microservices-demo}" 32 | 33 | if [[ "$TAG" != v* ]]; then 34 | fail "\$TAG must start with 'v', e.g. v0.1.0 (got: $TAG)" 35 | fi 36 | 37 | # build and push images 38 | "${SCRIPTDIR}"/make-docker-images.sh 39 | 40 | # update yaml 41 | "${SCRIPTDIR}"/make-release-artifacts.sh 42 | 43 | # create git release / push to master 44 | git add "${SCRIPTDIR}/../release/" 45 | git commit --allow-empty -m "Release $TAG" 46 | log "Pushing k8s manifests to master..." 47 | git tag "$TAG" 48 | git push --tags 49 | git push origin master 50 | 51 | log "Successfully tagged release $TAG." 52 | -------------------------------------------------------------------------------- /istio-manifests/frontend-gateway.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: networking.istio.io/v1alpha3 16 | kind: Gateway 17 | metadata: 18 | name: frontend-gateway 19 | spec: 20 | selector: 21 | istio: ingressgateway # use Istio default gateway implementation 22 | servers: 23 | - port: 24 | number: 80 25 | name: http 26 | protocol: HTTP 27 | hosts: 28 | - "*" 29 | --- 30 | apiVersion: networking.istio.io/v1alpha3 31 | kind: VirtualService 32 | metadata: 33 | name: frontend-ingress 34 | spec: 35 | hosts: 36 | - "*" 37 | gateways: 38 | - frontend-gateway 39 | http: 40 | - route: 41 | - destination: 42 | host: frontend 43 | port: 44 | number: 80 45 | -------------------------------------------------------------------------------- /istio-manifests/frontend.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: networking.istio.io/v1alpha3 16 | kind: VirtualService 17 | metadata: 18 | name: frontend 19 | spec: 20 | hosts: 21 | - "frontend.default.svc.cluster.local" 22 | http: 23 | - route: 24 | - destination: 25 | host: frontend 26 | port: 27 | number: 80 28 | -------------------------------------------------------------------------------- /istio-manifests/whitelist-egress-googleapis.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: networking.istio.io/v1alpha3 16 | kind: ServiceEntry 17 | metadata: 18 | name: whitelist-egress-googleapis 19 | spec: 20 | hosts: 21 | - "accounts.google.com" # Used to get token 22 | - "*.googleapis.com" 23 | ports: 24 | - number: 80 25 | protocol: HTTP 26 | name: http 27 | - number: 443 28 | protocol: HTTPS 29 | name: https 30 | --- 31 | apiVersion: networking.istio.io/v1alpha3 32 | kind: ServiceEntry 33 | metadata: 34 | name: whitelist-egress-google-metadata 35 | spec: 36 | hosts: 37 | - metadata.google.internal 38 | addresses: 39 | - 169.254.169.254 # GCE metadata server 40 | ports: 41 | - number: 80 42 | name: http 43 | protocol: HTTP 44 | - number: 443 45 | name: https 46 | protocol: HTTPS 47 | -------------------------------------------------------------------------------- /kubernetes-manifests/README.md: -------------------------------------------------------------------------------- 1 | # ./kubernetes-manifests 2 | 3 | :warning: Kubernetes manifests provided in this directory are not directly 4 | deployable to a cluster. They are meant to be used with `skaffold` command to 5 | insert the correct `image:` tags. 6 | 7 | Use the manifests in [/release](/release) directory which are configured with 8 | pre-built public images. 9 | -------------------------------------------------------------------------------- /kubernetes-manifests/adservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: adservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: adservice 23 | template: 24 | metadata: 25 | labels: 26 | app: adservice 27 | spec: 28 | terminationGracePeriodSeconds: 5 29 | containers: 30 | - name: server 31 | image: adservice 32 | ports: 33 | - containerPort: 9555 34 | env: 35 | - name: PORT 36 | value: "9555" 37 | # - name: DISABLE_STATS 38 | # value: "1" 39 | # - name: DISABLE_TRACING 40 | # value: "1" 41 | #- name: JAEGER_SERVICE_ADDR 42 | # value: "jaeger-collector:14268" 43 | resources: 44 | requests: 45 | cpu: 200m 46 | memory: 180Mi 47 | limits: 48 | cpu: 300m 49 | memory: 300Mi 50 | readinessProbe: 51 | initialDelaySeconds: 20 52 | periodSeconds: 15 53 | exec: 54 | command: ["/bin/grpc_health_probe", "-addr=:9555"] 55 | livenessProbe: 56 | initialDelaySeconds: 20 57 | periodSeconds: 15 58 | exec: 59 | command: ["/bin/grpc_health_probe", "-addr=:9555"] 60 | --- 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | name: adservice 65 | spec: 66 | type: ClusterIP 67 | selector: 68 | app: adservice 69 | ports: 70 | - name: grpc 71 | port: 9555 72 | targetPort: 9555 73 | -------------------------------------------------------------------------------- /kubernetes-manifests/cartservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: cartservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: cartservice 23 | template: 24 | metadata: 25 | labels: 26 | app: cartservice 27 | spec: 28 | terminationGracePeriodSeconds: 5 29 | containers: 30 | - name: server 31 | image: cartservice 32 | ports: 33 | - containerPort: 7070 34 | env: 35 | - name: REDIS_ADDR 36 | value: "redis-cart:6379" 37 | - name: PORT 38 | value: "7070" 39 | - name: LISTEN_ADDR 40 | value: "0.0.0.0" 41 | resources: 42 | requests: 43 | cpu: 200m 44 | memory: 64Mi 45 | limits: 46 | cpu: 300m 47 | memory: 128Mi 48 | readinessProbe: 49 | initialDelaySeconds: 15 50 | exec: 51 | command: ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"] 52 | livenessProbe: 53 | initialDelaySeconds: 15 54 | periodSeconds: 10 55 | exec: 56 | command: ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"] 57 | --- 58 | apiVersion: v1 59 | kind: Service 60 | metadata: 61 | name: cartservice 62 | spec: 63 | type: ClusterIP 64 | selector: 65 | app: cartservice 66 | ports: 67 | - name: grpc 68 | port: 7070 69 | targetPort: 7070 70 | -------------------------------------------------------------------------------- /kubernetes-manifests/checkoutservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: checkoutservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: checkoutservice 23 | template: 24 | metadata: 25 | labels: 26 | app: checkoutservice 27 | spec: 28 | containers: 29 | - name: server 30 | image: checkoutservice 31 | ports: 32 | - containerPort: 5050 33 | readinessProbe: 34 | exec: 35 | command: ["/bin/grpc_health_probe", "-addr=:5050"] 36 | livenessProbe: 37 | exec: 38 | command: ["/bin/grpc_health_probe", "-addr=:5050"] 39 | env: 40 | - name: PORT 41 | value: "5050" 42 | - name: PRODUCT_CATALOG_SERVICE_ADDR 43 | value: "productcatalogservice:3550" 44 | - name: SHIPPING_SERVICE_ADDR 45 | value: "shippingservice:50051" 46 | - name: PAYMENT_SERVICE_ADDR 47 | value: "paymentservice:50051" 48 | - name: EMAIL_SERVICE_ADDR 49 | value: "emailservice:5000" 50 | - name: CURRENCY_SERVICE_ADDR 51 | value: "currencyservice:7000" 52 | - name: CART_SERVICE_ADDR 53 | value: "cartservice:7070" 54 | # - name: DISABLE_STATS 55 | # value: "1" 56 | # - name: DISABLE_TRACING 57 | # value: "1" 58 | # - name: DISABLE_PROFILER 59 | # value: "1" 60 | # - name: JAEGER_SERVICE_ADDR 61 | # value: "jaeger-collector:14268" 62 | resources: 63 | requests: 64 | cpu: 100m 65 | memory: 64Mi 66 | limits: 67 | cpu: 200m 68 | memory: 128Mi 69 | --- 70 | apiVersion: v1 71 | kind: Service 72 | metadata: 73 | name: checkoutservice 74 | spec: 75 | type: ClusterIP 76 | selector: 77 | app: checkoutservice 78 | ports: 79 | - name: grpc 80 | port: 5050 81 | targetPort: 5050 82 | -------------------------------------------------------------------------------- /kubernetes-manifests/currencyservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: currencyservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: currencyservice 23 | template: 24 | metadata: 25 | labels: 26 | app: currencyservice 27 | spec: 28 | terminationGracePeriodSeconds: 5 29 | containers: 30 | - name: server 31 | image: currencyservice 32 | ports: 33 | - name: grpc 34 | containerPort: 7000 35 | env: 36 | - name: PORT 37 | value: "7000" 38 | # - name: DISABLE_TRACING 39 | # value: "1" 40 | # - name: DISABLE_PROFILER 41 | # value: "1" 42 | # - name: DISABLE_DEBUGGER 43 | # value: "1" 44 | readinessProbe: 45 | exec: 46 | command: ["/bin/grpc_health_probe", "-addr=:7000"] 47 | livenessProbe: 48 | exec: 49 | command: ["/bin/grpc_health_probe", "-addr=:7000"] 50 | resources: 51 | requests: 52 | cpu: 100m 53 | memory: 64Mi 54 | limits: 55 | cpu: 200m 56 | memory: 128Mi 57 | --- 58 | apiVersion: v1 59 | kind: Service 60 | metadata: 61 | name: currencyservice 62 | spec: 63 | type: ClusterIP 64 | selector: 65 | app: currencyservice 66 | ports: 67 | - name: grpc 68 | port: 7000 69 | targetPort: 7000 70 | -------------------------------------------------------------------------------- /kubernetes-manifests/emailservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: emailservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: emailservice 23 | template: 24 | metadata: 25 | labels: 26 | app: emailservice 27 | spec: 28 | terminationGracePeriodSeconds: 5 29 | containers: 30 | - name: server 31 | image: emailservice 32 | ports: 33 | - containerPort: 8080 34 | env: 35 | - name: PORT 36 | value: "8080" 37 | # - name: DISABLE_TRACING 38 | # value: "1" 39 | - name: DISABLE_PROFILER 40 | value: "1" 41 | readinessProbe: 42 | periodSeconds: 5 43 | exec: 44 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 45 | livenessProbe: 46 | periodSeconds: 5 47 | exec: 48 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 49 | resources: 50 | requests: 51 | cpu: 100m 52 | memory: 64Mi 53 | limits: 54 | cpu: 200m 55 | memory: 128Mi 56 | --- 57 | apiVersion: v1 58 | kind: Service 59 | metadata: 60 | name: emailservice 61 | spec: 62 | type: ClusterIP 63 | selector: 64 | app: emailservice 65 | ports: 66 | - name: grpc 67 | port: 5000 68 | targetPort: 8080 69 | -------------------------------------------------------------------------------- /kubernetes-manifests/frontend.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: frontend 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: frontend 23 | template: 24 | metadata: 25 | labels: 26 | app: frontend 27 | annotations: 28 | sidecar.istio.io/rewriteAppHTTPProbers: "true" 29 | spec: 30 | containers: 31 | - name: server 32 | image: frontend 33 | ports: 34 | - containerPort: 8080 35 | readinessProbe: 36 | initialDelaySeconds: 10 37 | httpGet: 38 | path: "/_healthz" 39 | port: 8080 40 | httpHeaders: 41 | - name: "Cookie" 42 | value: "shop_session-id=x-readiness-probe" 43 | livenessProbe: 44 | initialDelaySeconds: 10 45 | httpGet: 46 | path: "/_healthz" 47 | port: 8080 48 | httpHeaders: 49 | - name: "Cookie" 50 | value: "shop_session-id=x-liveness-probe" 51 | env: 52 | - name: PORT 53 | value: "8080" 54 | - name: PRODUCT_CATALOG_SERVICE_ADDR 55 | value: "productcatalogservice:3550" 56 | - name: CURRENCY_SERVICE_ADDR 57 | value: "currencyservice:7000" 58 | - name: CART_SERVICE_ADDR 59 | value: "cartservice:7070" 60 | - name: RECOMMENDATION_SERVICE_ADDR 61 | value: "recommendationservice:8080" 62 | - name: SHIPPING_SERVICE_ADDR 63 | value: "shippingservice:50051" 64 | - name: CHECKOUT_SERVICE_ADDR 65 | value: "checkoutservice:5050" 66 | - name: AD_SERVICE_ADDR 67 | value: "adservice:9555" 68 | # - name: DISABLE_TRACING 69 | # value: "1" 70 | # - name: DISABLE_PROFILER 71 | # value: "1" 72 | # - name: JAEGER_SERVICE_ADDR 73 | # value: "jaeger-collector:14268" 74 | resources: 75 | requests: 76 | cpu: 100m 77 | memory: 64Mi 78 | limits: 79 | cpu: 200m 80 | memory: 128Mi 81 | --- 82 | apiVersion: v1 83 | kind: Service 84 | metadata: 85 | name: frontend 86 | spec: 87 | type: ClusterIP 88 | selector: 89 | app: frontend 90 | ports: 91 | - name: http 92 | port: 80 93 | targetPort: 8080 94 | --- 95 | apiVersion: v1 96 | kind: Service 97 | metadata: 98 | name: frontend-external 99 | spec: 100 | type: LoadBalancer 101 | selector: 102 | app: frontend 103 | ports: 104 | - name: http 105 | port: 80 106 | targetPort: 8080 107 | -------------------------------------------------------------------------------- /kubernetes-manifests/loadgenerator.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: loadgenerator 18 | spec: 19 | selector: 20 | matchLabels: 21 | app: loadgenerator 22 | replicas: 1 23 | template: 24 | metadata: 25 | labels: 26 | app: loadgenerator 27 | annotations: 28 | sidecar.istio.io/rewriteAppHTTPProbers: "true" 29 | spec: 30 | terminationGracePeriodSeconds: 5 31 | restartPolicy: Always 32 | containers: 33 | - name: main 34 | image: loadgenerator 35 | env: 36 | - name: FRONTEND_ADDR 37 | value: "frontend:80" 38 | - name: USERS 39 | value: "10" 40 | resources: 41 | requests: 42 | cpu: 300m 43 | memory: 256Mi 44 | limits: 45 | cpu: 500m 46 | memory: 512Mi -------------------------------------------------------------------------------- /kubernetes-manifests/paymentservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: paymentservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: paymentservice 23 | template: 24 | metadata: 25 | labels: 26 | app: paymentservice 27 | spec: 28 | terminationGracePeriodSeconds: 5 29 | containers: 30 | - name: server 31 | image: paymentservice 32 | ports: 33 | - containerPort: 50051 34 | env: 35 | - name: PORT 36 | value: "50051" 37 | readinessProbe: 38 | exec: 39 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 40 | livenessProbe: 41 | exec: 42 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 43 | resources: 44 | requests: 45 | cpu: 100m 46 | memory: 64Mi 47 | limits: 48 | cpu: 200m 49 | memory: 128Mi 50 | --- 51 | apiVersion: v1 52 | kind: Service 53 | metadata: 54 | name: paymentservice 55 | spec: 56 | type: ClusterIP 57 | selector: 58 | app: paymentservice 59 | ports: 60 | - name: grpc 61 | port: 50051 62 | targetPort: 50051 63 | -------------------------------------------------------------------------------- /kubernetes-manifests/productcatalogservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: productcatalogservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: productcatalogservice 23 | template: 24 | metadata: 25 | labels: 26 | app: productcatalogservice 27 | spec: 28 | terminationGracePeriodSeconds: 5 29 | containers: 30 | - name: server 31 | image: productcatalogservice 32 | ports: 33 | - containerPort: 3550 34 | env: 35 | - name: PORT 36 | value: "3550" 37 | # - name: DISABLE_STATS 38 | # value: "1" 39 | # - name: DISABLE_TRACING 40 | # value: "1" 41 | # - name: DISABLE_PROFILER 42 | # value: "1" 43 | # - name: JAEGER_SERVICE_ADDR 44 | # value: "jaeger-collector:14268" 45 | readinessProbe: 46 | exec: 47 | command: ["/bin/grpc_health_probe", "-addr=:3550"] 48 | livenessProbe: 49 | exec: 50 | command: ["/bin/grpc_health_probe", "-addr=:3550"] 51 | resources: 52 | requests: 53 | cpu: 100m 54 | memory: 64Mi 55 | limits: 56 | cpu: 200m 57 | memory: 128Mi 58 | --- 59 | apiVersion: v1 60 | kind: Service 61 | metadata: 62 | name: productcatalogservice 63 | spec: 64 | type: ClusterIP 65 | selector: 66 | app: productcatalogservice 67 | ports: 68 | - name: grpc 69 | port: 3550 70 | targetPort: 3550 71 | -------------------------------------------------------------------------------- /kubernetes-manifests/recommendationservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: recommendationservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: recommendationservice 23 | template: 24 | metadata: 25 | labels: 26 | app: recommendationservice 27 | spec: 28 | terminationGracePeriodSeconds: 5 29 | containers: 30 | - name: server 31 | image: recommendationservice 32 | ports: 33 | - containerPort: 8080 34 | readinessProbe: 35 | periodSeconds: 5 36 | exec: 37 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 38 | livenessProbe: 39 | periodSeconds: 5 40 | exec: 41 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 42 | env: 43 | - name: PORT 44 | value: "8080" 45 | - name: PRODUCT_CATALOG_SERVICE_ADDR 46 | value: "productcatalogservice:3550" 47 | # - name: DISABLE_TRACING 48 | # value: "1" 49 | # - name: DISABLE_PROFILER 50 | # value: "1" 51 | # - name: DISABLE_DEBUGGER 52 | # value: "1" 53 | resources: 54 | requests: 55 | cpu: 100m 56 | memory: 220Mi 57 | limits: 58 | cpu: 200m 59 | memory: 450Mi 60 | --- 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | name: recommendationservice 65 | spec: 66 | type: ClusterIP 67 | selector: 68 | app: recommendationservice 69 | ports: 70 | - name: grpc 71 | port: 8080 72 | targetPort: 8080 73 | -------------------------------------------------------------------------------- /kubernetes-manifests/redis.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: redis-cart 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: redis-cart 23 | template: 24 | metadata: 25 | labels: 26 | app: redis-cart 27 | spec: 28 | containers: 29 | - name: redis 30 | image: redis:alpine 31 | ports: 32 | - containerPort: 6379 33 | readinessProbe: 34 | periodSeconds: 5 35 | tcpSocket: 36 | port: 6379 37 | livenessProbe: 38 | periodSeconds: 5 39 | tcpSocket: 40 | port: 6379 41 | volumeMounts: 42 | - mountPath: /data 43 | name: redis-data 44 | resources: 45 | limits: 46 | memory: 256Mi 47 | cpu: 125m 48 | requests: 49 | cpu: 70m 50 | memory: 200Mi 51 | volumes: 52 | - name: redis-data 53 | emptyDir: {} 54 | --- 55 | apiVersion: v1 56 | kind: Service 57 | metadata: 58 | name: redis-cart 59 | spec: 60 | type: ClusterIP 61 | selector: 62 | app: redis-cart 63 | ports: 64 | - name: redis 65 | port: 6379 66 | targetPort: 6379 67 | -------------------------------------------------------------------------------- /kubernetes-manifests/shippingservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: shippingservice 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: shippingservice 23 | template: 24 | metadata: 25 | labels: 26 | app: shippingservice 27 | spec: 28 | containers: 29 | - name: server 30 | image: shippingservice 31 | ports: 32 | - containerPort: 50051 33 | env: 34 | - name: PORT 35 | value: "50051" 36 | # - name: DISABLE_STATS 37 | # value: "1" 38 | # - name: DISABLE_TRACING 39 | # value: "1" 40 | # - name: DISABLE_PROFILER 41 | # value: "1" 42 | # - name: JAEGER_SERVICE_ADDR 43 | # value: "jaeger-collector:14268" 44 | readinessProbe: 45 | periodSeconds: 5 46 | exec: 47 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 48 | livenessProbe: 49 | exec: 50 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 51 | resources: 52 | requests: 53 | cpu: 100m 54 | memory: 64Mi 55 | limits: 56 | cpu: 200m 57 | memory: 128Mi 58 | --- 59 | apiVersion: v1 60 | kind: Service 61 | metadata: 62 | name: shippingservice 63 | spec: 64 | type: ClusterIP 65 | selector: 66 | app: shippingservice 67 | ports: 68 | - name: grpc 69 | port: 50051 70 | targetPort: 50051 71 | -------------------------------------------------------------------------------- /pb/demo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hipstershop; 4 | 5 | // -----------------Cart service----------------- 6 | 7 | service CartService { 8 | rpc AddItem(AddItemRequest) returns (Empty) {} 9 | rpc GetCart(GetCartRequest) returns (Cart) {} 10 | rpc EmptyCart(EmptyCartRequest) returns (Empty) {} 11 | } 12 | 13 | message CartItem { 14 | string product_id = 1; 15 | int32 quantity = 2; 16 | } 17 | 18 | message AddItemRequest { 19 | string user_id = 1; 20 | CartItem item = 2; 21 | } 22 | 23 | message EmptyCartRequest { 24 | string user_id = 1; 25 | } 26 | 27 | message GetCartRequest { 28 | string user_id = 1; 29 | } 30 | 31 | message Cart { 32 | string user_id = 1; 33 | repeated CartItem items = 2; 34 | } 35 | 36 | message Empty {} 37 | 38 | // ---------------Recommendation service---------- 39 | 40 | service RecommendationService { 41 | rpc ListRecommendations(ListRecommendationsRequest) returns (ListRecommendationsResponse){} 42 | } 43 | 44 | message ListRecommendationsRequest { 45 | string user_id = 1; 46 | repeated string product_ids = 2; 47 | } 48 | 49 | message ListRecommendationsResponse { 50 | repeated string product_ids = 1; 51 | } 52 | 53 | // ---------------Product Catalog---------------- 54 | 55 | service ProductCatalogService { 56 | rpc ListProducts(Empty) returns (ListProductsResponse) {} 57 | rpc GetProduct(GetProductRequest) returns (Product) {} 58 | rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {} 59 | } 60 | 61 | message Product { 62 | string id = 1; 63 | string name = 2; 64 | string description = 3; 65 | string picture = 4; 66 | Money price_usd = 5; 67 | 68 | // Categories such as "vintage" or "gardening" that can be used to look up 69 | // other related products. 70 | repeated string categories = 6; 71 | } 72 | 73 | message ListProductsResponse { 74 | repeated Product products = 1; 75 | } 76 | 77 | message GetProductRequest { 78 | string id = 1; 79 | } 80 | 81 | message SearchProductsRequest { 82 | string query = 1; 83 | } 84 | 85 | message SearchProductsResponse { 86 | repeated Product results = 1; 87 | } 88 | 89 | // ---------------Shipping Service---------- 90 | 91 | service ShippingService { 92 | rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {} 93 | rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {} 94 | } 95 | 96 | message GetQuoteRequest { 97 | Address address = 1; 98 | repeated CartItem items = 2; 99 | } 100 | 101 | message GetQuoteResponse { 102 | Money cost_usd = 1; 103 | } 104 | 105 | message ShipOrderRequest { 106 | Address address = 1; 107 | repeated CartItem items = 2; 108 | } 109 | 110 | message ShipOrderResponse { 111 | string tracking_id = 1; 112 | } 113 | 114 | message Address { 115 | string street_address = 1; 116 | string city = 2; 117 | string state = 3; 118 | string country = 4; 119 | int32 zip_code = 5; 120 | } 121 | 122 | // -----------------Currency service----------------- 123 | 124 | service CurrencyService { 125 | rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {} 126 | rpc Convert(CurrencyConversionRequest) returns (Money) {} 127 | } 128 | 129 | // Represents an amount of money with its currency type. 130 | message Money { 131 | // The 3-letter currency code defined in ISO 4217. 132 | string currency_code = 1; 133 | 134 | // The whole units of the amount. 135 | // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. 136 | int64 units = 2; 137 | 138 | // Number of nano (10^-9) units of the amount. 139 | // The value must be between -999,999,999 and +999,999,999 inclusive. 140 | // If `units` is positive, `nanos` must be positive or zero. 141 | // If `units` is zero, `nanos` can be positive, zero, or negative. 142 | // If `units` is negative, `nanos` must be negative or zero. 143 | // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. 144 | int32 nanos = 3; 145 | } 146 | 147 | message GetSupportedCurrenciesResponse { 148 | // The 3-letter currency code defined in ISO 4217. 149 | repeated string currency_codes = 1; 150 | } 151 | 152 | message CurrencyConversionRequest { 153 | Money from = 1; 154 | 155 | // The 3-letter currency code defined in ISO 4217. 156 | string to_code = 2; 157 | } 158 | 159 | // -------------Payment service----------------- 160 | 161 | service PaymentService { 162 | rpc Charge(ChargeRequest) returns (ChargeResponse) {} 163 | } 164 | 165 | message CreditCardInfo { 166 | string credit_card_number = 1; 167 | int32 credit_card_cvv = 2; 168 | int32 credit_card_expiration_year = 3; 169 | int32 credit_card_expiration_month = 4; 170 | } 171 | 172 | message ChargeRequest { 173 | Money amount = 1; 174 | CreditCardInfo credit_card = 2; 175 | } 176 | 177 | message ChargeResponse { 178 | string transaction_id = 1; 179 | } 180 | 181 | // -------------Email service----------------- 182 | 183 | service EmailService { 184 | rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {} 185 | } 186 | 187 | message OrderItem { 188 | CartItem item = 1; 189 | Money cost = 2; 190 | } 191 | 192 | message OrderResult { 193 | string order_id = 1; 194 | string shipping_tracking_id = 2; 195 | Money shipping_cost = 3; 196 | Address shipping_address = 4; 197 | repeated OrderItem items = 5; 198 | } 199 | 200 | message SendOrderConfirmationRequest { 201 | string email = 1; 202 | OrderResult order = 2; 203 | } 204 | 205 | 206 | // -------------Checkout service----------------- 207 | 208 | service CheckoutService { 209 | rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {} 210 | } 211 | 212 | message PlaceOrderRequest { 213 | string user_id = 1; 214 | string user_currency = 2; 215 | 216 | Address address = 3; 217 | string email = 5; 218 | CreditCardInfo credit_card = 6; 219 | } 220 | 221 | message PlaceOrderResponse { 222 | OrderResult order = 1; 223 | } 224 | 225 | // ------------Ad service------------------ 226 | 227 | service AdService { 228 | rpc GetAds(AdRequest) returns (AdResponse) {} 229 | } 230 | 231 | message AdRequest { 232 | // List of important key words from the current page describing the context. 233 | repeated string context_keys = 1; 234 | } 235 | 236 | message AdResponse { 237 | repeated Ad ads = 1; 238 | } 239 | 240 | message Ad { 241 | // url to redirect to when an ad is clicked. 242 | string redirect_url = 1; 243 | 244 | // short advertisement text to display. 245 | string text = 2; 246 | } 247 | -------------------------------------------------------------------------------- /pb/grpc/health/v1/health.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The gRPC Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The canonical version of this proto can be found at 16 | // https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto 17 | 18 | syntax = "proto3"; 19 | 20 | package grpc.health.v1; 21 | 22 | option csharp_namespace = "Grpc.Health.V1"; 23 | option go_package = "google.golang.org/grpc/health/grpc_health_v1"; 24 | option java_multiple_files = true; 25 | option java_outer_classname = "HealthProto"; 26 | option java_package = "io.grpc.health.v1"; 27 | 28 | message HealthCheckRequest { 29 | string service = 1; 30 | } 31 | 32 | message HealthCheckResponse { 33 | enum ServingStatus { 34 | UNKNOWN = 0; 35 | SERVING = 1; 36 | NOT_SERVING = 2; 37 | } 38 | ServingStatus status = 1; 39 | } 40 | 41 | service Health { 42 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse); 43 | } 44 | -------------------------------------------------------------------------------- /release/istio-manifests.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # ---------------------------------------------------------- 16 | # WARNING: This file is autogenerated. Do not manually edit. 17 | # ---------------------------------------------------------- 18 | 19 | apiVersion: networking.istio.io/v1alpha3 20 | kind: Gateway 21 | metadata: 22 | name: frontend-gateway 23 | spec: 24 | selector: 25 | istio: ingressgateway # use Istio default gateway implementation 26 | servers: 27 | - port: 28 | number: 80 29 | name: http 30 | protocol: HTTP 31 | hosts: 32 | - "*" 33 | --- 34 | apiVersion: networking.istio.io/v1alpha3 35 | kind: VirtualService 36 | metadata: 37 | name: frontend-ingress 38 | spec: 39 | hosts: 40 | - "*" 41 | gateways: 42 | - frontend-gateway 43 | http: 44 | - route: 45 | - destination: 46 | host: frontend 47 | port: 48 | number: 80 49 | --- 50 | apiVersion: networking.istio.io/v1alpha3 51 | kind: VirtualService 52 | metadata: 53 | name: frontend 54 | spec: 55 | hosts: 56 | - "frontend.default.svc.cluster.local" 57 | http: 58 | - route: 59 | - destination: 60 | host: frontend 61 | port: 62 | number: 80 63 | --- 64 | apiVersion: networking.istio.io/v1alpha3 65 | kind: ServiceEntry 66 | metadata: 67 | name: whitelist-egress-googleapis 68 | spec: 69 | hosts: 70 | - "accounts.google.com" # Used to get token 71 | - "*.googleapis.com" 72 | ports: 73 | - number: 80 74 | protocol: HTTP 75 | name: http 76 | - number: 443 77 | protocol: HTTPS 78 | name: https 79 | --- 80 | apiVersion: networking.istio.io/v1alpha3 81 | kind: ServiceEntry 82 | metadata: 83 | name: whitelist-egress-google-metadata 84 | spec: 85 | hosts: 86 | - metadata.google.internal 87 | addresses: 88 | - 169.254.169.254 # GCE metadata server 89 | ports: 90 | - number: 80 91 | name: http 92 | protocol: HTTP 93 | - number: 443 94 | name: https 95 | protocol: HTTPS 96 | --- 97 | -------------------------------------------------------------------------------- /skaffold.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | apiVersion: skaffold/v1beta2 16 | kind: Config 17 | build: 18 | artifacts: 19 | # image tags are relative; to specify an image repo (e.g. GCR), you 20 | # must provide a "default repo" using one of the methods described 21 | # here: 22 | # https://skaffold.dev/docs/concepts/#image-repository-handling 23 | - image: emailservice 24 | context: src/emailservice 25 | - image: productcatalogservice 26 | context: src/productcatalogservice 27 | - image: recommendationservice 28 | context: src/recommendationservice 29 | - image: shippingservice 30 | context: src/shippingservice 31 | - image: checkoutservice 32 | context: src/checkoutservice 33 | - image: paymentservice 34 | context: src/paymentservice 35 | - image: currencyservice 36 | context: src/currencyservice 37 | - image: cartservice 38 | context: src/cartservice 39 | - image: frontend 40 | context: src/frontend 41 | - image: loadgenerator 42 | context: src/loadgenerator 43 | - image: adservice 44 | context: src/adservice 45 | tagPolicy: 46 | gitCommit: {} 47 | deploy: 48 | kubectl: 49 | manifests: 50 | - ./kubernetes-manifests/**.yaml 51 | profiles: 52 | # "gcb" profile allows building and pushing the images 53 | # on Google Container Builder without requiring docker 54 | # installed on the developer machine. However, note that 55 | # since GCB does not cache the builds, each build will 56 | # start from scratch and therefore take a long time. 57 | # 58 | # This is not used by default. To use it, run: 59 | # skaffold run -p gcb 60 | - name: gcb 61 | build: 62 | googleCloudBuild: 63 | diskSizeGb: 300 64 | machineType: N1_HIGHCPU_32 65 | timeout: 4000s 66 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # Go: for the time being we are not checking in the vendor/ directories to git 2 | # to prevent the repo from getting larger forever. In each Go service, you can 3 | # run "dep ensure --vendor-only" to download the dependencies to vendor/ based 4 | # on the Gopkg.{toml,lock} files in that directory. 5 | vendor/ 6 | -------------------------------------------------------------------------------- /src/adservice/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.ipr 3 | *.iws 4 | .gradle/** 5 | .idea/** 6 | build/** 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/adservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-slim as builder 2 | 3 | WORKDIR /app 4 | 5 | COPY ["build.gradle", "gradlew", "./"] 6 | COPY gradle gradle 7 | RUN chmod +x gradlew 8 | RUN ./gradlew downloadRepos 9 | 10 | COPY . . 11 | RUN chmod +x gradlew 12 | RUN ./gradlew installDist 13 | 14 | FROM openjdk:8-slim 15 | 16 | # Download Stackdriver Profiler Java agent 17 | RUN apt-get -y update && apt-get install -qqy \ 18 | wget \ 19 | && rm -rf /var/lib/apt/lists/* 20 | RUN mkdir -p /opt/cprof && \ 21 | wget -q -O- https://storage.googleapis.com/cloud-profiler/java/latest/profiler_java_agent.tar.gz \ 22 | | tar xzv -C /opt/cprof && \ 23 | rm -rf profiler_java_agent.tar.gz 24 | 25 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.1 && \ 26 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 27 | chmod +x /bin/grpc_health_probe 28 | 29 | WORKDIR /app 30 | COPY --from=builder /app . 31 | 32 | EXPOSE 9555 33 | ENTRYPOINT ["/app/build/install/hipstershop/bin/AdService"] 34 | -------------------------------------------------------------------------------- /src/adservice/README.md: -------------------------------------------------------------------------------- 1 | # Ad Service 2 | 3 | The Ad service provides advertisement based on context keys. If no context keys are provided then it returns random ads. 4 | 5 | ## Building locally 6 | 7 | The Ad service uses gradlew to compile/install/distribute. Gradle wrapper is already part of the source code. To build Ad Service, run: 8 | 9 | ``` 10 | ./gradlew installDist 11 | ``` 12 | It will create executable script src/adservice/build/install/hipstershop/bin/AdService 13 | 14 | ### Upgrading gradle version 15 | If you need to upgrade the version of gradle then run 16 | 17 | ``` 18 | ./gradlew wrapper --gradle-version 19 | ``` 20 | 21 | ## Building docker image 22 | 23 | From the repository root, run: 24 | 25 | ``` 26 | docker build --file src/adservice/Dockerfile . 27 | ``` 28 | 29 | -------------------------------------------------------------------------------- /src/adservice/build.gradle: -------------------------------------------------------------------------------- 1 | description = 'Ad Service' 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | mavenLocal() 7 | maven { 8 | url "https://plugins.gradle.org/m2/" 9 | } 10 | } 11 | dependencies { 12 | classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.3' 13 | classpath "gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.7.1" 14 | } 15 | } 16 | 17 | apply plugin: 'idea' 18 | apply plugin: 'java' 19 | apply plugin: 'com.google.protobuf' 20 | apply plugin: 'com.github.sherter.google-java-format' 21 | 22 | repositories { 23 | mavenCentral() 24 | mavenLocal() 25 | } 26 | 27 | group = "adservice" 28 | version = "0.1.0-SNAPSHOT" 29 | 30 | def opencensusVersion = "0.18.0" 31 | def grpcVersion = "1.17.0" 32 | def jacksonVersion = "2.9.6" 33 | 34 | tasks.withType(JavaCompile) { 35 | sourceCompatibility = '1.8' 36 | targetCompatibility = '1.8' 37 | } 38 | 39 | ext { 40 | speed = project.hasProperty('speed') ? project.getProperty('speed') : false 41 | offlineCompile = new File("$buildDir/output/lib") 42 | } 43 | 44 | dependencies { 45 | if (speed) { 46 | compile fileTree(dir: offlineCompile, include: '*.jar') 47 | } else { 48 | compile "com.google.api.grpc:proto-google-common-protos:1.12.0", 49 | "io.opencensus:opencensus-api:${opencensusVersion}", 50 | "io.opencensus:opencensus-contrib-grpc-util:${opencensusVersion}", 51 | "io.opencensus:opencensus-exporter-trace-jaeger:${opencensusVersion}", 52 | "io.opencensus:opencensus-exporter-stats-stackdriver:${opencensusVersion}", 53 | "io.opencensus:opencensus-exporter-trace-stackdriver:${opencensusVersion}", 54 | "io.opencensus:opencensus-exporter-trace-logging:${opencensusVersion}", 55 | "io.grpc:grpc-protobuf:${grpcVersion}", 56 | "io.grpc:grpc-stub:${grpcVersion}", 57 | "io.grpc:grpc-netty:${grpcVersion}", 58 | "io.grpc:grpc-services:${grpcVersion}", 59 | "org.apache.logging.log4j:log4j-core:2.11.1" 60 | 61 | runtime "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}", 62 | "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}", 63 | "io.opencensus:opencensus-contrib-log-correlation-log4j2:${opencensusVersion}", 64 | "io.opencensus:opencensus-impl:${opencensusVersion}", 65 | "io.netty:netty-tcnative-boringssl-static:2.0.8.Final" 66 | } 67 | } 68 | 69 | protobuf { 70 | protoc { 71 | artifact = 'com.google.protobuf:protoc:3.5.1-1' 72 | } 73 | plugins { 74 | grpc { 75 | artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" 76 | } 77 | } 78 | generateProtoTasks { 79 | all()*.plugins { 80 | grpc {} 81 | } 82 | ofSourceSet('main') 83 | } 84 | } 85 | 86 | googleJavaFormat { 87 | toolVersion '1.7' 88 | } 89 | 90 | // Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code. 91 | sourceSets { 92 | main { 93 | java { 94 | srcDirs 'hipstershop' 95 | srcDirs 'build/generated/source/proto/main/java/hipstershop' 96 | srcDirs 'build/generated/source/proto/main/grpc/hipstershop' 97 | } 98 | } 99 | } 100 | 101 | // Provide convenience executables for trying out the examples. 102 | apply plugin: 'application' 103 | 104 | startScripts.enabled = false 105 | 106 | // This to cache dependencies during Docker image building. First build will take time. 107 | // Subsequent build will be incremental. 108 | task downloadRepos(type: Copy) { 109 | from configurations.compile 110 | into offlineCompile 111 | from configurations.runtime 112 | into offlineCompile 113 | } 114 | 115 | task adService(type: CreateStartScripts) { 116 | mainClassName = 'hipstershop.AdService' 117 | applicationName = 'AdService' 118 | outputDir = new File(project.buildDir, 'tmp') 119 | classpath = jar.outputs.files + project.configurations.runtime 120 | defaultJvmOpts = 121 | ["-Dlog4j2.contextDataInjector=io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector", 122 | "-agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=adservice,-cprof_service_version=1.0.0"] 123 | } 124 | 125 | task adServiceClient(type: CreateStartScripts) { 126 | mainClassName = 'hipstershop.AdServiceClient' 127 | applicationName = 'AdServiceClient' 128 | outputDir = new File(project.buildDir, 'tmp') 129 | classpath = jar.outputs.files + project.configurations.runtime 130 | defaultJvmOpts = 131 | ["-Dlog4j2.contextDataInjector=io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector", 132 | "-agentpath:/opt/cprof/profiler_java_agent.so=-cprof_service=adserviceclient,-cprof_service_version=1.0.0"] 133 | } 134 | 135 | applicationDistribution.into('bin') { 136 | from(adService) 137 | from(adServiceClient) 138 | fileMode = 0755 139 | } 140 | -------------------------------------------------------------------------------- /src/adservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | # protos are needed in adservice folder for compiling during Docker build. 20 | 21 | mkdir -p proto && \ 22 | cp ../../pb/demo.proto src/main/proto 23 | -------------------------------------------------------------------------------- /src/adservice/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/src/adservice/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/adservice/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip 6 | -------------------------------------------------------------------------------- /src/adservice/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/adservice/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/adservice/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'hipstershop' 2 | -------------------------------------------------------------------------------- /src/adservice/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/cartservice/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/* 2 | /obj/* 3 | .vs/*.* 4 | -------------------------------------------------------------------------------- /src/cartservice/CartServiceImpl.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Collections.Concurrent; 16 | using System.Collections.Generic; 17 | using System.Linq; 18 | using System.Threading.Tasks; 19 | using cartservice.interfaces; 20 | using Grpc.Core; 21 | using Hipstershop; 22 | using static Hipstershop.CartService; 23 | 24 | namespace cartservice 25 | { 26 | // Cart wrapper to deal with grpc communication 27 | internal class CartServiceImpl : CartServiceBase 28 | { 29 | private ICartStore cartStore; 30 | private readonly static Empty Empty = new Empty(); 31 | 32 | public CartServiceImpl(ICartStore cartStore) 33 | { 34 | this.cartStore = cartStore; 35 | } 36 | 37 | public async override Task AddItem(AddItemRequest request, Grpc.Core.ServerCallContext context) 38 | { 39 | await cartStore.AddItemAsync(request.UserId, request.Item.ProductId, request.Item.Quantity); 40 | return Empty; 41 | } 42 | 43 | public async override Task EmptyCart(EmptyCartRequest request, ServerCallContext context) 44 | { 45 | await cartStore.EmptyCartAsync(request.UserId); 46 | return Empty; 47 | } 48 | 49 | public override Task GetCart(GetCartRequest request, ServerCallContext context) 50 | { 51 | return cartStore.GetCartAsync(request.UserId); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/cartservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-sdk-alpine as builder 2 | WORKDIR /app 3 | COPY . . 4 | RUN dotnet restore && \ 5 | dotnet build && \ 6 | dotnet publish -c release -r linux-musl-x64 -o /cartservice 7 | 8 | # cartservice 9 | FROM alpine:3.8 10 | 11 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 12 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 13 | chmod +x /bin/grpc_health_probe 14 | 15 | # Dependencies for runtime 16 | # busybox-extras => telnet 17 | RUN apk add --no-cache \ 18 | busybox-extras \ 19 | libc6-compat \ 20 | libunwind \ 21 | libuuid \ 22 | libgcc \ 23 | libstdc++ \ 24 | libintl \ 25 | icu 26 | WORKDIR /app 27 | COPY --from=builder /cartservice . 28 | ENTRYPOINT ["./cartservice", "start"] 29 | -------------------------------------------------------------------------------- /src/cartservice/HealthImpl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using cartservice.interfaces; 4 | using Grpc.Core; 5 | using Grpc.Health.V1; 6 | using StackExchange.Redis; 7 | using static Grpc.Health.V1.Health; 8 | 9 | namespace cartservice { 10 | internal class HealthImpl : HealthBase { 11 | private ICartStore dependency { get; } 12 | public HealthImpl (ICartStore dependency) { 13 | this.dependency = dependency; 14 | } 15 | 16 | public override Task Check(HealthCheckRequest request, ServerCallContext context){ 17 | Console.WriteLine ("Checking CartService Health"); 18 | return Task.FromResult(new HealthCheckResponse { 19 | Status = dependency.Ping() ? HealthCheckResponse.Types.ServingStatus.Serving : HealthCheckResponse.Types.ServingStatus.NotServing 20 | }); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/cartservice/cartservice.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Always 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/cartservice/cartstore/LocalCartStore.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Concurrent; 17 | using System.Threading.Tasks; 18 | using System.Linq; 19 | using cartservice.interfaces; 20 | using Hipstershop; 21 | 22 | namespace cartservice.cartstore 23 | { 24 | internal class LocalCartStore : ICartStore 25 | { 26 | // Maps between user and their cart 27 | private ConcurrentDictionary userCartItems = new ConcurrentDictionary(); 28 | private readonly Hipstershop.Cart emptyCart = new Hipstershop.Cart(); 29 | 30 | public Task InitializeAsync() 31 | { 32 | Console.WriteLine("Local Cart Store was initialized"); 33 | 34 | return Task.CompletedTask; 35 | } 36 | 37 | public Task AddItemAsync(string userId, string productId, int quantity) 38 | { 39 | Console.WriteLine($"AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity}"); 40 | var newCart = new Hipstershop.Cart 41 | { 42 | UserId = userId, 43 | Items = { new Hipstershop.CartItem { ProductId = productId, Quantity = quantity } } 44 | }; 45 | userCartItems.AddOrUpdate(userId, newCart, 46 | (k, exVal) => 47 | { 48 | // If the item exists, we update its quantity 49 | var existingItem = exVal.Items.SingleOrDefault(item => item.ProductId == productId); 50 | if (existingItem != null) 51 | { 52 | existingItem.Quantity += quantity; 53 | } 54 | else 55 | { 56 | exVal.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity }); 57 | } 58 | 59 | return exVal; 60 | }); 61 | 62 | return Task.CompletedTask; 63 | } 64 | 65 | public Task EmptyCartAsync(string userId) 66 | { 67 | Console.WriteLine($"EmptyCartAsync called with userId={userId}"); 68 | userCartItems[userId] = new Hipstershop.Cart(); 69 | 70 | return Task.CompletedTask; 71 | } 72 | 73 | public Task GetCartAsync(string userId) 74 | { 75 | Console.WriteLine($"GetCartAsync called with userId={userId}"); 76 | Hipstershop.Cart cart = null; 77 | if (!userCartItems.TryGetValue(userId, out cart)) 78 | { 79 | Console.WriteLine($"No carts for user {userId}"); 80 | return Task.FromResult(emptyCart); 81 | } 82 | 83 | return Task.FromResult(cart); 84 | } 85 | 86 | public bool Ping() 87 | { 88 | return true; 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/cartservice/genproto.bat: -------------------------------------------------------------------------------- 1 | @REM Copyright 2018 Google LLC 2 | @REM 3 | @REM Licensed under the Apache License, Version 2.0 (the "License"); 4 | @REM you may not use this file except in compliance with the License. 5 | @REM You may obtain a copy of the License at 6 | @REM 7 | @REM http://www.apache.org/licenses/LICENSE-2.0 8 | @REM 9 | @REM Unless required by applicable law or agreed to in writing, software 10 | @REM distributed under the License is distributed on an "AS IS" BASIS, 11 | @REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | @REM See the License for the specific language governing permissions and 13 | @REM limitations under the License. 14 | 15 | @rem Generate the C# code for .proto files 16 | 17 | setlocal 18 | 19 | @rem enter this directory 20 | cd /d %~dp0 21 | 22 | set NUGET_PATH=%UserProfile%\.nuget\packages 23 | set TOOLS_PATH=%NUGET_PATH%\Grpc.Tools\1.12.0\tools\windows_x64 24 | 25 | %TOOLS_PATH%\protoc.exe -I%~dp0/../../pb;%NUGET_PATH%\google.protobuf.tools\3.5.1\tools\ --csharp_out %~dp0\grpc_generated %~dp0\..\..\pb\demo.proto --grpc_out %~dp0\grpc_generated --plugin=protoc-gen-grpc=%TOOLS_PATH%\grpc_csharp_plugin.exe 26 | 27 | endlocal 28 | -------------------------------------------------------------------------------- /src/cartservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Generate the C# code for .proto files 18 | set -e 19 | 20 | PROTODIR=../../pb 21 | 22 | # enter this directory 23 | CWD="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 24 | 25 | protoc --csharp_out=$CWD/grpc_generated -I $PROTODIR $PROTODIR/demo.proto 26 | 27 | -------------------------------------------------------------------------------- /src/cartservice/interfaces/ICartStore.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Threading.Tasks; 16 | 17 | namespace cartservice.interfaces 18 | { 19 | internal interface ICartStore 20 | { 21 | Task InitializeAsync(); 22 | 23 | Task AddItemAsync(string userId, string productId, int quantity); 24 | Task EmptyCartAsync(string userId); 25 | 26 | Task GetCartAsync(string userId); 27 | 28 | bool Ping(); 29 | } 30 | } -------------------------------------------------------------------------------- /src/cartservice/scripts/build_image.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | @REM Copyright 2018 Google LLC 3 | @REM 4 | @REM Licensed under the Apache License, Version 2.0 (the "License"); 5 | @REM you may not use this file except in compliance with the License. 6 | @REM You may obtain a copy of the License at 7 | @REM 8 | @REM http://www.apache.org/licenses/LICENSE-2.0 9 | @REM 10 | @REM Unless required by applicable law or agreed to in writing, software 11 | @REM distributed under the License is distributed on an "AS IS" BASIS, 12 | @REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @REM See the License for the specific language governing permissions and 14 | @REM limitations under the License. 15 | 16 | echo building container image for cart service 17 | docker build -t cartservice ..\. 18 | 19 | echo running the image, mapping the port 20 | rem echo docker run -it --rm -p 5000:8080 --name 21 | -------------------------------------------------------------------------------- /src/cartservice/scripts/docker_setup.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | @REM Copyright 2018 Google LLC 3 | @REM 4 | @REM Licensed under the Apache License, Version 2.0 (the "License"); 5 | @REM you may not use this file except in compliance with the License. 6 | @REM You may obtain a copy of the License at 7 | @REM 8 | @REM http://www.apache.org/licenses/LICENSE-2.0 9 | @REM 10 | @REM Unless required by applicable law or agreed to in writing, software 11 | @REM distributed under the License is distributed on an "AS IS" BASIS, 12 | @REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @REM See the License for the specific language governing permissions and 14 | @REM limitations under the License. 15 | 16 | set ENV=%1 17 | 18 | IF %ENV%==local GOTO local 19 | IF %ENV%==docker GOTO docker_local 20 | GOTO End1 21 | 22 | :local 23 | set REDIS_PORT=6379 24 | set REDIS_ADDR=localhost:%REDIS_PORT% 25 | set LISTEN_ADDR=localhost 26 | set PORT=7070 27 | set GRPC_TRACE=all 28 | 29 | echo running redis emulator locally on a separate window 30 | taskkill /f /im "redis-server.exe" 31 | start redis-server "C:\ProgramData\chocolatey\lib\redis-64\redis.windows.conf" 32 | 33 | echo running the cart service locally 34 | dotnet build ..\. 35 | dotnet run --project ../cartservice.csproj start 36 | GOTO End1 37 | 38 | :docker_local 39 | set REDIS_PORT=6379 40 | rem set REDIS_ADDR=redis:%REDIS_PORT% 41 | set LISTEN_ADDR=localhost 42 | set PORT=7070 43 | 44 | echo run docker container with redis 45 | 46 | echo Forcing to remove redis cache so we always start the container from scratch 47 | docker rm --force redis > nul 2>&1 48 | echo Starting out redis container 49 | docker run -d --name=redis redis > nul 2>&1 50 | rem This assigns the output of ip4 addr of redis container into REDIS_ADDR 51 | FOR /F "tokens=*" %%g IN ('docker inspect -f "{{ .NetworkSettings.Networks.bridge.IPAddress }}" redis') do (SET REDIS_ADDR=%%g) 52 | echo addr=%REDIS_ADDR% 53 | echo building container image for cart service 54 | docker build -t cartservice ..\. 55 | 56 | echo run container image for cart service 57 | docker run -it --name=cartservice --rm -e REDIS_ADDR=%REDIS_ADDR%:%REDIS_PORT% -e LISTEN_ADDR=%LISTEN_ADDR% -e PORT=%PORT% -p %PORT%:%PORT% cartservice 58 | 59 | GOTO End1 60 | 61 | :End1 62 | 63 | rem run docker container with cart service 64 | rem docker run -it --rm -e REDIS_ADDR=%REDIS_ADDR%:%REDIS_PORT% -e CART_SERVICE_ADDR=%CART_SERVICE_ADDR% -e CART_SERVICE_PORT=%CART_SERVICE_PORT% -p %CART_SERVICE_PORT%:%CART_SERVICE_PORT% cartservice 65 | rem -e GRPC_TRACE=all -e GRPC_VERBOSITY=debug 66 | -------------------------------------------------------------------------------- /src/cartservice/scripts/run_redis_emulator_windows.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | @REM Copyright 2018 Google LLC 3 | @REM 4 | @REM Licensed under the Apache License, Version 2.0 (the "License"); 5 | @REM you may not use this file except in compliance with the License. 6 | @REM You may obtain a copy of the License at 7 | @REM 8 | @REM http://www.apache.org/licenses/LICENSE-2.0 9 | @REM 10 | @REM Unless required by applicable law or agreed to in writing, software 11 | @REM distributed under the License is distributed on an "AS IS" BASIS, 12 | @REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @REM See the License for the specific language governing permissions and 14 | @REM limitations under the License. 15 | 16 | rem install redis on windows using choco 17 | rem choco install redis-64 18 | 19 | rem run redis 20 | redis-server --daemonize yes 21 | 22 | rem testing locally 23 | rem redis-cli 24 | rem SET foo bar 25 | rem GET foo 26 | -------------------------------------------------------------------------------- /src/checkoutservice/.dockerignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /src/checkoutservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12-alpine as builder 2 | RUN apk add --no-cache ca-certificates git && \ 3 | wget -qO/go/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 && \ 4 | chmod +x /go/bin/dep 5 | 6 | ENV PROJECT github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice 7 | WORKDIR /go/src/$PROJECT 8 | 9 | # restore dependencies 10 | COPY Gopkg.* ./ 11 | RUN dep ensure --vendor-only -v 12 | 13 | COPY . . 14 | RUN go build -gcflags='-N -l' -o /checkoutservice . 15 | 16 | FROM alpine as release 17 | RUN apk add --no-cache ca-certificates 18 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 19 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 20 | chmod +x /bin/grpc_health_probe 21 | COPY --from=builder /checkoutservice /checkoutservice 22 | EXPOSE 5050 23 | ENTRYPOINT ["/checkoutservice"] 24 | -------------------------------------------------------------------------------- /src/checkoutservice/Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "cloud.google.com/go" 30 | version = "0.40.0" 31 | 32 | [[constraint]] 33 | name = "contrib.go.opencensus.io/exporter/stackdriver" 34 | version = "0.5.0" 35 | 36 | [[constraint]] 37 | name = "github.com/golang/protobuf" 38 | version = "1.2.0" 39 | 40 | [[constraint]] 41 | name = "github.com/google/uuid" 42 | version = "1.0.0" 43 | 44 | [[constraint]] 45 | name = "github.com/sirupsen/logrus" 46 | version = "1.0.6" 47 | 48 | [[constraint]] 49 | name = "go.opencensus.io" 50 | version = "0.16.0" 51 | 52 | [[constraint]] 53 | branch = "master" 54 | name = "golang.org/x/net" 55 | 56 | [prune] 57 | go-tests = true 58 | unused-packages = true 59 | -------------------------------------------------------------------------------- /src/checkoutservice/README.md: -------------------------------------------------------------------------------- 1 | # checkoutservice 2 | 3 | Run the following command to restore dependencies to `vendor/` directory: 4 | 5 | dep ensure --vendor-only 6 | -------------------------------------------------------------------------------- /src/checkoutservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | PATH=$PATH:$GOPATH/bin 20 | protodir=../../pb 21 | 22 | protoc --go_out=plugins=grpc:genproto -I $protodir $protodir/demo.proto 23 | -------------------------------------------------------------------------------- /src/checkoutservice/money/money.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package money 16 | 17 | import ( 18 | "errors" 19 | 20 | pb "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/genproto" 21 | ) 22 | 23 | const ( 24 | nanosMin = -999999999 25 | nanosMax = +999999999 26 | nanosMod = 1000000000 27 | ) 28 | 29 | var ( 30 | ErrInvalidValue = errors.New("one of the specified money values is invalid") 31 | ErrMismatchingCurrency = errors.New("mismatching currency codes") 32 | ) 33 | 34 | // IsValid checks if specified value has a valid units/nanos signs and ranges. 35 | func IsValid(m pb.Money) bool { 36 | return signMatches(m) && validNanos(m.GetNanos()) 37 | } 38 | 39 | func signMatches(m pb.Money) bool { 40 | return m.GetNanos() == 0 || m.GetUnits() == 0 || (m.GetNanos() < 0) == (m.GetUnits() < 0) 41 | } 42 | 43 | func validNanos(nanos int32) bool { return nanosMin <= nanos && nanos <= nanosMax } 44 | 45 | // IsZero returns true if the specified money value is equal to zero. 46 | func IsZero(m pb.Money) bool { return m.GetUnits() == 0 && m.GetNanos() == 0 } 47 | 48 | // IsPositive returns true if the specified money value is valid and is 49 | // positive. 50 | func IsPositive(m pb.Money) bool { 51 | return IsValid(m) && m.GetUnits() > 0 || (m.GetUnits() == 0 && m.GetNanos() > 0) 52 | } 53 | 54 | // IsNegative returns true if the specified money value is valid and is 55 | // negative. 56 | func IsNegative(m pb.Money) bool { 57 | return IsValid(m) && m.GetUnits() < 0 || (m.GetUnits() == 0 && m.GetNanos() < 0) 58 | } 59 | 60 | // AreSameCurrency returns true if values l and r have a currency code and 61 | // they are the same values. 62 | func AreSameCurrency(l, r pb.Money) bool { 63 | return l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetCurrencyCode() != "" 64 | } 65 | 66 | // AreEquals returns true if values l and r are the equal, including the 67 | // currency. This does not check validity of the provided values. 68 | func AreEquals(l, r pb.Money) bool { 69 | return l.GetCurrencyCode() == r.GetCurrencyCode() && 70 | l.GetUnits() == r.GetUnits() && l.GetNanos() == r.GetNanos() 71 | } 72 | 73 | // Negate returns the same amount with the sign negated. 74 | func Negate(m pb.Money) pb.Money { 75 | return pb.Money{ 76 | Units: -m.GetUnits(), 77 | Nanos: -m.GetNanos(), 78 | CurrencyCode: m.GetCurrencyCode()} 79 | } 80 | 81 | // Must panics if the given error is not nil. This can be used with other 82 | // functions like: "m := Must(Sum(a,b))". 83 | func Must(v pb.Money, err error) pb.Money { 84 | if err != nil { 85 | panic(err) 86 | } 87 | return v 88 | } 89 | 90 | // Sum adds two values. Returns an error if one of the values are invalid or 91 | // currency codes are not matching (unless currency code is unspecified for 92 | // both). 93 | func Sum(l, r pb.Money) (pb.Money, error) { 94 | if !IsValid(l) || !IsValid(r) { 95 | return pb.Money{}, ErrInvalidValue 96 | } else if l.GetCurrencyCode() != r.GetCurrencyCode() { 97 | return pb.Money{}, ErrMismatchingCurrency 98 | } 99 | units := l.GetUnits() + r.GetUnits() 100 | nanos := l.GetNanos() + r.GetNanos() 101 | 102 | if (units == 0 && nanos == 0) || (units > 0 && nanos >= 0) || (units < 0 && nanos <= 0) { 103 | // same sign 104 | units += int64(nanos / nanosMod) 105 | nanos = nanos % nanosMod 106 | } else { 107 | // different sign. nanos guaranteed to not to go over the limit 108 | if units > 0 { 109 | units-- 110 | nanos += nanosMod 111 | } else { 112 | units++ 113 | nanos -= nanosMod 114 | } 115 | } 116 | 117 | return pb.Money{ 118 | Units: units, 119 | Nanos: nanos, 120 | CurrencyCode: l.GetCurrencyCode()}, nil 121 | } 122 | 123 | // MultiplySlow is a slow multiplication operation done through adding the value 124 | // to itself n-1 times. 125 | func MultiplySlow(m pb.Money, n uint32) pb.Money { 126 | out := m 127 | for n > 1 { 128 | out = Must(Sum(out, m)) 129 | n-- 130 | } 131 | return out 132 | } 133 | -------------------------------------------------------------------------------- /src/currencyservice/.dockerignore: -------------------------------------------------------------------------------- 1 | client.js 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /src/currencyservice/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /src/currencyservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8-alpine as base 2 | 3 | FROM base as builder 4 | 5 | # Some packages (e.g. @google-cloud/profiler) require additional 6 | # deps for post-install scripts 7 | RUN apk add --update --no-cache \ 8 | python \ 9 | make \ 10 | g++ 11 | 12 | WORKDIR /usr/src/app 13 | 14 | COPY package*.json ./ 15 | 16 | RUN npm install --only=production 17 | 18 | FROM base 19 | 20 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 21 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 22 | chmod +x /bin/grpc_health_probe 23 | 24 | WORKDIR /usr/src/app 25 | 26 | COPY --from=builder /usr/src/app/node_modules ./node_modules 27 | 28 | COPY . . 29 | 30 | EXPOSE 7000 31 | 32 | ENTRYPOINT [ "node", "server.js" ] 33 | -------------------------------------------------------------------------------- /src/currencyservice/client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2015 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | require('@google-cloud/trace-agent').start(); 19 | 20 | const path = require('path'); 21 | const grpc = require('grpc'); 22 | const leftPad = require('left-pad'); 23 | const pino = require('pino'); 24 | 25 | const PROTO_PATH = path.join(__dirname, './proto/demo.proto'); 26 | const PORT = 7000; 27 | 28 | const shopProto = grpc.load(PROTO_PATH).hipstershop; 29 | const client = new shopProto.CurrencyService(`localhost:${PORT}`, 30 | grpc.credentials.createInsecure()); 31 | 32 | const logger = pino({ 33 | name: 'currencyservice-client', 34 | messageKey: 'message', 35 | changeLevelName: 'severity', 36 | useLevelLabels: true 37 | }); 38 | 39 | const request = { 40 | from: { 41 | currency_code: 'CHF', 42 | units: 300, 43 | nanos: 0 44 | }, 45 | to_code: 'EUR' 46 | }; 47 | 48 | function _moneyToString (m) { 49 | return `${m.units}.${m.nanos.toString().padStart(9,'0')} ${m.currency_code}`; 50 | } 51 | 52 | client.getSupportedCurrencies({}, (err, response) => { 53 | if (err) { 54 | logger.error(`Error in getSupportedCurrencies: ${err}`); 55 | } else { 56 | logger.info(`Currency codes: ${response.currency_codes}`); 57 | } 58 | }); 59 | 60 | client.convert(request, (err, response) => { 61 | if (err) { 62 | logger.error(`Error in convert: ${err}`); 63 | } else { 64 | logger.log(`Convert: ${_moneyToString(request.from)} to ${_moneyToString(response)}`); 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /src/currencyservice/data/currency_conversion.json: -------------------------------------------------------------------------------- 1 | { 2 | "EUR": "1.0", 3 | "USD": "1.1305", 4 | "JPY": "126.40", 5 | "BGN": "1.9558", 6 | "CZK": "25.592", 7 | "DKK": "7.4609", 8 | "GBP": "0.85970", 9 | "HUF": "315.51", 10 | "PLN": "4.2996", 11 | "RON": "4.7463", 12 | "SEK": "10.5375", 13 | "CHF": "1.1360", 14 | "ISK": "136.80", 15 | "NOK": "9.8040", 16 | "HRK": "7.4210", 17 | "RUB": "74.4208", 18 | "TRY": "6.1247", 19 | "AUD": "1.6072", 20 | "BRL": "4.2682", 21 | "CAD": "1.5128", 22 | "CNY": "7.5857", 23 | "HKD": "8.8743", 24 | "IDR": "15999.40", 25 | "ILS": "4.0875", 26 | "INR": "79.4320", 27 | "KRW": "1275.05", 28 | "MXN": "21.7999", 29 | "MYR": "4.6289", 30 | "NZD": "1.6679", 31 | "PHP": "59.083", 32 | "SGD": "1.5349", 33 | "THB": "36.012", 34 | "ZAR": "16.0583" 35 | } -------------------------------------------------------------------------------- /src/currencyservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # protos are loaded dynamically for node, simply copies over the proto. 18 | mkdir -p proto 19 | cp -r ../../pb/* ./proto 20 | -------------------------------------------------------------------------------- /src/currencyservice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grpc-currency-service", 3 | "version": "0.1.0", 4 | "description": "A gRPC currency conversion microservice", 5 | "repository": "https://github.com/GoogleCloudPlatform/microservices-demo", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "lint": "semistandard *.js" 9 | }, 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "@google-cloud/debug-agent": "^4.0.1", 13 | "@google-cloud/profiler": "^2.0.2", 14 | "@google-cloud/trace-agent": "4.0.1", 15 | "@grpc/proto-loader": "^0.3.0", 16 | "async": "^1.5.2", 17 | "google-protobuf": "^3.0.0", 18 | "grpc": "^1.22.2", 19 | "pino": "^5.6.2", 20 | "request": "^2.87.0", 21 | "xml2js": "^0.4.19" 22 | }, 23 | "devDependencies": { 24 | "semistandard": "^12.0.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/currencyservice/proto/grpc/health/v1/health.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The gRPC Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The canonical version of this proto can be found at 16 | // https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto 17 | 18 | syntax = "proto3"; 19 | 20 | package grpc.health.v1; 21 | 22 | option csharp_namespace = "Grpc.Health.V1"; 23 | option go_package = "google.golang.org/grpc/health/grpc_health_v1"; 24 | option java_multiple_files = true; 25 | option java_outer_classname = "HealthProto"; 26 | option java_package = "io.grpc.health.v1"; 27 | 28 | message HealthCheckRequest { 29 | string service = 1; 30 | } 31 | 32 | message HealthCheckResponse { 33 | enum ServingStatus { 34 | UNKNOWN = 0; 35 | SERVING = 1; 36 | NOT_SERVING = 2; 37 | } 38 | ServingStatus status = 1; 39 | } 40 | 41 | service Health { 42 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse); 43 | } 44 | -------------------------------------------------------------------------------- /src/currencyservice/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | if(process.env.DISABLE_PROFILER) { 18 | console.log("Profiler disabled.") 19 | } 20 | else { 21 | console.log("Profiler enabled.") 22 | require('@google-cloud/profiler').start({ 23 | serviceContext: { 24 | service: 'currencyservice', 25 | version: '1.0.0' 26 | } 27 | }); 28 | } 29 | 30 | 31 | if(process.env.DISABLE_TRACING) { 32 | console.log("Tracing disabled.") 33 | } 34 | else { 35 | console.log("Tracing enabled.") 36 | require('@google-cloud/trace-agent').start(); 37 | } 38 | 39 | if(process.env.DISABLE_DEBUGGER) { 40 | console.log("Debugger disabled.") 41 | } 42 | else { 43 | console.log("Debugger enabled.") 44 | require('@google-cloud/debug-agent').start({ 45 | serviceContext: { 46 | service: 'currencyservice', 47 | version: 'VERSION' 48 | } 49 | }); 50 | } 51 | 52 | const path = require('path'); 53 | const grpc = require('grpc'); 54 | const pino = require('pino'); 55 | const protoLoader = require('@grpc/proto-loader'); 56 | 57 | const MAIN_PROTO_PATH = path.join(__dirname, './proto/demo.proto'); 58 | const HEALTH_PROTO_PATH = path.join(__dirname, './proto/grpc/health/v1/health.proto'); 59 | 60 | const PORT = process.env.PORT; 61 | 62 | const shopProto = _loadProto(MAIN_PROTO_PATH).hipstershop; 63 | const healthProto = _loadProto(HEALTH_PROTO_PATH).grpc.health.v1; 64 | 65 | const logger = pino({ 66 | name: 'currencyservice-server', 67 | messageKey: 'message', 68 | changeLevelName: 'severity', 69 | useLevelLabels: true 70 | }); 71 | 72 | /** 73 | * Helper function that loads a protobuf file. 74 | */ 75 | function _loadProto (path) { 76 | const packageDefinition = protoLoader.loadSync( 77 | path, 78 | { 79 | keepCase: true, 80 | longs: String, 81 | enums: String, 82 | defaults: true, 83 | oneofs: true 84 | } 85 | ); 86 | return grpc.loadPackageDefinition(packageDefinition); 87 | } 88 | 89 | /** 90 | * Helper function that gets currency data from a stored JSON file 91 | * Uses public data from European Central Bank 92 | */ 93 | function _getCurrencyData (callback) { 94 | const data = require('./data/currency_conversion.json'); 95 | callback(data); 96 | } 97 | 98 | /** 99 | * Helper function that handles decimal/fractional carrying 100 | */ 101 | function _carry (amount) { 102 | const fractionSize = Math.pow(10, 9); 103 | amount.nanos += (amount.units % 1) * fractionSize; 104 | amount.units = Math.floor(amount.units) + Math.floor(amount.nanos / fractionSize); 105 | amount.nanos = amount.nanos % fractionSize; 106 | return amount; 107 | } 108 | 109 | /** 110 | * Lists the supported currencies 111 | */ 112 | function getSupportedCurrencies (call, callback) { 113 | logger.info('Getting supported currencies...'); 114 | _getCurrencyData((data) => { 115 | callback(null, {currency_codes: Object.keys(data)}); 116 | }); 117 | } 118 | 119 | /** 120 | * Converts between currencies 121 | */ 122 | function convert (call, callback) { 123 | logger.info('received conversion request'); 124 | try { 125 | _getCurrencyData((data) => { 126 | const request = call.request; 127 | 128 | // Convert: from_currency --> EUR 129 | const from = request.from; 130 | const euros = _carry({ 131 | units: from.units / data[from.currency_code], 132 | nanos: from.nanos / data[from.currency_code] 133 | }); 134 | 135 | euros.nanos = Math.round(euros.nanos); 136 | 137 | // Convert: EUR --> to_currency 138 | const result = _carry({ 139 | units: euros.units * data[request.to_code], 140 | nanos: euros.nanos * data[request.to_code] 141 | }); 142 | 143 | result.units = Math.floor(result.units); 144 | result.nanos = Math.floor(result.nanos); 145 | result.currency_code = request.to_code; 146 | 147 | logger.info(`conversion request successful`); 148 | callback(null, result); 149 | }); 150 | } catch (err) { 151 | logger.error(`conversion request failed: ${err}`); 152 | callback(err.message); 153 | } 154 | } 155 | 156 | /** 157 | * Endpoint for health checks 158 | */ 159 | function check (call, callback) { 160 | callback(null, { status: 'SERVING' }); 161 | } 162 | 163 | /** 164 | * Starts an RPC server that receives requests for the 165 | * CurrencyConverter service at the sample server port 166 | */ 167 | function main () { 168 | logger.info(`Starting gRPC server on port ${PORT}...`); 169 | const server = new grpc.Server(); 170 | server.addService(shopProto.CurrencyService.service, {getSupportedCurrencies, convert}); 171 | server.addService(healthProto.Health.service, {check}); 172 | server.bind(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure()); 173 | server.start(); 174 | } 175 | 176 | main(); 177 | -------------------------------------------------------------------------------- /src/emailservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-slim as base 2 | 3 | FROM base as builder 4 | 5 | RUN apt-get -qq update \ 6 | && apt-get install -y --no-install-recommends \ 7 | g++ \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | # get packages 11 | COPY requirements.txt . 12 | RUN pip install -r requirements.txt 13 | 14 | FROM base as final 15 | # Enable unbuffered logging 16 | ENV PYTHONUNBUFFERED=1 17 | # Enable Profiler 18 | ENV ENABLE_PROFILER=1 19 | 20 | RUN apt-get -qq update \ 21 | && apt-get install -y --no-install-recommends \ 22 | wget 23 | 24 | # Download the grpc health probe 25 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 26 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 27 | chmod +x /bin/grpc_health_probe 28 | 29 | WORKDIR /email_server 30 | 31 | # Grab packages from builder 32 | COPY --from=builder /usr/local/lib/python3.7/ /usr/local/lib/python3.7/ 33 | 34 | # Add the application 35 | COPY . . 36 | 37 | EXPOSE 8080 38 | ENTRYPOINT [ "python", "email_server.py" ] 39 | -------------------------------------------------------------------------------- /src/emailservice/email_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import grpc 18 | 19 | import demo_pb2 20 | import demo_pb2_grpc 21 | 22 | from logger import getJSONLogger 23 | logger = getJSONLogger('emailservice-client') 24 | 25 | from opencensus.trace.tracer import Tracer 26 | from opencensus.trace.exporters import stackdriver_exporter 27 | from opencensus.trace.ext.grpc import client_interceptor 28 | 29 | try: 30 | exporter = stackdriver_exporter.StackdriverExporter() 31 | tracer = Tracer(exporter=exporter) 32 | tracer_interceptor = client_interceptor.OpenCensusClientInterceptor(tracer, host_port='0.0.0.0:8080') 33 | except: 34 | tracer_interceptor = client_interceptor.OpenCensusClientInterceptor() 35 | 36 | def send_confirmation_email(email, order): 37 | channel = grpc.insecure_channel('0.0.0.0:8080') 38 | channel = grpc.intercept_channel(channel, tracer_interceptor) 39 | stub = demo_pb2_grpc.EmailServiceStub(channel) 40 | try: 41 | response = stub.SendOrderConfirmation(demo_pb2.SendOrderConfirmationRequest( 42 | email = email, 43 | order = order 44 | )) 45 | logger.info('Request sent.') 46 | except grpc.RpcError as err: 47 | logger.error(err.details()) 48 | logger.error('{}, {}'.format(err.code().name, err.code().value)) 49 | 50 | if __name__ == '__main__': 51 | logger.info('Client for email service.') 52 | -------------------------------------------------------------------------------- /src/emailservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | python -m grpc_tools.protoc -I../../pb --python_out=. --grpc_python_out=. ../../pb/demo.proto 20 | -------------------------------------------------------------------------------- /src/emailservice/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import logging 18 | import sys 19 | from pythonjsonlogger import jsonlogger 20 | 21 | # TODO(yoshifumi) this class is duplicated since other Python services are 22 | # not sharing the modules for logging. 23 | class CustomJsonFormatter(jsonlogger.JsonFormatter): 24 | def add_fields(self, log_record, record, message_dict): 25 | super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) 26 | if not log_record.get('timestamp'): 27 | log_record['timestamp'] = record.created 28 | if log_record.get('severity'): 29 | log_record['severity'] = log_record['severity'].upper() 30 | else: 31 | log_record['severity'] = record.levelname 32 | 33 | def getJSONLogger(name): 34 | logger = logging.getLogger(name) 35 | handler = logging.StreamHandler(sys.stdout) 36 | formatter = CustomJsonFormatter('(timestamp) (severity) (name) (message)') 37 | handler.setFormatter(formatter) 38 | logger.addHandler(handler) 39 | logger.setLevel(logging.INFO) 40 | logger.propagate = False 41 | return logger 42 | -------------------------------------------------------------------------------- /src/emailservice/requirements.in: -------------------------------------------------------------------------------- 1 | google-api-core==1.6.0 2 | grpcio-health-checking==1.12.1 3 | grpcio==1.16.1 4 | jinja2==2.10 5 | opencensus[stackdriver]==0.1.10 6 | python-json-logger==0.1.9 7 | google-cloud-profiler==1.0.8 8 | -------------------------------------------------------------------------------- /src/emailservice/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements.txt requirements.in 6 | # 7 | cachetools==3.0.0 # via google-auth 8 | certifi==2018.11.29 # via requests 9 | chardet==3.0.4 # via requests 10 | google-api-core[grpc]==1.6.0 11 | google-api-python-client==1.7.8 # via google-cloud-profiler 12 | google-auth-httplib2==0.0.3 # via google-api-python-client, google-cloud-profiler 13 | google-auth==1.6.2 # via google-api-core, google-api-python-client, google-auth-httplib2, google-cloud-profiler 14 | google-cloud-core==0.29.1 # via google-cloud-trace 15 | google-cloud-profiler==1.0.8 16 | google-cloud-trace==0.20.2 # via opencensus 17 | googleapis-common-protos==1.5.5 # via google-api-core 18 | grpcio-health-checking==1.12.1 19 | grpcio==1.16.1 20 | httplib2==0.12.1 # via google-api-python-client, google-auth-httplib2 21 | idna==2.8 # via requests 22 | jinja2==2.10 23 | markupsafe==1.1.0 # via jinja2 24 | opencensus[stackdriver]==0.1.10 25 | protobuf==3.6.1 # via google-api-core, google-cloud-profiler, googleapis-common-protos, grpcio-health-checking 26 | pyasn1-modules==0.2.3 # via google-auth 27 | pyasn1==0.4.5 # via pyasn1-modules, rsa 28 | python-json-logger==0.1.9 29 | pytz==2018.9 # via google-api-core 30 | requests==2.21.0 # via google-api-core, google-cloud-profiler 31 | rsa==4.0 # via google-auth 32 | six==1.12.0 # via google-api-core, google-api-python-client, google-auth, grpcio, protobuf 33 | uritemplate==3.0.0 # via google-api-python-client 34 | urllib3==1.24.1 # via requests 35 | -------------------------------------------------------------------------------- /src/emailservice/templates/confirmation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Your Order Confirmation 5 | 6 | 7 | 12 | 13 |

Your Order Confirmation

14 |

Thanks for shopping with us!

15 |

Order ID

16 |

#{{ order.order_id }}

17 |

Shipping

18 |

#{{ order.shipping_tracking_id }}

19 |

{{ order.shipping_cost.units }}. {{ "%02d" | format(order.shipping_cost.nanos // 10000000) }} {{ order.shipping_cost.currency_code }}

20 |

{{ order.shipping_address.street_address_1 }}, {{order.shipping_address.street_address_2}}, {{order.shipping_address.city}}, {{order.shipping_address.country}} {{order.shipping_address.zip_code}}

21 |

Items

22 | 23 | 24 | 25 | 26 | 27 | 28 | {% for item in order.items %} 29 | 30 | 31 | 32 | 33 | 34 | {% endfor %} 35 |
Item No.QuantityPrice
#{{ item.item.product_id }}{{ item.item.quantity }}{{ item.cost.units }}.{{ "%02d" | format(item.cost.nanos // 10000000) }} {{ item.cost.currency_code }}
36 | 37 | 38 | -------------------------------------------------------------------------------- /src/frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /src/frontend/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/src/frontend/.gitkeep -------------------------------------------------------------------------------- /src/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12-alpine as builder 2 | RUN apk add --no-cache ca-certificates git && \ 3 | wget -qO/go/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 && \ 4 | chmod +x /go/bin/dep 5 | 6 | ENV PROJECT github.com/GoogleCloudPlatform/microservices-demo/src/frontend 7 | WORKDIR /go/src/$PROJECT 8 | 9 | # restore dependencies 10 | COPY Gopkg.* ./ 11 | RUN dep ensure --vendor-only -v 12 | COPY . . 13 | RUN go install . 14 | 15 | FROM alpine as release 16 | RUN apk add --no-cache ca-certificates \ 17 | busybox-extras net-tools bind-tools 18 | WORKDIR /frontend 19 | COPY --from=builder /go/bin/frontend /frontend/server 20 | COPY ./templates ./templates 21 | COPY ./static ./static 22 | EXPOSE 8080 23 | ENTRYPOINT ["/frontend/server"] 24 | -------------------------------------------------------------------------------- /src/frontend/Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "cloud.google.com/go" 30 | version = "0.40.0" 31 | 32 | [[constraint]] 33 | name = "contrib.go.opencensus.io/exporter/stackdriver" 34 | version = "0.5.0" 35 | 36 | [[constraint]] 37 | name = "github.com/golang/protobuf" 38 | version = "1.2.0" 39 | 40 | [[constraint]] 41 | name = "github.com/google/uuid" 42 | version = "1.0.0" 43 | 44 | [[constraint]] 45 | name = "github.com/gorilla/mux" 46 | version = "1.6.2" 47 | 48 | [[constraint]] 49 | name = "github.com/pkg/errors" 50 | version = "0.8.0" 51 | 52 | [[constraint]] 53 | name = "github.com/sirupsen/logrus" 54 | version = "1.0.6" 55 | 56 | [[constraint]] 57 | name = "go.opencensus.io" 58 | version = "0.16.0" 59 | 60 | [[constraint]] 61 | branch = "master" 62 | name = "golang.org/x/net" 63 | 64 | [prune] 65 | go-tests = true 66 | unused-packages = true 67 | -------------------------------------------------------------------------------- /src/frontend/README.md: -------------------------------------------------------------------------------- 1 | # frontend 2 | 3 | Run the following command to restore dependencies to `vendor/` directory: 4 | 5 | dep ensure --vendor-only 6 | -------------------------------------------------------------------------------- /src/frontend/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | PATH=$PATH:$GOPATH/bin 20 | protodir=../../pb 21 | 22 | protoc --go_out=plugins=grpc:genproto -I $protodir $protodir/demo.proto 23 | -------------------------------------------------------------------------------- /src/frontend/middleware.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "net/http" 20 | "time" 21 | 22 | "github.com/google/uuid" 23 | "github.com/sirupsen/logrus" 24 | ) 25 | 26 | type ctxKeyLog struct{} 27 | type ctxKeyRequestID struct{} 28 | 29 | type logHandler struct { 30 | log *logrus.Logger 31 | next http.Handler 32 | } 33 | 34 | type responseRecorder struct { 35 | b int 36 | status int 37 | w http.ResponseWriter 38 | } 39 | 40 | func (r *responseRecorder) Header() http.Header { return r.w.Header() } 41 | 42 | func (r *responseRecorder) Write(p []byte) (int, error) { 43 | if r.status == 0 { 44 | r.status = http.StatusOK 45 | } 46 | n, err := r.w.Write(p) 47 | r.b += n 48 | return n, err 49 | } 50 | 51 | func (r *responseRecorder) WriteHeader(statusCode int) { 52 | r.status = statusCode 53 | r.w.WriteHeader(statusCode) 54 | } 55 | 56 | func (lh *logHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 57 | ctx := r.Context() 58 | requestID, _ := uuid.NewRandom() 59 | ctx = context.WithValue(ctx, ctxKeyRequestID{}, requestID.String()) 60 | 61 | start := time.Now() 62 | rr := &responseRecorder{w: w} 63 | log := lh.log.WithFields(logrus.Fields{ 64 | "http.req.path": r.URL.Path, 65 | "http.req.method": r.Method, 66 | "http.req.id": requestID.String(), 67 | }) 68 | if v, ok := r.Context().Value(ctxKeySessionID{}).(string); ok { 69 | log = log.WithField("session", v) 70 | } 71 | log.Debug("request started") 72 | defer func() { 73 | log.WithFields(logrus.Fields{ 74 | "http.resp.took_ms": int64(time.Since(start) / time.Millisecond), 75 | "http.resp.status": rr.status, 76 | "http.resp.bytes": rr.b}).Debugf("request complete") 77 | }() 78 | 79 | ctx = context.WithValue(ctx, ctxKeyLog{}, log) 80 | r = r.WithContext(ctx) 81 | lh.next.ServeHTTP(rr, r) 82 | } 83 | 84 | func ensureSessionID(next http.Handler) http.HandlerFunc { 85 | return func(w http.ResponseWriter, r *http.Request) { 86 | var sessionID string 87 | c, err := r.Cookie(cookieSessionID) 88 | if err == http.ErrNoCookie { 89 | u, _ := uuid.NewRandom() 90 | sessionID = u.String() 91 | http.SetCookie(w, &http.Cookie{ 92 | Name: cookieSessionID, 93 | Value: sessionID, 94 | MaxAge: cookieMaxAge, 95 | }) 96 | } else if err != nil { 97 | return 98 | } else { 99 | sessionID = c.Value 100 | } 101 | ctx := context.WithValue(r.Context(), ctxKeySessionID{}, sessionID) 102 | r = r.WithContext(ctx) 103 | next.ServeHTTP(w, r) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/frontend/money/money.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package money 16 | 17 | import ( 18 | "errors" 19 | 20 | pb "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto" 21 | ) 22 | 23 | const ( 24 | nanosMin = -999999999 25 | nanosMax = +999999999 26 | nanosMod = 1000000000 27 | ) 28 | 29 | var ( 30 | ErrInvalidValue = errors.New("one of the specified money values is invalid") 31 | ErrMismatchingCurrency = errors.New("mismatching currency codes") 32 | ) 33 | 34 | // IsValid checks if specified value has a valid units/nanos signs and ranges. 35 | func IsValid(m pb.Money) bool { 36 | return signMatches(m) && validNanos(m.GetNanos()) 37 | } 38 | 39 | func signMatches(m pb.Money) bool { 40 | return m.GetNanos() == 0 || m.GetUnits() == 0 || (m.GetNanos() < 0) == (m.GetUnits() < 0) 41 | } 42 | 43 | func validNanos(nanos int32) bool { return nanosMin <= nanos && nanos <= nanosMax } 44 | 45 | // IsZero returns true if the specified money value is equal to zero. 46 | func IsZero(m pb.Money) bool { return m.GetUnits() == 0 && m.GetNanos() == 0 } 47 | 48 | // IsPositive returns true if the specified money value is valid and is 49 | // positive. 50 | func IsPositive(m pb.Money) bool { 51 | return IsValid(m) && m.GetUnits() > 0 || (m.GetUnits() == 0 && m.GetNanos() > 0) 52 | } 53 | 54 | // IsNegative returns true if the specified money value is valid and is 55 | // negative. 56 | func IsNegative(m pb.Money) bool { 57 | return IsValid(m) && m.GetUnits() < 0 || (m.GetUnits() == 0 && m.GetNanos() < 0) 58 | } 59 | 60 | // AreSameCurrency returns true if values l and r have a currency code and 61 | // they are the same values. 62 | func AreSameCurrency(l, r pb.Money) bool { 63 | return l.GetCurrencyCode() == r.GetCurrencyCode() && l.GetCurrencyCode() != "" 64 | } 65 | 66 | // AreEquals returns true if values l and r are the equal, including the 67 | // currency. This does not check validity of the provided values. 68 | func AreEquals(l, r pb.Money) bool { 69 | return l.GetCurrencyCode() == r.GetCurrencyCode() && 70 | l.GetUnits() == r.GetUnits() && l.GetNanos() == r.GetNanos() 71 | } 72 | 73 | // Negate returns the same amount with the sign negated. 74 | func Negate(m pb.Money) pb.Money { 75 | return pb.Money{ 76 | Units: -m.GetUnits(), 77 | Nanos: -m.GetNanos(), 78 | CurrencyCode: m.GetCurrencyCode()} 79 | } 80 | 81 | // Must panics if the given error is not nil. This can be used with other 82 | // functions like: "m := Must(Sum(a,b))". 83 | func Must(v pb.Money, err error) pb.Money { 84 | if err != nil { 85 | panic(err) 86 | } 87 | return v 88 | } 89 | 90 | // Sum adds two values. Returns an error if one of the values are invalid or 91 | // currency codes are not matching (unless currency code is unspecified for 92 | // both). 93 | func Sum(l, r pb.Money) (pb.Money, error) { 94 | if !IsValid(l) || !IsValid(r) { 95 | return pb.Money{}, ErrInvalidValue 96 | } else if l.GetCurrencyCode() != r.GetCurrencyCode() { 97 | return pb.Money{}, ErrMismatchingCurrency 98 | } 99 | units := l.GetUnits() + r.GetUnits() 100 | nanos := l.GetNanos() + r.GetNanos() 101 | 102 | if (units == 0 && nanos == 0) || (units > 0 && nanos >= 0) || (units < 0 && nanos <= 0) { 103 | // same sign 104 | units += int64(nanos / nanosMod) 105 | nanos = nanos % nanosMod 106 | } else { 107 | // different sign. nanos guaranteed to not to go over the limit 108 | if units > 0 { 109 | units-- 110 | nanos += nanosMod 111 | } else { 112 | units++ 113 | nanos -= nanosMod 114 | } 115 | } 116 | 117 | return pb.Money{ 118 | Units: units, 119 | Nanos: nanos, 120 | CurrencyCode: l.GetCurrencyCode()}, nil 121 | } 122 | 123 | // MultiplySlow is a slow multiplication operation done through adding the value 124 | // to itself n-1 times. 125 | func MultiplySlow(m pb.Money, n uint32) pb.Money { 126 | out := m 127 | for n > 1 { 128 | out = Must(Sum(out, m)) 129 | n-- 130 | } 131 | return out 132 | } 133 | -------------------------------------------------------------------------------- /src/frontend/rpc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | pb "github.com/GoogleCloudPlatform/microservices-demo/src/frontend/genproto" 22 | 23 | "github.com/pkg/errors" 24 | ) 25 | 26 | const ( 27 | avoidNoopCurrencyConversionRPC = false 28 | ) 29 | 30 | func (fe *frontendServer) getCurrencies(ctx context.Context) ([]string, error) { 31 | currs, err := pb.NewCurrencyServiceClient(fe.currencySvcConn). 32 | GetSupportedCurrencies(ctx, &pb.Empty{}) 33 | if err != nil { 34 | return nil, err 35 | } 36 | var out []string 37 | for _, c := range currs.CurrencyCodes { 38 | if _, ok := whitelistedCurrencies[c]; ok { 39 | out = append(out, c) 40 | } 41 | } 42 | return out, nil 43 | } 44 | 45 | func (fe *frontendServer) getProducts(ctx context.Context) ([]*pb.Product, error) { 46 | resp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn). 47 | ListProducts(ctx, &pb.Empty{}) 48 | return resp.GetProducts(), err 49 | } 50 | 51 | func (fe *frontendServer) getProduct(ctx context.Context, id string) (*pb.Product, error) { 52 | resp, err := pb.NewProductCatalogServiceClient(fe.productCatalogSvcConn). 53 | GetProduct(ctx, &pb.GetProductRequest{Id: id}) 54 | return resp, err 55 | } 56 | 57 | func (fe *frontendServer) getCart(ctx context.Context, userID string) ([]*pb.CartItem, error) { 58 | resp, err := pb.NewCartServiceClient(fe.cartSvcConn).GetCart(ctx, &pb.GetCartRequest{UserId: userID}) 59 | return resp.GetItems(), err 60 | } 61 | 62 | func (fe *frontendServer) emptyCart(ctx context.Context, userID string) error { 63 | _, err := pb.NewCartServiceClient(fe.cartSvcConn).EmptyCart(ctx, &pb.EmptyCartRequest{UserId: userID}) 64 | return err 65 | } 66 | 67 | func (fe *frontendServer) insertCart(ctx context.Context, userID, productID string, quantity int32) error { 68 | _, err := pb.NewCartServiceClient(fe.cartSvcConn).AddItem(ctx, &pb.AddItemRequest{ 69 | UserId: userID, 70 | Item: &pb.CartItem{ 71 | ProductId: productID, 72 | Quantity: quantity}, 73 | }) 74 | return err 75 | } 76 | 77 | func (fe *frontendServer) convertCurrency(ctx context.Context, money *pb.Money, currency string) (*pb.Money, error) { 78 | if avoidNoopCurrencyConversionRPC && money.GetCurrencyCode() == currency { 79 | return money, nil 80 | } 81 | return pb.NewCurrencyServiceClient(fe.currencySvcConn). 82 | Convert(ctx, &pb.CurrencyConversionRequest{ 83 | From: money, 84 | ToCode: currency}) 85 | } 86 | 87 | func (fe *frontendServer) getShippingQuote(ctx context.Context, items []*pb.CartItem, currency string) (*pb.Money, error) { 88 | quote, err := pb.NewShippingServiceClient(fe.shippingSvcConn).GetQuote(ctx, 89 | &pb.GetQuoteRequest{ 90 | Address: nil, 91 | Items: items}) 92 | if err != nil { 93 | return nil, err 94 | } 95 | localized, err := fe.convertCurrency(ctx, quote.GetCostUsd(), currency) 96 | return localized, errors.Wrap(err, "failed to convert currency for shipping cost") 97 | } 98 | 99 | func (fe *frontendServer) getRecommendations(ctx context.Context, userID string, productIDs []string) ([]*pb.Product, error) { 100 | resp, err := pb.NewRecommendationServiceClient(fe.recommendationSvcConn).ListRecommendations(ctx, 101 | &pb.ListRecommendationsRequest{UserId: userID, ProductIds: productIDs}) 102 | if err != nil { 103 | return nil, err 104 | } 105 | out := make([]*pb.Product, len(resp.GetProductIds())) 106 | for i, v := range resp.GetProductIds() { 107 | p, err := fe.getProduct(ctx, v) 108 | if err != nil { 109 | return nil, errors.Wrapf(err, "failed to get recommended product info (#%s)", v) 110 | } 111 | out[i] = p 112 | } 113 | if len(out) > 4 { 114 | out = out[:4] // take only first four to fit the UI 115 | } 116 | return out, err 117 | } 118 | 119 | func (fe *frontendServer) getAd(ctx context.Context, ctxKeys []string) ([]*pb.Ad, error) { 120 | ctx, cancel := context.WithTimeout(ctx, time.Millisecond*100) 121 | defer cancel() 122 | 123 | resp, err := pb.NewAdServiceClient(fe.adSvcConn).GetAds(ctx, &pb.AdRequest{ 124 | ContextKeys: ctxKeys, 125 | }) 126 | return resp.GetAds(), errors.Wrap(err, "failed to get ads") 127 | } 128 | -------------------------------------------------------------------------------- /src/frontend/static/img/products/air-plant.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/src/frontend/static/img/products/air-plant.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/barista-kit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/src/frontend/static/img/products/barista-kit.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/camera-lens.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/src/frontend/static/img/products/camera-lens.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/camp-mug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/src/frontend/static/img/products/camp-mug.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/city-bike.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/src/frontend/static/img/products/city-bike.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/credits.txt: -------------------------------------------------------------------------------- 1 | film-camera.jpg,CC0 Public Domain,https://pxhere.com/en/photo/829555 2 | camera-lens.jpg,CC0 Public Domain,https://pxhere.com/en/photo/670041 3 | air-plant.jpg,,https://unsplash.com/photos/uUwEAW5jFLE 4 | camp-mug.jpg,,https://unsplash.com/photos/h9VhRlMfVkg 5 | record-player.jpg,,https://unsplash.com/photos/pEEHFSX1vak 6 | city-bike.jpg,,https://unsplash.com/photos/Lpe9u9etwMU 7 | typewriter.jpg,,https://unsplash.com/photos/mk7D-4UCfmg 8 | barista-kit.jpg,,https://unsplash.com/photos/ZiRyGGIpRCw 9 | terrarium.jpg,,https://unsplash.com/photos/E9QYLj0724Y 10 | -------------------------------------------------------------------------------- /src/frontend/static/img/products/film-camera.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/src/frontend/static/img/products/film-camera.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/record-player.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/src/frontend/static/img/products/record-player.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/terrarium.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/src/frontend/static/img/products/terrarium.jpg -------------------------------------------------------------------------------- /src/frontend/static/img/products/typewriter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gremlin/microservices-demo/0eb6e19a9ed2e1289441fc197271b2be46cf671e/src/frontend/static/img/products/typewriter.jpg -------------------------------------------------------------------------------- /src/frontend/templates/ad.html: -------------------------------------------------------------------------------- 1 | {{ define "text_ad" }} 2 |
3 | 9 |
10 | {{ end }} -------------------------------------------------------------------------------- /src/frontend/templates/error.html: -------------------------------------------------------------------------------- 1 | {{ define "error" }} 2 | {{ template "header" . }} 3 | 4 |
5 |
6 |
7 |

Uh, oh!

8 |

Something has failed. Below are some details for debugging.

9 | 10 |

HTTP Status: {{.status_code}} {{.status}}

11 |
13 |                     {{- .error -}}
14 |                 
15 |
16 |
17 |
18 | 19 | {{ template "footer" . }} 20 | {{ end }} 21 | -------------------------------------------------------------------------------- /src/frontend/templates/footer.html: -------------------------------------------------------------------------------- 1 | {{ define "footer" }} 2 |
3 |
4 |

5 | © 2018 Google Inc 6 | 7 | (Source Code) 8 | 9 |

10 |

11 | 12 | This website is hosted for demo purposes only. It is not an 13 | actual shop. This is not an official Google project. 14 | 15 |

16 | 17 | {{ if $.session_id }}session-id: {{ $.session_id }}
{{end}} 18 | {{ if $.request_id }}request-id: {{ $.request_id }}
{{end}} 19 |
20 |
21 |
22 | 23 | 24 | 25 | {{ end }} 26 | -------------------------------------------------------------------------------- /src/frontend/templates/header.html: -------------------------------------------------------------------------------- 1 | {{ define "header" }} 2 | 3 | 4 | 5 | 6 | 7 | 8 | Hipster Shop 9 | 10 | 11 | 12 | 13 |
14 | 32 |
33 | 34 | 35 | {{end}} 36 | -------------------------------------------------------------------------------- /src/frontend/templates/home.html: -------------------------------------------------------------------------------- 1 | {{ define "home" }} 2 | 3 | {{ template "header" . }} 4 |
5 |
10 |
11 |

12 | One-stop for Hipster Fashion & Style Online 13 |

14 |

15 | Tired of mainstream fashion ideas, popular trends and 16 | societal norms? This line of lifestyle products will help 17 | you catch up with the hipster trend and express your 18 | personal style. Start shopping hip and vintage items now! 19 |

20 |
21 |
22 | 23 |
24 |
25 |
26 | {{ range $.products }} 27 |
28 |
29 | 30 | 33 | 34 |
35 |
36 | {{ .Item.Name }} 37 |
38 |
39 |
40 | 41 | 42 | 43 |
44 | 45 | {{ renderMoney .Price }} 46 | 47 | 48 |
49 |
50 |
51 |
52 | {{ end }} 53 |
54 |
55 | {{ with $.ad }}{{ template "text_ad" . }}{{ end}} 56 |
57 |
58 |
59 |
60 | 61 | {{ template "footer" . }} 62 | 63 | {{ end }} 64 | -------------------------------------------------------------------------------- /src/frontend/templates/order.html: -------------------------------------------------------------------------------- 1 | {{ define "order" }} 2 | {{ template "header" . }} 3 | 4 |
5 |
6 |
7 |
8 |
9 |

10 | Your order is complete! 11 |

12 |

13 | Order Confirmation ID: {{.order.OrderId}} 14 |
15 | Shipping Tracking ID: {{.order.ShippingTrackingId}} 16 |

17 |

18 | Shipping Cost: {{renderMoney .order.ShippingCost}} 19 |
20 | Total Paid: {{renderMoney .total_paid}} 21 |

22 | Browse other products → 23 |
24 |
25 |
26 | 27 | {{ if $.recommendations }} 28 |
29 | {{ template "recommendations" $.recommendations }} 30 |
31 | {{ end }} 32 |
33 |
34 |
35 | 36 | {{ template "footer" . }} 37 | {{ end }} 38 | -------------------------------------------------------------------------------- /src/frontend/templates/product.html: -------------------------------------------------------------------------------- 1 | {{ define "product" }} 2 | {{ template "header" . }} 3 | 4 |
5 |
6 |
7 |
8 |
9 | 11 |
12 |
13 |

{{$.product.Item.Name}}

14 | 15 |

16 | {{ renderMoney $.product.Price}} 17 |

18 |
19 |

20 |

Product Description:
21 | {{$.product.Item.Description}} 22 |

23 |
24 | 25 |
26 | 27 |
28 |
29 | 30 |
31 | 39 | 40 |
41 |
42 |
43 |
44 | 45 | {{ if $.recommendations}} 46 |
47 | {{ template "recommendations" $.recommendations }} 48 | {{ end }} 49 | 50 | {{ with $.ad }}{{ template "text_ad" . }}{{ end}} 51 |
52 |
53 | 54 |
55 | {{ template "footer" . }} 56 | {{ end }} 57 | -------------------------------------------------------------------------------- /src/frontend/templates/recommendations.html: -------------------------------------------------------------------------------- 1 | {{ define "recommendations" }} 2 |
Products you might like
3 |
4 | {{range . }} 5 |
6 |
7 | 8 | 11 | 12 |
13 | 14 | {{ .Name }} 15 | 16 |
17 |
18 |
19 | {{ end }} 20 |
21 | {{ end }} 22 | -------------------------------------------------------------------------------- /src/loadgenerator/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3-slim as base 2 | 3 | FROM base as builder 4 | 5 | RUN apt-get -qq update \ 6 | && apt-get install -y --no-install-recommends \ 7 | g++ 8 | 9 | COPY requirements.txt . 10 | 11 | RUN pip install --install-option="--prefix=/install" -r requirements.txt 12 | 13 | FROM base 14 | COPY --from=builder /install /usr/local 15 | 16 | COPY . . 17 | RUN chmod +x ./loadgen.sh 18 | RUN apt-get -qq update \ 19 | && apt-get install -y --no-install-recommends \ 20 | curl 21 | ENTRYPOINT ./loadgen.sh 22 | -------------------------------------------------------------------------------- /src/loadgenerator/loadgen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -e 18 | trap "exit" TERM 19 | 20 | if [[ -z "${FRONTEND_ADDR}" ]]; then 21 | echo >&2 "FRONTEND_ADDR not specified" 22 | exit 1 23 | fi 24 | 25 | set -x 26 | 27 | # if one request to the frontend fails, then exit 28 | STATUSCODE=$(curl --silent --output /dev/stderr --write-out "%{http_code}" http://${FRONTEND_ADDR}) 29 | if test $STATUSCODE -ne 200; then 30 | echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" 31 | exit 1 32 | fi 33 | 34 | # else, run loadgen 35 | locust --host="http://${FRONTEND_ADDR}" --no-web -c "${USERS:-10}" 2>&1 36 | -------------------------------------------------------------------------------- /src/loadgenerator/locustfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import random 18 | from locust import HttpLocust, TaskSet 19 | 20 | products = [ 21 | '0PUK6V6EV0', 22 | '1YMWWN1N4O', 23 | '2ZYFJ3GM2N', 24 | '66VCHSJNUP', 25 | '6E92ZMYYFZ', 26 | '9SIQT8TOJO', 27 | 'L9ECAV7KIM', 28 | 'LS4PSXUNUM', 29 | 'OLJCESPC7Z'] 30 | 31 | def index(l): 32 | l.client.get("/") 33 | 34 | def setCurrency(l): 35 | currencies = ['EUR', 'USD', 'JPY', 'CAD'] 36 | l.client.post("/setCurrency", 37 | {'currency_code': random.choice(currencies)}) 38 | 39 | def browseProduct(l): 40 | l.client.get("/product/" + random.choice(products)) 41 | 42 | def viewCart(l): 43 | l.client.get("/cart") 44 | 45 | def addToCart(l): 46 | product = random.choice(products) 47 | l.client.get("/product/" + product) 48 | l.client.post("/cart", { 49 | 'product_id': product, 50 | 'quantity': random.choice([1,2,3,4,5,10])}) 51 | 52 | def checkout(l): 53 | addToCart(l) 54 | l.client.post("/cart/checkout", { 55 | 'email': 'someone@example.com', 56 | 'street_address': '1600 Amphitheatre Parkway', 57 | 'zip_code': '94043', 58 | 'city': 'Mountain View', 59 | 'state': 'CA', 60 | 'country': 'United States', 61 | 'credit_card_number': '4432-8015-6152-0454', 62 | 'credit_card_expiration_month': '1', 63 | 'credit_card_expiration_year': '2039', 64 | 'credit_card_cvv': '672', 65 | }) 66 | 67 | class UserBehavior(TaskSet): 68 | 69 | def on_start(self): 70 | index(self) 71 | 72 | tasks = {index: 1, 73 | setCurrency: 2, 74 | browseProduct: 10, 75 | addToCart: 2, 76 | viewCart: 3, 77 | checkout: 1} 78 | 79 | class WebsiteUser(HttpLocust): 80 | task_set = UserBehavior 81 | min_wait = 1000 82 | max_wait = 10000 83 | -------------------------------------------------------------------------------- /src/loadgenerator/requirements.in: -------------------------------------------------------------------------------- 1 | locustio==0.8.1 2 | -------------------------------------------------------------------------------- /src/loadgenerator/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements.txt requirements.in 6 | # 7 | certifi==2018.11.29 # via requests 8 | chardet==3.0.4 # via requests 9 | click==7.0 # via flask 10 | flask==1.0.2 # via locustio 11 | gevent==1.4.0 # via locustio 12 | greenlet==0.4.15 # via gevent 13 | idna==2.8 # via requests 14 | itsdangerous==1.1.0 # via flask 15 | jinja2==2.10 # via flask 16 | locustio==0.8.1 17 | markupsafe==1.1.0 # via jinja2 18 | msgpack-python==0.5.6 # via locustio 19 | pyzmq==17.0.0 # via locustio 20 | requests==2.21.0 # via locustio 21 | six==1.12.0 # via locustio 22 | urllib3==1.24.1 # via requests 23 | werkzeug==0.14.1 # via flask 24 | -------------------------------------------------------------------------------- /src/paymentservice/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /src/paymentservice/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /src/paymentservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8-alpine as base 2 | 3 | FROM base as builder 4 | 5 | # Some packages (e.g. @google-cloud/profiler) require additional 6 | # deps for post-install scripts 7 | RUN apk add --update --no-cache \ 8 | python \ 9 | make \ 10 | g++ 11 | 12 | WORKDIR /usr/src/app 13 | 14 | COPY package*.json ./ 15 | 16 | RUN npm install --only=production 17 | 18 | FROM base 19 | 20 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 21 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 22 | chmod +x /bin/grpc_health_probe 23 | 24 | WORKDIR /usr/src/app 25 | 26 | COPY --from=builder /usr/src/app/node_modules ./node_modules 27 | 28 | COPY . . 29 | 30 | EXPOSE 50051 31 | 32 | ENTRYPOINT [ "node", "index.js" ] 33 | -------------------------------------------------------------------------------- /src/paymentservice/charge.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const cardValidator = require('simple-card-validator'); 16 | const uuid = require('uuid/v4'); 17 | const pino = require('pino'); 18 | 19 | const logger = pino({ 20 | name: 'paymentservice-charge', 21 | messageKey: 'message', 22 | changeLevelName: 'severity', 23 | useLevelLabels: true 24 | }); 25 | 26 | 27 | class CreditCardError extends Error { 28 | constructor (message) { 29 | super(message); 30 | this.code = 400; // Invalid argument error 31 | } 32 | } 33 | 34 | class InvalidCreditCard extends CreditCardError { 35 | constructor (cardType) { 36 | super(`Credit card info is invalid`); 37 | } 38 | } 39 | 40 | class UnacceptedCreditCard extends CreditCardError { 41 | constructor (cardType) { 42 | super(`Sorry, we cannot process ${cardType} credit cards. Only VISA or MasterCard is accepted.`); 43 | } 44 | } 45 | 46 | class ExpiredCreditCard extends CreditCardError { 47 | constructor (number, month, year) { 48 | super(`Your credit card (ending ${number.substr(-4)}) expired on ${month}/${year}`); 49 | } 50 | } 51 | 52 | /** 53 | * Verifies the credit card number and (pretend) charges the card. 54 | * 55 | * @param {*} request 56 | * @return transaction_id - a random uuid v4. 57 | */ 58 | module.exports = function charge (request) { 59 | const { amount, credit_card: creditCard } = request; 60 | const cardNumber = creditCard.credit_card_number; 61 | const cardInfo = cardValidator(cardNumber); 62 | const { 63 | card_type: cardType, 64 | valid 65 | } = cardInfo.getCardDetails(); 66 | 67 | if (!valid) { throw new InvalidCreditCard(); } 68 | 69 | // Only VISA and mastercard is accepted, other card types (AMEX, dinersclub) will 70 | // throw UnacceptedCreditCard error. 71 | if (!(cardType === 'visa' || cardType === 'mastercard')) { throw new UnacceptedCreditCard(cardType); } 72 | 73 | // Also validate expiration is > today. 74 | const currentMonth = new Date().getMonth() + 1; 75 | const currentYear = new Date().getFullYear(); 76 | const { credit_card_expiration_year: year, credit_card_expiration_month: month } = creditCard; 77 | if ((currentYear * 12 + currentMonth) > (year * 12 + month)) { throw new ExpiredCreditCard(cardNumber.replace('-', ''), month, year); } 78 | 79 | logger.info(`Transaction processed: ${cardType} ending ${cardNumber.substr(-4)} \ 80 | Amount: ${amount.currency_code}${amount.units}.${amount.nanos}`); 81 | 82 | return { transaction_id: uuid() }; 83 | }; 84 | -------------------------------------------------------------------------------- /src/paymentservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # protos are loaded dynamically for node, simply copies over the proto. 18 | mkdir -p proto 19 | cp -r ../../pb/* ./proto 20 | -------------------------------------------------------------------------------- /src/paymentservice/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | require('@google-cloud/profiler').start({ 20 | serviceContext: { 21 | service: 'paymentservice', 22 | version: '1.0.0' 23 | } 24 | }); 25 | require('@google-cloud/trace-agent').start(); 26 | require('@google-cloud/debug-agent').start({ 27 | serviceContext: { 28 | service: 'paymentservice', 29 | version: 'VERSION' 30 | } 31 | }); 32 | 33 | const path = require('path'); 34 | const HipsterShopServer = require('./server'); 35 | 36 | const PORT = process.env['PORT']; 37 | const PROTO_PATH = path.join(__dirname, '/proto/'); 38 | 39 | const server = new HipsterShopServer(PROTO_PATH, PORT); 40 | 41 | server.listen(); 42 | -------------------------------------------------------------------------------- /src/paymentservice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paymentservice", 3 | "version": "0.0.1", 4 | "description": "Payment Microservice demo", 5 | "repository": "https://github.com/GoogleCloudPlatform/microservices-demo", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "lint": "semistandard *.js" 10 | }, 11 | "author": "Jonathan Lui", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@google-cloud/debug-agent": "^4.0.1", 15 | "@google-cloud/profiler": "^2.0.2", 16 | "@google-cloud/trace-agent": "4.0.1", 17 | "@grpc/proto-loader": "^0.1.0", 18 | "grpc": "^1.22.2", 19 | "pino": "^5.6.2", 20 | "simple-card-validator": "^1.1.0", 21 | "uuid": "^3.2.1" 22 | }, 23 | "devDependencies": { 24 | "semistandard": "^12.0.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/paymentservice/proto/grpc/health/v1/health.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The gRPC Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The canonical version of this proto can be found at 16 | // https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto 17 | 18 | syntax = "proto3"; 19 | 20 | package grpc.health.v1; 21 | 22 | option csharp_namespace = "Grpc.Health.V1"; 23 | option go_package = "google.golang.org/grpc/health/grpc_health_v1"; 24 | option java_multiple_files = true; 25 | option java_outer_classname = "HealthProto"; 26 | option java_package = "io.grpc.health.v1"; 27 | 28 | message HealthCheckRequest { 29 | string service = 1; 30 | } 31 | 32 | message HealthCheckResponse { 33 | enum ServingStatus { 34 | UNKNOWN = 0; 35 | SERVING = 1; 36 | NOT_SERVING = 2; 37 | } 38 | ServingStatus status = 1; 39 | } 40 | 41 | service Health { 42 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse); 43 | } 44 | -------------------------------------------------------------------------------- /src/paymentservice/server.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const path = require('path'); 16 | const grpc = require('grpc'); 17 | const pino = require('pino'); 18 | const protoLoader = require('@grpc/proto-loader'); 19 | 20 | const charge = require('./charge'); 21 | 22 | const logger = pino({ 23 | name: 'paymentservice-server', 24 | messageKey: 'message', 25 | changeLevelName: 'severity', 26 | useLevelLabels: true 27 | }); 28 | 29 | class HipsterShopServer { 30 | constructor (protoRoot, port = HipsterShopServer.PORT) { 31 | this.port = port; 32 | 33 | this.packages = { 34 | hipsterShop: this.loadProto(path.join(protoRoot, 'demo.proto')), 35 | health: this.loadProto(path.join(protoRoot, 'grpc/health/v1/health.proto')) 36 | }; 37 | 38 | this.server = new grpc.Server(); 39 | this.loadAllProtos(protoRoot); 40 | } 41 | 42 | /** 43 | * Handler for PaymentService.Charge. 44 | * @param {*} call { ChargeRequest } 45 | * @param {*} callback fn(err, ChargeResponse) 46 | */ 47 | static ChargeServiceHandler (call, callback) { 48 | try { 49 | logger.info(`PaymentService#Charge invoked with request ${JSON.stringify(call.request)}`); 50 | const response = charge(call.request); 51 | callback(null, response); 52 | } catch (err) { 53 | console.warn(err); 54 | callback(err); 55 | } 56 | } 57 | 58 | static CheckHandler (call, callback) { 59 | callback(null, { status: 'SERVING' }); 60 | } 61 | 62 | listen () { 63 | this.server.bind(`0.0.0.0:${this.port}`, grpc.ServerCredentials.createInsecure()); 64 | logger.info(`PaymentService grpc server listening on ${this.port}`); 65 | this.server.start(); 66 | } 67 | 68 | loadProto (path) { 69 | const packageDefinition = protoLoader.loadSync( 70 | path, 71 | { 72 | keepCase: true, 73 | longs: String, 74 | enums: String, 75 | defaults: true, 76 | oneofs: true 77 | } 78 | ); 79 | return grpc.loadPackageDefinition(packageDefinition); 80 | } 81 | 82 | loadAllProtos (protoRoot) { 83 | const hipsterShopPackage = this.packages.hipsterShop.hipstershop; 84 | const healthPackage = this.packages.health.grpc.health.v1; 85 | 86 | this.server.addService( 87 | hipsterShopPackage.PaymentService.service, 88 | { 89 | charge: HipsterShopServer.ChargeServiceHandler.bind(this) 90 | } 91 | ); 92 | 93 | this.server.addService( 94 | healthPackage.Health.service, 95 | { 96 | check: HipsterShopServer.CheckHandler.bind(this) 97 | } 98 | ); 99 | } 100 | } 101 | 102 | HipsterShopServer.PORT = process.env.PORT; 103 | 104 | module.exports = HipsterShopServer; 105 | -------------------------------------------------------------------------------- /src/productcatalogservice/.dockerignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /src/productcatalogservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12-alpine AS builder 2 | RUN apk add --no-cache ca-certificates git && \ 3 | wget -qO/go/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 && \ 4 | chmod +x /go/bin/dep 5 | 6 | ENV PROJECT github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice 7 | WORKDIR /go/src/$PROJECT 8 | 9 | # restore dependencies 10 | COPY Gopkg.* ./ 11 | RUN dep ensure --vendor-only -v 12 | 13 | COPY . . 14 | RUN go build -o /productcatalogservice . 15 | 16 | FROM alpine AS release 17 | RUN apk add --no-cache ca-certificates 18 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 19 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 20 | chmod +x /bin/grpc_health_probe 21 | WORKDIR /productcatalogservice 22 | COPY --from=builder /productcatalogservice ./server 23 | COPY products.json . 24 | EXPOSE 3550 25 | ENTRYPOINT ["/productcatalogservice/server"] 26 | 27 | -------------------------------------------------------------------------------- /src/productcatalogservice/Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "cloud.google.com/go" 30 | version = "0.40.0" 31 | 32 | [[constraint]] 33 | name = "contrib.go.opencensus.io/exporter/stackdriver" 34 | version = "0.5.0" 35 | 36 | [[constraint]] 37 | name = "github.com/golang/protobuf" 38 | version = "1.2.0" 39 | 40 | [[constraint]] 41 | name = "github.com/google/go-cmp" 42 | version = "0.2.0" 43 | 44 | [[constraint]] 45 | name = "github.com/sirupsen/logrus" 46 | version = "1.0.6" 47 | 48 | [[constraint]] 49 | name = "go.opencensus.io" 50 | version = "0.16.0" 51 | 52 | [[constraint]] 53 | branch = "master" 54 | name = "golang.org/x/net" 55 | 56 | [[constraint]] 57 | name = "google.golang.org/grpc" 58 | version = "1.17.0" 59 | 60 | [prune] 61 | go-tests = true 62 | unused-packages = true 63 | -------------------------------------------------------------------------------- /src/productcatalogservice/README.md: -------------------------------------------------------------------------------- 1 | # productcatalogservice 2 | 3 | Run the following command to restore dependencies to `vendor/` directory: 4 | 5 | dep ensure --vendor-only 6 | 7 | ## Dynamic catalog reloading / artificial delay 8 | 9 | This service has a "dynamic catalog reloading" feature that is purposefully 10 | not well implemented. The goal of this feature is to allow you to modify the 11 | `products.json` file and have the changes be picked up without having to 12 | restart the service. 13 | 14 | However, this feature is bugged: the catalog is actually reloaded on each 15 | request, introducing a noticeable delay in the frontend. This delay will also 16 | show up in profiling tools: the `parseCatalog` function will take more than 80% 17 | of the CPU time. 18 | 19 | You can trigger this feature (and the delay) by sending a `USR1` signal and 20 | remove it (if needed) by sending a `USR2` signal: 21 | 22 | ``` 23 | # Trigger bug 24 | kubectl exec \ 25 | $(kubectl get pods -l app=productcatalogservice -o jsonpath='{.items[0].metadata.name}') \ 26 | -c server -- kill -USR1 1 27 | # Remove bug 28 | kubectl exec \ 29 | $(kubectl get pods -l app=productcatalogservice -o jsonpath='{.items[0].metadata.name}') \ 30 | -c server -- kill -USR2 1 31 | ``` 32 | 33 | ## Latency injection 34 | 35 | This service has an `EXTRA_LATENCY` environment variable. This will inject a sleep for the specified [time.Duration](https://golang.org/pkg/time/#ParseDuration) on every call to 36 | to the server. 37 | 38 | For example, use `EXTRA_LATENCY="5.5s"` to sleep for 5.5 seconds on every request. 39 | -------------------------------------------------------------------------------- /src/productcatalogservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | PATH=$PATH:$GOPATH/bin 20 | protodir=../../pb 21 | 22 | protoc --go_out=plugins=grpc:genproto -I $protodir $protodir/demo.proto 23 | -------------------------------------------------------------------------------- /src/productcatalogservice/products.json: -------------------------------------------------------------------------------- 1 | { 2 | "products": [ 3 | { 4 | "id": "OLJCESPC7Z", 5 | "name": "Vintage Typewriter", 6 | "description": "This typewriter looks good in your living room.", 7 | "picture": "/static/img/products/typewriter.jpg", 8 | "priceUsd": { 9 | "currencyCode": "USD", 10 | "units": 67, 11 | "nanos": 990000000 12 | }, 13 | "categories": ["vintage"] 14 | }, 15 | { 16 | "id": "66VCHSJNUP", 17 | "name": "Vintage Camera Lens", 18 | "description": "You won't have a camera to use it and it probably doesn't work anyway.", 19 | "picture": "/static/img/products/camera-lens.jpg", 20 | "priceUsd": { 21 | "currencyCode": "USD", 22 | "units": 12, 23 | "nanos": 490000000 24 | }, 25 | "categories": ["photography", "vintage"] 26 | }, 27 | { 28 | "id": "1YMWWN1N4O", 29 | "name": "Home Barista Kit", 30 | "description": "Always wanted to brew coffee with Chemex and Aeropress at home?", 31 | "picture": "/static/img/products/barista-kit.jpg", 32 | "priceUsd": { 33 | "currencyCode": "USD", 34 | "units": 124 35 | }, 36 | "categories": ["cookware"] 37 | }, 38 | { 39 | "id": "L9ECAV7KIM", 40 | "name": "Terrarium", 41 | "description": "This terrarium will looks great in your white painted living room.", 42 | "picture": "/static/img/products/terrarium.jpg", 43 | "priceUsd": { 44 | "currencyCode": "USD", 45 | "units": 36, 46 | "nanos": 450000000 47 | }, 48 | "categories": ["gardening"] 49 | }, 50 | { 51 | "id": "2ZYFJ3GM2N", 52 | "name": "Film Camera", 53 | "description": "This camera looks like it's a film camera, but it's actually digital.", 54 | "picture": "/static/img/products/film-camera.jpg", 55 | "priceUsd": { 56 | "currencyCode": "USD", 57 | "units": 2245 58 | }, 59 | "categories": ["photography", "vintage"] 60 | }, 61 | { 62 | "id": "0PUK6V6EV0", 63 | "name": "Vintage Record Player", 64 | "description": "It still works.", 65 | "picture": "/static/img/products/record-player.jpg", 66 | "priceUsd": { 67 | "currencyCode": "USD", 68 | "units": 65, 69 | "nanos": 500000000 70 | }, 71 | "categories": ["music", "vintage"] 72 | }, 73 | { 74 | "id": "LS4PSXUNUM", 75 | "name": "Metal Camping Mug", 76 | "description": "You probably don't go camping that often but this is better than plastic cups.", 77 | "picture": "/static/img/products/camp-mug.jpg", 78 | "priceUsd": { 79 | "currencyCode": "USD", 80 | "units": 24, 81 | "nanos": 330000000 82 | }, 83 | "categories": ["cookware"] 84 | }, 85 | { 86 | "id": "9SIQT8TOJO", 87 | "name": "City Bike", 88 | "description": "This single gear bike probably cannot climb the hills of San Francisco.", 89 | "picture": "/static/img/products/city-bike.jpg", 90 | "priceUsd": { 91 | "currencyCode": "USD", 92 | "units": 789, 93 | "nanos": 500000000 94 | }, 95 | "categories": ["cycling"] 96 | }, 97 | { 98 | "id": "6E92ZMYYFZ", 99 | "name": "Air Plant", 100 | "description": "Have you ever wondered whether air plants need water? Buy one and figure out.", 101 | "picture": "/static/img/products/air-plant.jpg", 102 | "priceUsd": { 103 | "currencyCode": "USD", 104 | "units": 12, 105 | "nanos": 300000000 106 | }, 107 | "categories": ["gardening"] 108 | } 109 | ] 110 | } 111 | -------------------------------------------------------------------------------- /src/productcatalogservice/server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | 21 | pb "github.com/GoogleCloudPlatform/microservices-demo/src/productcatalogservice/genproto" 22 | "github.com/golang/protobuf/proto" 23 | "github.com/google/go-cmp/cmp" 24 | "go.opencensus.io/plugin/ocgrpc" 25 | "google.golang.org/grpc" 26 | "google.golang.org/grpc/codes" 27 | "google.golang.org/grpc/status" 28 | ) 29 | 30 | func TestServer(t *testing.T) { 31 | ctx := context.Background() 32 | addr := run(0) 33 | conn, err := grpc.Dial(addr, 34 | grpc.WithInsecure(), 35 | grpc.WithStatsHandler(&ocgrpc.ClientHandler{})) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | defer conn.Close() 40 | client := pb.NewProductCatalogServiceClient(conn) 41 | res, err := client.ListProducts(ctx, &pb.Empty{}) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | if diff := cmp.Diff(res.Products, parseCatalog(), cmp.Comparer(proto.Equal)); diff != "" { 46 | t.Error(diff) 47 | } 48 | 49 | got, err := client.GetProduct(ctx, &pb.GetProductRequest{Id: "OLJCESPC7Z"}) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | if want := parseCatalog()[0]; !proto.Equal(got, want) { 54 | t.Errorf("got %v, want %v", got, want) 55 | } 56 | _, err = client.GetProduct(ctx, &pb.GetProductRequest{Id: "N/A"}) 57 | if got, want := status.Code(err), codes.NotFound; got != want { 58 | t.Errorf("got %s, want %s", got, want) 59 | } 60 | 61 | sres, err := client.SearchProducts(ctx, &pb.SearchProductsRequest{Query: "typewriter"}) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | if diff := cmp.Diff(sres.Results, []*pb.Product{parseCatalog()[0]}, cmp.Comparer(proto.Equal)); diff != "" { 66 | t.Error(diff) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/recommendationservice/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /src/recommendationservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2.7-slim 2 | RUN apt-get update -qqy && \ 3 | apt-get -qqy install wget g++ && \ 4 | rm -rf /var/lib/apt/lists/* 5 | # show python logs as they occur 6 | ENV PYTHONUNBUFFERED=0 7 | 8 | # download the grpc health probe 9 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 10 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 11 | chmod +x /bin/grpc_health_probe 12 | 13 | # get packages 14 | WORKDIR /recommendationservice 15 | COPY requirements.txt requirements.txt 16 | RUN pip install -r requirements.txt 17 | 18 | # add files into working directory 19 | COPY . . 20 | 21 | # set listen port 22 | ENV PORT "8080" 23 | EXPOSE 8080 24 | 25 | ENTRYPOINT ["python", "/recommendationservice/recommendation_server.py"] 26 | -------------------------------------------------------------------------------- /src/recommendationservice/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import sys 18 | import grpc 19 | import demo_pb2 20 | import demo_pb2_grpc 21 | 22 | from opencensus.trace.tracer import Tracer 23 | from opencensus.trace.exporters import stackdriver_exporter 24 | from opencensus.trace.ext.grpc import client_interceptor 25 | 26 | from logger import getJSONLogger 27 | logger = getJSONLogger('recommendationservice-server') 28 | 29 | if __name__ == "__main__": 30 | # get port 31 | if len(sys.argv) > 1: 32 | port = sys.argv[1] 33 | else: 34 | port = "8080" 35 | 36 | try: 37 | exporter = stackdriver_exporter.StackdriverExporter() 38 | tracer = Tracer(exporter=exporter) 39 | tracer_interceptor = client_interceptor.OpenCensusClientInterceptor(tracer, host_port='localhost:'+port) 40 | except: 41 | tracer_interceptor = client_interceptor.OpenCensusClientInterceptor() 42 | 43 | # set up server stub 44 | channel = grpc.insecure_channel('localhost:'+port) 45 | channel = grpc.intercept_channel(channel, tracer_interceptor) 46 | stub = demo_pb2_grpc.RecommendationServiceStub(channel) 47 | # form request 48 | request = demo_pb2.ListRecommendationsRequest(user_id="test", product_ids=["test"]) 49 | # make call to server 50 | response = stub.ListRecommendations(request) 51 | logger.info(response) 52 | -------------------------------------------------------------------------------- /src/recommendationservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash 18 | set -e 19 | 20 | # script to compile python protos 21 | # 22 | # requires gRPC tools: 23 | # pip install -r requirements.txt 24 | 25 | python -m grpc_tools.protoc -I../../pb --python_out=. --grpc_python_out=. ../../pb/demo.proto 26 | -------------------------------------------------------------------------------- /src/recommendationservice/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import logging 18 | import sys 19 | from pythonjsonlogger import jsonlogger 20 | 21 | # TODO(yoshifumi) this class is duplicated since other Python services are 22 | # not sharing the modules for logging. 23 | class CustomJsonFormatter(jsonlogger.JsonFormatter): 24 | def add_fields(self, log_record, record, message_dict): 25 | super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) 26 | if not log_record.get('timestamp'): 27 | log_record['timestamp'] = record.created 28 | if log_record.get('severity'): 29 | log_record['severity'] = log_record['severity'].upper() 30 | else: 31 | log_record['severity'] = record.levelname 32 | 33 | def getJSONLogger(name): 34 | logger = logging.getLogger(name) 35 | handler = logging.StreamHandler(sys.stdout) 36 | formatter = CustomJsonFormatter('(timestamp) (severity) (name) (message)') 37 | handler.setFormatter(formatter) 38 | logger.addHandler(handler) 39 | logger.setLevel(logging.INFO) 40 | logger.propagate = False 41 | return logger 42 | -------------------------------------------------------------------------------- /src/recommendationservice/recommendation_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import os 18 | import random 19 | import time 20 | import traceback 21 | from concurrent import futures 22 | 23 | import googleclouddebugger 24 | import googlecloudprofiler 25 | import grpc 26 | from opencensus.trace.exporters import print_exporter 27 | from opencensus.trace.exporters import stackdriver_exporter 28 | from opencensus.trace.ext.grpc import server_interceptor 29 | from opencensus.common.transports.async_ import AsyncTransport 30 | from opencensus.trace.samplers import always_on 31 | 32 | import demo_pb2 33 | import demo_pb2_grpc 34 | from grpc_health.v1 import health_pb2 35 | from grpc_health.v1 import health_pb2_grpc 36 | 37 | from logger import getJSONLogger 38 | logger = getJSONLogger('recommendationservice-server') 39 | 40 | def initStackdriverProfiling(): 41 | project_id = None 42 | try: 43 | project_id = os.environ["GCP_PROJECT_ID"] 44 | except KeyError: 45 | # Environment variable not set 46 | pass 47 | 48 | for retry in xrange(1,4): 49 | try: 50 | if project_id: 51 | googlecloudprofiler.start(service='recommendation_server', service_version='1.0.0', verbose=0, project_id=project_id) 52 | else: 53 | googlecloudprofiler.start(service='recommendation_server', service_version='1.0.0', verbose=0) 54 | logger.info("Successfully started Stackdriver Profiler.") 55 | return 56 | except (BaseException) as exc: 57 | logger.info("Unable to start Stackdriver Profiler Python agent. " + str(exc)) 58 | if (retry < 4): 59 | logger.info("Sleeping %d seconds to retry Stackdriver Profiler agent initialization"%(retry*10)) 60 | time.sleep (1) 61 | else: 62 | logger.warning("Could not initialize Stackdriver Profiler after retrying, giving up") 63 | return 64 | 65 | class RecommendationService(demo_pb2_grpc.RecommendationServiceServicer): 66 | def ListRecommendations(self, request, context): 67 | max_responses = 5 68 | # fetch list of products from product catalog stub 69 | cat_response = product_catalog_stub.ListProducts(demo_pb2.Empty()) 70 | product_ids = [x.id for x in cat_response.products] 71 | filtered_products = list(set(product_ids)-set(request.product_ids)) 72 | num_products = len(filtered_products) 73 | num_return = min(max_responses, num_products) 74 | # sample list of indicies to return 75 | indices = random.sample(range(num_products), num_return) 76 | # fetch product ids from indices 77 | prod_list = [filtered_products[i] for i in indices] 78 | logger.info("[Recv ListRecommendations] product_ids={}".format(prod_list)) 79 | # build and return response 80 | response = demo_pb2.ListRecommendationsResponse() 81 | response.product_ids.extend(prod_list) 82 | return response 83 | 84 | def Check(self, request, context): 85 | return health_pb2.HealthCheckResponse( 86 | status=health_pb2.HealthCheckResponse.SERVING) 87 | 88 | 89 | if __name__ == "__main__": 90 | logger.info("initializing recommendationservice") 91 | 92 | try: 93 | if "DISABLE_PROFILER" in os.environ: 94 | raise KeyError() 95 | else: 96 | logger.info("Profiler enabled.") 97 | initStackdriverProfiling() 98 | except KeyError: 99 | logger.info("Profiler disabled.") 100 | 101 | try: 102 | if "DISABLE_TRACING" in os.environ: 103 | raise KeyError() 104 | else: 105 | logger.info("Tracing enabled.") 106 | sampler = always_on.AlwaysOnSampler() 107 | exporter = stackdriver_exporter.StackdriverExporter( 108 | project_id=os.environ.get('GCP_PROJECT_ID'), 109 | transport=AsyncTransport) 110 | tracer_interceptor = server_interceptor.OpenCensusServerInterceptor(sampler, exporter) 111 | except KeyError: 112 | logger.info("Tracing disabled.") 113 | tracer_interceptor = server_interceptor.OpenCensusServerInterceptor() 114 | 115 | 116 | try: 117 | if "DISABLE_DEBUGGER" in os.environ: 118 | raise KeyError() 119 | else: 120 | logger.info("Debugger enabled.") 121 | try: 122 | googleclouddebugger.enable( 123 | module='recommendationserver', 124 | version='1.0.0' 125 | ) 126 | except Exception, err: 127 | logger.error("Could not enable debugger") 128 | logger.error(traceback.print_exc()) 129 | pass 130 | except KeyError: 131 | logger.info("Debugger disabled.") 132 | 133 | port = os.environ.get('PORT', "8080") 134 | catalog_addr = os.environ.get('PRODUCT_CATALOG_SERVICE_ADDR', '') 135 | if catalog_addr == "": 136 | raise Exception('PRODUCT_CATALOG_SERVICE_ADDR environment variable not set') 137 | logger.info("product catalog address: " + catalog_addr) 138 | channel = grpc.insecure_channel(catalog_addr) 139 | product_catalog_stub = demo_pb2_grpc.ProductCatalogServiceStub(channel) 140 | 141 | # create gRPC server 142 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), 143 | interceptors=(tracer_interceptor,)) 144 | 145 | # add class to gRPC server 146 | service = RecommendationService() 147 | demo_pb2_grpc.add_RecommendationServiceServicer_to_server(service, server) 148 | health_pb2_grpc.add_HealthServicer_to_server(service, server) 149 | 150 | # start server 151 | logger.info("listening on port: " + port) 152 | server.add_insecure_port('[::]:'+port) 153 | server.start() 154 | 155 | # keep alive 156 | try: 157 | while True: 158 | time.sleep(10000) 159 | except KeyboardInterrupt: 160 | server.stop(0) 161 | -------------------------------------------------------------------------------- /src/recommendationservice/requirements.in: -------------------------------------------------------------------------------- 1 | google-api-core==1.6.0 2 | google-python-cloud-debugger==2.9 3 | grpcio-health-checking==1.13.0 4 | grpcio==1.16.1 5 | opencensus[stackdriver]==0.1.10 6 | python-json-logger==0.1.9 7 | google-cloud-profiler==1.0.8 8 | -------------------------------------------------------------------------------- /src/recommendationservice/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements.txt requirements.in 6 | # 7 | cachetools==3.1.0 # via google-auth 8 | certifi==2018.11.29 # via requests 9 | chardet==3.0.4 # via requests 10 | google-api-core[grpc]==1.6.0 11 | google-api-python-client==1.7.8 # via google-cloud-profiler, google-python-cloud-debugger 12 | google-auth-httplib2==0.0.3 # via google-api-python-client, google-cloud-profiler, google-python-cloud-debugger 13 | google-auth==1.6.2 # via google-api-core, google-api-python-client, google-auth-httplib2, google-cloud-profiler, google-python-cloud-debugger 14 | google-cloud-core==0.29.1 # via google-cloud-trace 15 | google-cloud-profiler==1.0.8 16 | google-cloud-trace==0.20.2 # via opencensus 17 | google-python-cloud-debugger==2.9 18 | googleapis-common-protos==1.5.6 # via google-api-core 19 | grpcio-health-checking==1.13.0 20 | grpcio==1.16.1 21 | httplib2==0.12.0 # via google-api-python-client, google-auth-httplib2 22 | idna==2.8 # via requests 23 | opencensus[stackdriver]==0.1.10 24 | protobuf==3.6.1 # via google-api-core, google-cloud-profiler, googleapis-common-protos, grpcio-health-checking 25 | pyasn1-modules==0.2.4 # via google-auth 26 | pyasn1==0.4.5 # via pyasn1-modules, rsa 27 | python-json-logger==0.1.9 28 | pytz==2018.9 # via google-api-core 29 | pyyaml==3.13 # via google-python-cloud-debugger 30 | requests==2.21.0 # via google-api-core, google-cloud-profiler 31 | rsa==4.0 # via google-auth 32 | six==1.12.0 # via google-api-core, google-api-python-client, google-auth, google-python-cloud-debugger, grpcio, protobuf 33 | uritemplate==3.0.0 # via google-api-python-client 34 | urllib3==1.24.1 # via requests 35 | -------------------------------------------------------------------------------- /src/shippingservice/.dockerignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /src/shippingservice/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12-alpine as builder 2 | RUN apk add --no-cache ca-certificates git && \ 3 | wget -qO/go/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 && \ 4 | chmod +x /go/bin/dep 5 | 6 | ENV PROJECT github.com/GoogleCloudPlatform/microservices-demo/src/shippingservice 7 | WORKDIR /go/src/$PROJECT 8 | 9 | # restore dependencies 10 | COPY Gopkg.* ./ 11 | RUN dep ensure --vendor-only -v 12 | COPY . . 13 | RUN go install . 14 | 15 | FROM alpine as release 16 | RUN apk add --no-cache ca-certificates 17 | RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \ 18 | wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ 19 | chmod +x /bin/grpc_health_probe 20 | COPY --from=builder /go/bin/shippingservice /shippingservice 21 | ENV APP_PORT=50051 22 | EXPOSE 50051 23 | ENTRYPOINT ["/shippingservice"] 24 | -------------------------------------------------------------------------------- /src/shippingservice/Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "cloud.google.com/go" 30 | version = "0.40.0" 31 | 32 | [[constraint]] 33 | name = "contrib.go.opencensus.io/exporter/stackdriver" 34 | version = "0.5.0" 35 | 36 | [[constraint]] 37 | name = "github.com/golang/protobuf" 38 | version = "1.2.0" 39 | 40 | [[constraint]] 41 | name = "github.com/sirupsen/logrus" 42 | version = "1.0.6" 43 | 44 | [[constraint]] 45 | name = "go.opencensus.io" 46 | version = "0.16.0" 47 | 48 | [[constraint]] 49 | branch = "master" 50 | name = "golang.org/x/net" 51 | 52 | [prune] 53 | go-tests = true 54 | unused-packages = true 55 | -------------------------------------------------------------------------------- /src/shippingservice/README.md: -------------------------------------------------------------------------------- 1 | # Shipping Service 2 | 3 | The Shipping service provides price quote, tracking IDs, and the impression of order fulfillment & shipping processes. 4 | 5 | ## Local 6 | 7 | Run the following command to restore dependencies to `vendor/` directory: 8 | 9 | dep ensure --vendor-only 10 | 11 | ## Build 12 | 13 | From repository root, run: 14 | 15 | ``` 16 | docker build --file src/shippingservice/Dockerfile . 17 | ``` 18 | 19 | ## Test 20 | 21 | ``` 22 | go test . 23 | ``` 24 | -------------------------------------------------------------------------------- /src/shippingservice/genproto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | #!/bin/bash -e 18 | 19 | PATH=$PATH:$GOPATH/bin 20 | protodir=../../pb 21 | 22 | protoc --go_out=plugins=grpc:genproto -I $protodir $protodir/demo.proto 23 | -------------------------------------------------------------------------------- /src/shippingservice/quote.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "math" 20 | ) 21 | 22 | // Quote represents a currency value. 23 | type Quote struct { 24 | Dollars uint32 25 | Cents uint32 26 | } 27 | 28 | // String representation of the Quote. 29 | func (q Quote) String() string { 30 | return fmt.Sprintf("$%d.%d", q.Dollars, q.Cents) 31 | } 32 | 33 | // CreateQuoteFromCount takes a number of items and returns a Price struct. 34 | func CreateQuoteFromCount(count int) Quote { 35 | return CreateQuoteFromFloat(quoteByCountFloat(count)) 36 | } 37 | 38 | // CreateQuoteFromFloat takes a price represented as a float and creates a Price struct. 39 | func CreateQuoteFromFloat(value float64) Quote { 40 | units, fraction := math.Modf(value) 41 | return Quote{ 42 | uint32(units), 43 | uint32(math.Trunc(fraction * 100)), 44 | } 45 | } 46 | 47 | // quoteByCountFloat takes a number of items and generates a price quote represented as a float. 48 | func quoteByCountFloat(count int) float64 { 49 | if count == 0 { 50 | return 0 51 | } 52 | count64 := float64(count) 53 | var p = 1 + (count64 * 0.2) 54 | return count64 + math.Pow(3, p) 55 | } 56 | -------------------------------------------------------------------------------- /src/shippingservice/shippingservice_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "testing" 19 | 20 | "golang.org/x/net/context" 21 | 22 | pb "github.com/GoogleCloudPlatform/microservices-demo/src/shippingservice/genproto" 23 | ) 24 | 25 | // TestGetQuote is a basic check on the GetQuote RPC service. 26 | func TestGetQuote(t *testing.T) { 27 | s := server{} 28 | 29 | // A basic test case to test logic and protobuf interactions. 30 | req := &pb.GetQuoteRequest{ 31 | Address: &pb.Address{ 32 | StreetAddress: "Muffin Man", 33 | City: "London", 34 | State: "", 35 | Country: "England", 36 | }, 37 | Items: []*pb.CartItem{ 38 | { 39 | ProductId: "23", 40 | Quantity: 1, 41 | }, 42 | { 43 | ProductId: "46", 44 | Quantity: 3, 45 | }, 46 | }, 47 | } 48 | 49 | res, err := s.GetQuote(context.Background(), req) 50 | if err != nil { 51 | t.Errorf("TestGetQuote (%v) failed", err) 52 | } 53 | if res.CostUsd.GetUnits() != 11 || res.CostUsd.GetNanos() != 220000000 { 54 | t.Errorf("TestGetQuote: Quote value '%d.%d' does not match expected '%s'", res.CostUsd.GetUnits(), res.CostUsd.GetNanos(), "11.220000000") 55 | } 56 | } 57 | 58 | // TestShipOrder is a basic check on the ShipOrder RPC service. 59 | func TestShipOrder(t *testing.T) { 60 | s := server{} 61 | 62 | // A basic test case to test logic and protobuf interactions. 63 | req := &pb.ShipOrderRequest{ 64 | Address: &pb.Address{ 65 | StreetAddress: "Muffin Man", 66 | City: "London", 67 | State: "", 68 | Country: "England", 69 | }, 70 | Items: []*pb.CartItem{ 71 | { 72 | ProductId: "23", 73 | Quantity: 1, 74 | }, 75 | { 76 | ProductId: "46", 77 | Quantity: 3, 78 | }, 79 | }, 80 | } 81 | 82 | res, err := s.ShipOrder(context.Background(), req) 83 | if err != nil { 84 | t.Errorf("TestShipOrder (%v) failed", err) 85 | } 86 | // @todo improve quality of this test to check for a pattern such as '[A-Z]{2}-\d+-\d+'. 87 | if len(res.TrackingId) != 18 { 88 | t.Errorf("TestShipOrder: Tracking ID is malformed - has %d characters, %d expected", len(res.TrackingId), 18) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/shippingservice/tracker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "math/rand" 20 | "time" 21 | ) 22 | 23 | // seeded determines if the random number generator is ready. 24 | var seeded bool = false 25 | 26 | // CreateTrackingId generates a tracking ID. 27 | func CreateTrackingId(salt string) string { 28 | if !seeded { 29 | rand.Seed(time.Now().UnixNano()) 30 | seeded = true 31 | } 32 | 33 | return fmt.Sprintf("%c%c-%d%s-%d%s", 34 | getRandomLetterCode(), 35 | getRandomLetterCode(), 36 | len(salt), 37 | getRandomNumber(3), 38 | len(salt)/2, 39 | getRandomNumber(7), 40 | ) 41 | } 42 | 43 | // getRandomLetterCode generates a code point value for a capital letter. 44 | func getRandomLetterCode() uint32 { 45 | return 65 + uint32(rand.Intn(25)) 46 | } 47 | 48 | // getRandomNumber generates a string representation of a number with the requested number of digits. 49 | func getRandomNumber(digits int) string { 50 | str := "" 51 | for i := 0; i < digits; i++ { 52 | str = fmt.Sprintf("%s%d", str, rand.Intn(10)) 53 | } 54 | 55 | return str 56 | } 57 | -------------------------------------------------------------------------------- /tests/cartservice/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/* 2 | /obj/* 3 | /.vs/* -------------------------------------------------------------------------------- /tests/cartservice/CartServiceTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Threading.Tasks; 17 | using Grpc.Core; 18 | using Hipstershop; 19 | using Xunit; 20 | using static Hipstershop.CartService; 21 | 22 | namespace cartservice 23 | { 24 | public class E2ETests 25 | { 26 | private static string serverHostName = "localhost"; 27 | private static int port = 7070; 28 | 29 | [Fact] 30 | public async Task GetItem_NoAddItemBefore_EmptyCartReturned() 31 | { 32 | string userId = Guid.NewGuid().ToString(); 33 | 34 | // Construct server's Uri 35 | string targetUri = $"{serverHostName}:{port}"; 36 | 37 | // Create a GRPC communication channel between the client and the server 38 | var channel = new Channel(targetUri, ChannelCredentials.Insecure); 39 | 40 | var client = new CartServiceClient(channel); 41 | 42 | var request = new GetCartRequest 43 | { 44 | UserId = userId, 45 | }; 46 | 47 | var cart = await client.GetCartAsync(request); 48 | Assert.NotNull(cart); 49 | 50 | // All grpc objects implement IEquitable, so we can compare equality with by-value semantics 51 | Assert.Equal(new Cart(), cart); 52 | } 53 | 54 | [Fact] 55 | public async Task AddItem_ItemExists_Updated() 56 | { 57 | string userId = Guid.NewGuid().ToString(); 58 | 59 | // Construct server's Uri 60 | string targetUri = $"{serverHostName}:{port}"; 61 | 62 | // Create a GRPC communication channel between the client and the server 63 | var channel = new Channel(targetUri, ChannelCredentials.Insecure); 64 | 65 | var client = new CartServiceClient(channel); 66 | var request = new AddItemRequest 67 | { 68 | UserId = userId, 69 | Item = new CartItem 70 | { 71 | ProductId = "1", 72 | Quantity = 1 73 | } 74 | }; 75 | 76 | // First add - nothing should fail 77 | await client.AddItemAsync(request); 78 | 79 | // Second add of existing product - quantity should be updated 80 | await client.AddItemAsync(request); 81 | 82 | var getCartRequest = new GetCartRequest 83 | { 84 | UserId = userId 85 | }; 86 | var cart = await client.GetCartAsync(getCartRequest); 87 | Assert.NotNull(cart); 88 | Assert.Equal(userId, cart.UserId); 89 | Assert.Single(cart.Items); 90 | Assert.Equal(2, cart.Items[0].Quantity); 91 | 92 | // Cleanup 93 | await client.EmptyCartAsync(new EmptyCartRequest{ UserId = userId }); 94 | } 95 | 96 | [Fact] 97 | public async Task AddItem_New_Inserted() 98 | { 99 | string userId = Guid.NewGuid().ToString(); 100 | 101 | // Construct server's Uri 102 | string targetUri = $"{serverHostName}:{port}"; 103 | 104 | // Create a GRPC communication channel between the client and the server 105 | var channel = new Channel(targetUri, ChannelCredentials.Insecure); 106 | 107 | // Create a proxy object to work with the server 108 | var client = new CartServiceClient(channel); 109 | 110 | var request = new AddItemRequest 111 | { 112 | UserId = userId, 113 | Item = new CartItem 114 | { 115 | ProductId = "1", 116 | Quantity = 1 117 | } 118 | }; 119 | 120 | await client.AddItemAsync(request); 121 | 122 | var getCartRequest = new GetCartRequest 123 | { 124 | UserId = userId 125 | }; 126 | var cart = await client.GetCartAsync(getCartRequest); 127 | Assert.NotNull(cart); 128 | Assert.Equal(userId, cart.UserId); 129 | Assert.Single(cart.Items); 130 | 131 | await client.EmptyCartAsync(new EmptyCartRequest{ UserId = userId }); 132 | cart = await client.GetCartAsync(getCartRequest); 133 | Assert.Empty(cart.Items); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/cartservice/cartservice.tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Always 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/cartservice/cartservice.tests.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | true 5 | 6 | --------------------------------------------------------------------------------