├── .github └── workflows │ ├── ci.yml │ └── docker.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── cloudshell-tutorial.md ├── cymbal-shops.md ├── development-guide.md ├── development-principles.md ├── img │ ├── architecture-diagram.png │ ├── jaeger-dependencies.png │ ├── memorystore.png │ ├── online-boutique-frontend-1.png │ └── online-boutique-frontend-2.png ├── jaeger.md ├── memorystore.md ├── network-policies │ ├── README.md │ ├── network-policy-adservice.yaml │ ├── network-policy-cartservice.yaml │ ├── network-policy-checkoutservice.yaml │ ├── network-policy-currencyservice.yaml │ ├── network-policy-deny-all.yaml │ ├── network-policy-emailservice.yaml │ ├── network-policy-frontend.yaml │ ├── network-policy-loadgenerator.yaml │ ├── network-policy-paymentservice.yaml │ ├── network-policy-productcatalogservice.yaml │ ├── network-policy-recommendationservice.yaml │ ├── network-policy-redis.yaml │ └── network-policy-shippingservice.yaml ├── service-mesh.md └── workload-identity.md ├── go.work ├── go.work.sum ├── infra ├── istio │ ├── allow-egress-googleapis.yaml │ ├── frontend-gateway.yaml │ └── frontend.yaml └── k8s │ ├── README.md │ ├── adservice.yaml │ ├── cartservice.yaml │ ├── checkoutservice.yaml │ ├── currencyservice.yaml │ ├── emailservice.yaml │ ├── frontend.yaml │ ├── gomicro.yaml │ ├── loadgenerator.yaml │ ├── paymentservice.yaml │ ├── productcatalogservice.yaml │ ├── recommendationservice.yaml │ ├── redis.yaml │ └── shippingservice.yaml ├── release ├── istio.yaml └── k8s.yaml ├── service ├── ad │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── config │ │ └── config.go │ ├── go.mod │ ├── go.sum │ ├── handler │ │ ├── adservice.go │ │ └── health.go │ ├── main.go │ ├── proto │ │ ├── adservice.pb.go │ │ ├── adservice.pb.micro.go │ │ ├── adservice.proto │ │ ├── health.pb.go │ │ ├── health.pb.micro.go │ │ └── health.proto │ └── tracing.go ├── cart │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── cartstore │ │ ├── interface.go │ │ └── memory.go │ ├── config │ │ └── config.go │ ├── go.mod │ ├── go.sum │ ├── handler │ │ ├── cartservice.go │ │ └── health.go │ ├── main.go │ ├── proto │ │ ├── cartservice.pb.go │ │ ├── cartservice.pb.micro.go │ │ ├── cartservice.proto │ │ ├── health.pb.go │ │ ├── health.pb.micro.go │ │ └── health.proto │ └── tracing.go ├── checkout │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── config │ │ └── config.go │ ├── go.mod │ ├── go.sum │ ├── handler │ │ ├── checkoutservice.go │ │ └── health.go │ ├── main.go │ ├── money │ │ ├── money.go │ │ └── money_test.go │ ├── proto │ │ ├── health.pb.go │ │ ├── health.pb.micro.go │ │ ├── health.proto │ │ ├── hipstershop.pb.go │ │ ├── hipstershop.pb.micro.go │ │ └── hipstershop.proto │ └── tracing.go ├── currency │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── config │ │ └── config.go │ ├── data │ │ └── currency_conversion.json │ ├── go.mod │ ├── go.sum │ ├── handler │ │ ├── currencyservice.go │ │ └── health.go │ ├── main.go │ ├── proto │ │ ├── currencyservice.pb.go │ │ ├── currencyservice.pb.micro.go │ │ ├── currencyservice.proto │ │ ├── health.pb.go │ │ ├── health.pb.micro.go │ │ └── health.proto │ └── tracing.go ├── email │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── config │ │ └── config.go │ ├── go.mod │ ├── go.sum │ ├── handler │ │ ├── emailservice.go │ │ └── health.go │ ├── main.go │ ├── proto │ │ ├── emailservice.pb.go │ │ ├── emailservice.pb.micro.go │ │ ├── emailservice.proto │ │ ├── health.pb.go │ │ ├── health.pb.micro.go │ │ └── health.proto │ ├── template │ │ └── confirmation.html │ └── tracing.go ├── frontend │ ├── .dockerignore │ ├── .gitkeep │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── config │ │ └── config.go │ ├── deployment_details.go │ ├── go.mod │ ├── go.sum │ ├── handlers.go │ ├── main.go │ ├── middleware.go │ ├── money │ │ ├── money.go │ │ └── money_test.go │ ├── proto │ │ ├── hipstershop.pb.go │ │ ├── hipstershop.pb.micro.go │ │ └── hipstershop.proto │ ├── rpc.go │ ├── static │ │ ├── favicon-cymbal.ico │ │ ├── favicon.ico │ │ ├── icons │ │ │ ├── Cymbal_NavLogo.svg │ │ │ ├── Hipster_Advert2.svg │ │ │ ├── Hipster_CartIcon.svg │ │ │ ├── Hipster_CheckOutIcon.svg │ │ │ ├── Hipster_CurrencyIcon.svg │ │ │ ├── Hipster_DownArrow.svg │ │ │ ├── Hipster_FacebookIcon.svg │ │ │ ├── Hipster_GooglePlayIcon.svg │ │ │ ├── Hipster_HelpIcon.svg │ │ │ ├── Hipster_HeroLogo.svg │ │ │ ├── Hipster_HeroLogoCyan.svg │ │ │ ├── Hipster_InstagramIcon.svg │ │ │ ├── Hipster_KitchenwareOffer.svg │ │ │ ├── Hipster_NavLogo.svg │ │ │ ├── Hipster_PinterestIcon.svg │ │ │ ├── Hipster_ProfileIcon.svg │ │ │ ├── Hipster_SearchIcon.svg │ │ │ ├── Hipster_TwitterIcon.svg │ │ │ ├── Hipster_UpDownControl.svg │ │ │ └── Hipster_YoutubeIcon.svg │ │ ├── images │ │ │ ├── Advert2BannerImage.png │ │ │ ├── AdvertBannerImage.png │ │ │ ├── HeroBannerImage.png │ │ │ ├── HeroBannerImage2.png │ │ │ ├── VRHeadsets.png │ │ │ ├── credits.txt │ │ │ ├── folded-clothes-on-white-chair-wide.jpg │ │ │ └── folded-clothes-on-white-chair.jpg │ │ ├── img │ │ │ └── products │ │ │ │ ├── bamboo-glass-jar.jpg │ │ │ │ ├── candle-holder.jpg │ │ │ │ ├── hairdryer.jpg │ │ │ │ ├── loafers.jpg │ │ │ │ ├── mug.jpg │ │ │ │ ├── salt-and-pepper-shakers.jpg │ │ │ │ ├── sunglasses.jpg │ │ │ │ ├── tank-top.jpg │ │ │ │ └── watch.jpg │ │ └── styles │ │ │ ├── cart.css │ │ │ ├── order.css │ │ │ └── styles.css │ ├── templates │ │ ├── ad.html │ │ ├── cart.html │ │ ├── error.html │ │ ├── footer.html │ │ ├── header.html │ │ ├── home.html │ │ ├── order.html │ │ ├── product.html │ │ └── recommendations.html │ └── tracing.go ├── loadgenerator │ ├── Dockerfile │ ├── locustfile.py │ ├── requirements.in │ └── requirements.txt ├── payment │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── config │ │ └── config.go │ ├── go.mod │ ├── go.sum │ ├── handler │ │ ├── health.go │ │ └── paymentservice.go │ ├── main.go │ ├── proto │ │ ├── health.pb.go │ │ ├── health.pb.micro.go │ │ ├── health.proto │ │ ├── paymentservice.pb.go │ │ ├── paymentservice.pb.micro.go │ │ └── paymentservice.proto │ └── tracing.go ├── productcatalog │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── config │ │ └── config.go │ ├── data │ │ └── products.json │ ├── go.mod │ ├── go.sum │ ├── handler │ │ ├── health.go │ │ └── productcatalogservice.go │ ├── main.go │ ├── proto │ │ ├── health.pb.go │ │ ├── health.pb.micro.go │ │ ├── health.proto │ │ ├── productcatalogservice.pb.go │ │ ├── productcatalogservice.pb.micro.go │ │ └── productcatalogservice.proto │ └── tracing.go ├── recommendation │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── config │ │ └── config.go │ ├── go.mod │ ├── go.sum │ ├── handler │ │ ├── health.go │ │ └── recommendationservice.go │ ├── main.go │ ├── proto │ │ ├── health.pb.go │ │ ├── health.pb.micro.go │ │ ├── health.proto │ │ ├── recommendationservice.pb.go │ │ ├── recommendationservice.pb.micro.go │ │ └── recommendationservice.proto │ └── tracing.go └── shipping │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── config │ └── config.go │ ├── go.mod │ ├── go.sum │ ├── handler │ ├── health.go │ ├── quote.go │ ├── shippingservice.go │ └── tracker.go │ ├── main.go │ ├── proto │ ├── health.pb.go │ ├── health.pb.micro.go │ ├── health.proto │ ├── shippingservice.pb.go │ ├── shippingservice.pb.micro.go │ └── shippingservice.proto │ └── tracing.go └── skaffold.yaml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | push: 8 | branches: 9 | - "main" 10 | 11 | jobs: 12 | testing: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v3 19 | with: 20 | go-version: 1.18 21 | 22 | - name: Test AdService 23 | run: | 24 | cd service/ad 25 | go vet ./... 26 | go test -v ./... 27 | 28 | 29 | - name: Test CartService 30 | run: | 31 | cd service/cart 32 | go vet ./... 33 | go test -v ./... 34 | 35 | - name: Notify of test failure 36 | if: failure() 37 | id: slack 38 | uses: slackapi/slack-github-action@v1.18.0 39 | with: 40 | channel-id: 'github-actions' 41 | slack-message: "Dashboard tests: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" 42 | env: 43 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 44 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | # Publish semver tags as releases. 6 | tags: ["*"] 7 | 8 | env: 9 | IMAGE_NAME: xpunch/go-micro-demo-ad 10 | 11 | jobs: 12 | ad: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: read 16 | packages: write 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v3 21 | 22 | # Extract metadata (tags, labels) for Docker 23 | # https://github.com/docker/metadata-action 24 | - name: Extract Docker metadata 25 | id: meta 26 | uses: docker/metadata-action@v3 27 | with: 28 | images: xpunch/go-micro-demo-ad 29 | 30 | # Login against a Docker registry except on PR 31 | # https://github.com/docker/login-action 32 | - name: Login to Docker Hub 33 | uses: docker/login-action@v1 34 | with: 35 | username: ${{ secrets.DOCKERHUB_USERNAME }} 36 | password: ${{ secrets.DOCKERHUB_TOKEN }} 37 | 38 | # Build and push Docker image with Buildx (don't push on PR) 39 | # https://github.com/docker/build-push-action 40 | - name: Build and push Docker image 41 | id: build-and-push 42 | uses: docker/build-push-action@v3 43 | with: 44 | context: "{{defaultContext}}:service/ad" 45 | push: true 46 | tags: ${{ steps.meta.outputs.tags }} 47 | labels: ${{ steps.meta.outputs.labels }} 48 | 49 | - name: Notify of docker publish failure 50 | if: failure() 51 | id: slack 52 | uses: slackapi/slack-github-action@v1.18.0 53 | with: 54 | channel-id: 'github-actions' 55 | slack-message: "AdService docker publish: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" 56 | env: 57 | SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # IDE 18 | .vscode 19 | __debug_bin -------------------------------------------------------------------------------- /docs/cymbal-shops.md: -------------------------------------------------------------------------------- 1 | # Cymbal Shops Branding 2 | 3 | By default, when you deploy this sample app, the "Online Boutique" branding (logo and wording) will be used. 4 | But you may want to use Google Cloud's fictitious company, _Cymbal Shops_, instead. 5 | 6 | To use "Cymbal Shops" branding, set the `CYMBAL_BRANDING` environment variable to `"true"` in the the Kubernetes manifest (`.yaml`) for the `frontend` Deployment. 7 | 8 | ``` 9 | apiVersion: apps/v1 10 | kind: Deployment 11 | metadata: 12 | name: frontend 13 | spec: 14 | ... 15 | template: 16 | ... 17 | spec: 18 | ... 19 | containers: 20 | ... 21 | env: 22 | ... 23 | - name: CYMBAL_BRANDING 24 | value: "true" 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/development-principles.md: -------------------------------------------------------------------------------- 1 | # Development Principles 2 | 3 | > **Note:** This document outlines guidances behind some development decisions 4 | > behind the Online Boutique 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 | ### Microservice implementations should not be complex 15 | 16 | Each service should provide a minimal implementation and try to avoid 17 | unnecessary code and logic that's not executed. 18 | 19 | Keep in mind that any service implementation is a decent example of “a GRPC 20 | application that runs on Kubernetes”. Keeping the source code short and 21 | navigable will serve this purpose. 22 | 23 | It is okay to have intentional inefficiencies in the code as they help 24 | illustrate the capabilities of profiling and diagnostics offerings. 25 | -------------------------------------------------------------------------------- /docs/img/architecture-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/docs/img/architecture-diagram.png -------------------------------------------------------------------------------- /docs/img/jaeger-dependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/docs/img/jaeger-dependencies.png -------------------------------------------------------------------------------- /docs/img/memorystore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/docs/img/memorystore.png -------------------------------------------------------------------------------- /docs/img/online-boutique-frontend-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/docs/img/online-boutique-frontend-1.png -------------------------------------------------------------------------------- /docs/img/online-boutique-frontend-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/docs/img/online-boutique-frontend-2.png -------------------------------------------------------------------------------- /docs/network-policies/network-policy-adservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: ad 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: ad 23 | policyTypes: 24 | - Ingress 25 | - Egress 26 | ingress: 27 | - from: 28 | - podSelector: 29 | matchLabels: 30 | app: frontend 31 | ports: 32 | - port: 9555 33 | protocol: TCP 34 | egress: 35 | - {} -------------------------------------------------------------------------------- /docs/network-policies/network-policy-cartservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: cart 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: cart 23 | policyTypes: 24 | - Ingress 25 | - Egress 26 | ingress: 27 | - from: 28 | - podSelector: 29 | matchLabels: 30 | app: frontend 31 | - podSelector: 32 | matchLabels: 33 | app: checkout 34 | ports: 35 | - port: 7070 36 | protocol: TCP 37 | egress: 38 | - {} -------------------------------------------------------------------------------- /docs/network-policies/network-policy-checkoutservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: checkout 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: checkout 23 | policyTypes: 24 | - Ingress 25 | - Egress 26 | ingress: 27 | - from: 28 | - podSelector: 29 | matchLabels: 30 | app: frontend 31 | ports: 32 | - port: 5050 33 | protocol: TCP 34 | egress: 35 | - {} -------------------------------------------------------------------------------- /docs/network-policies/network-policy-currencyservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: currency 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: currency 23 | policyTypes: 24 | - Ingress 25 | - Egress 26 | ingress: 27 | - from: 28 | - podSelector: 29 | matchLabels: 30 | app: frontend 31 | - podSelector: 32 | matchLabels: 33 | app: checkout 34 | ports: 35 | - port: 7000 36 | protocol: TCP 37 | egress: 38 | - {} -------------------------------------------------------------------------------- /docs/network-policies/network-policy-deny-all.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: deny-all 19 | spec: 20 | podSelector: {} 21 | policyTypes: 22 | - Ingress 23 | - Egress 24 | -------------------------------------------------------------------------------- /docs/network-policies/network-policy-emailservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: email 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: email 23 | policyTypes: 24 | - Ingress 25 | - Egress 26 | ingress: 27 | - from: 28 | - podSelector: 29 | matchLabels: 30 | app: checkout 31 | ports: 32 | - port: 8080 33 | protocol: TCP 34 | egress: 35 | - {} -------------------------------------------------------------------------------- /docs/network-policies/network-policy-frontend.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: frontend 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: frontend 23 | policyTypes: 24 | - Ingress 25 | - Egress 26 | ingress: 27 | - {} 28 | egress: 29 | - {} -------------------------------------------------------------------------------- /docs/network-policies/network-policy-loadgenerator.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: loadgenerator 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: loadgenerator 23 | policyTypes: 24 | - Egress 25 | egress: 26 | - {} -------------------------------------------------------------------------------- /docs/network-policies/network-policy-paymentservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: payment 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: payment 23 | policyTypes: 24 | - Ingress 25 | - Egress 26 | ingress: 27 | - from: 28 | - podSelector: 29 | matchLabels: 30 | app: checkout 31 | ports: 32 | - port: 50051 33 | protocol: TCP 34 | egress: 35 | - {} -------------------------------------------------------------------------------- /docs/network-policies/network-policy-productcatalogservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: productcatalog 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: productcatalog 23 | policyTypes: 24 | - Ingress 25 | - Egress 26 | ingress: 27 | - from: 28 | - podSelector: 29 | matchLabels: 30 | app: frontend 31 | - podSelector: 32 | matchLabels: 33 | app: checkout 34 | - podSelector: 35 | matchLabels: 36 | app: recommendation 37 | ports: 38 | - port: 3550 39 | protocol: TCP 40 | egress: 41 | - {} -------------------------------------------------------------------------------- /docs/network-policies/network-policy-recommendationservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: recommendation 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: recommendation 23 | policyTypes: 24 | - Ingress 25 | - Egress 26 | ingress: 27 | - from: 28 | - podSelector: 29 | matchLabels: 30 | app: frontend 31 | ports: 32 | - port: 8080 33 | protocol: TCP 34 | egress: 35 | - {} -------------------------------------------------------------------------------- /docs/network-policies/network-policy-redis.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: redis-cart 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: redis-cart 23 | policyTypes: 24 | - Ingress 25 | - Egress 26 | ingress: 27 | - from: 28 | - podSelector: 29 | matchLabels: 30 | app: cart 31 | ports: 32 | - port: 6379 33 | protocol: TCP 34 | egress: 35 | - {} 36 | -------------------------------------------------------------------------------- /docs/network-policies/network-policy-shippingservice.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 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.k8s.io/v1 16 | kind: NetworkPolicy 17 | metadata: 18 | name: shipping 19 | spec: 20 | podSelector: 21 | matchLabels: 22 | app: shipping 23 | policyTypes: 24 | - Ingress 25 | - Egress 26 | ingress: 27 | - from: 28 | - podSelector: 29 | matchLabels: 30 | app: frontend 31 | - podSelector: 32 | matchLabels: 33 | app: checkout 34 | ports: 35 | - port: 50051 36 | protocol: TCP 37 | egress: 38 | - {} -------------------------------------------------------------------------------- /docs/service-mesh.md: -------------------------------------------------------------------------------- 1 | # Deploying to an Istio-enabled cluster 2 | 3 | This repository provides an [`istio-manifests`](/istio-manifests) directory containing ingress resources (an Istio `Gateway` and `VirtualService`) needed to expose the app frontend running inside a Kubernetes cluster. 4 | 5 | You can apply these resources to your cluster in addition to the `kubernetes-manifests`, then use the Istio IngressGateway's external IP to view the app frontend. See the following instructions for Istio steps. 6 | 7 | ## Steps 8 | 9 | 1. Create a GKE cluster with at least 4 nodes, machine type `e2-standard-4`. 10 | 11 | ``` 12 | PROJECT_ID="" 13 | ZONE="" 14 | 15 | gcloud container clusters create onlineboutique \ 16 | --project=${PROJECT_ID} --zone=${ZONE} \ 17 | --machine-type=e2-standard-4 --num-nodes=4 18 | ``` 19 | 20 | 2. [Install Istio](https://istio.io/latest/docs/setup/getting-started/) on your cluster. 21 | 22 | 3. Enable Istio sidecar proxy injection in the `default` Kubernetes namespace. 23 | 24 | ```sh 25 | kubectl label namespace default istio-injection=enabled 26 | ``` 27 | 28 | 4. Apply all the manifests in the `/release` directory. This includes the Istio and Kubernetes manifests. 29 | 30 | ```sh 31 | kubectl apply -f ./release 32 | ``` 33 | 34 | 5. Run `kubectl get pods` to see pods are in a healthy and ready state. 35 | 36 | 6. Find the IP address of your Istio gateway Ingress or Service, and visit the 37 | application frontend in a web browser. 38 | 39 | ```sh 40 | INGRESS_HOST="$(kubectl -n istio-system get service istio-ingressgateway \ 41 | -o jsonpath='{.status.loadBalancer.ingress[0].ip}')" 42 | echo "$INGRESS_HOST" 43 | ``` 44 | 45 | ```sh 46 | curl -v "http://$INGRESS_HOST" 47 | ``` 48 | 49 | 50 | ## Additional service mesh demos using OnlineBoutique 51 | 52 | - [Canary deployment](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/istio-canary-gke) 53 | - [Security (mTLS, JWT, Authorization)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/security-intro) 54 | - [Cloud Operations (Stackdriver)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/istio-stackdriver) 55 | - [Stackdriver metrics (Open source Istio)](https://github.com/GoogleCloudPlatform/istio-samples/tree/master/stackdriver-metrics) 56 | 57 | -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | go 1.19 2 | 3 | use ( 4 | ./service/ad 5 | ./service/cart 6 | ./service/checkout 7 | ./service/currency 8 | ./service/email 9 | ./service/frontend 10 | ./service/payment 11 | ./service/productcatalog 12 | ./service/recommendation 13 | ./service/shipping 14 | ) 15 | -------------------------------------------------------------------------------- /go.work.sum: -------------------------------------------------------------------------------- 1 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 2 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 3 | github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= 4 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 5 | -------------------------------------------------------------------------------- /infra/istio/allow-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: allow-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: allow-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 | -------------------------------------------------------------------------------- /infra/istio/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 | -------------------------------------------------------------------------------- /infra/istio/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 | -------------------------------------------------------------------------------- /infra/k8s/README.md: -------------------------------------------------------------------------------- 1 | # ./k8s 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 | -------------------------------------------------------------------------------- /infra/k8s/adservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: ad 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: ad 9 | template: 10 | metadata: 11 | labels: 12 | app: ad 13 | spec: 14 | serviceAccountName: go-micro 15 | terminationGracePeriodSeconds: 5 16 | containers: 17 | - name: server 18 | image: ad 19 | ports: 20 | - containerPort: 9555 21 | env: 22 | - name: PORT 23 | value: "9555" 24 | - name: MICRO_REGISTRY 25 | value: "kubernetes" 26 | - name: TRACING_ENABLE 27 | value: "true" 28 | - name: TRACING_JAEGER_URL 29 | value: "http://jaeger-collector.default.svc.cluster.local:14268/api/traces" 30 | # resources: 31 | # requests: 32 | # cpu: 100m 33 | # memory: 64Mi 34 | # limits: 35 | # cpu: 200m 36 | # memory: 128Mi 37 | readinessProbe: 38 | initialDelaySeconds: 20 39 | periodSeconds: 15 40 | exec: 41 | command: ["/bin/grpc_health_probe", "-addr=:9555"] 42 | livenessProbe: 43 | initialDelaySeconds: 20 44 | periodSeconds: 15 45 | exec: 46 | command: ["/bin/grpc_health_probe", "-addr=:9555"] 47 | --- 48 | apiVersion: v1 49 | kind: Service 50 | metadata: 51 | name: ad 52 | spec: 53 | type: ClusterIP 54 | selector: 55 | app: ad 56 | ports: 57 | - name: grpc 58 | port: 9555 59 | targetPort: 9555 60 | -------------------------------------------------------------------------------- /infra/k8s/cartservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: cart 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: cart 9 | template: 10 | metadata: 11 | labels: 12 | app: cart 13 | spec: 14 | serviceAccountName: go-micro 15 | terminationGracePeriodSeconds: 5 16 | securityContext: 17 | fsGroup: 1000 18 | runAsGroup: 1000 19 | runAsNonRoot: true 20 | runAsUser: 1000 21 | containers: 22 | - name: server 23 | securityContext: 24 | allowPrivilegeEscalation: false 25 | capabilities: 26 | drop: 27 | - all 28 | privileged: false 29 | readOnlyRootFilesystem: true 30 | image: cart 31 | ports: 32 | - containerPort: 7070 33 | env: 34 | - name: PORT 35 | value: "7070" 36 | - name: MICRO_REGISTRY 37 | value: "kubernetes" 38 | - name: REDIS_ADDR 39 | value: "redis-cart:6379" 40 | - name: TRACING_ENABLE 41 | value: "true" 42 | - name: TRACING_JAEGER_URL 43 | value: "http://jaeger-collector.default.svc.cluster.local:14268/api/traces" 44 | # resources: 45 | # requests: 46 | # cpu: 100m 47 | # memory: 64Mi 48 | # limits: 49 | # cpu: 200m 50 | # memory: 128Mi 51 | readinessProbe: 52 | initialDelaySeconds: 15 53 | exec: 54 | command: 55 | ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"] 56 | livenessProbe: 57 | initialDelaySeconds: 15 58 | periodSeconds: 10 59 | exec: 60 | command: 61 | ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"] 62 | --- 63 | apiVersion: v1 64 | kind: Service 65 | metadata: 66 | name: cart 67 | spec: 68 | type: ClusterIP 69 | selector: 70 | app: cart 71 | ports: 72 | - name: grpc 73 | port: 7070 74 | targetPort: 7070 75 | -------------------------------------------------------------------------------- /infra/k8s/checkoutservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: checkout 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: checkout 9 | template: 10 | metadata: 11 | labels: 12 | app: checkout 13 | spec: 14 | serviceAccountName: go-micro 15 | containers: 16 | - name: server 17 | image: checkout 18 | ports: 19 | - containerPort: 5050 20 | readinessProbe: 21 | exec: 22 | command: ["/bin/grpc_health_probe", "-addr=:5050"] 23 | livenessProbe: 24 | exec: 25 | command: ["/bin/grpc_health_probe", "-addr=:5050"] 26 | env: 27 | - name: PORT 28 | value: "5050" 29 | - name: MICRO_REGISTRY 30 | value: "kubernetes" 31 | - name: PRODUCTCATALOGSERVICE 32 | value: "productcatalog" 33 | - name: SHIPPINGSERVICE 34 | value: "shipping" 35 | - name: PAYMENTSERVICE 36 | value: "payment" 37 | - name: EMAILSERVICE 38 | value: "email" 39 | - name: CURRENCYSERVICE 40 | value: "currency" 41 | - name: CARTSERVICE 42 | value: "cart" 43 | - name: TRACING_ENABLE 44 | value: "true" 45 | - name: TRACING_JAEGER_URL 46 | value: "http://jaeger-collector.default.svc.cluster.local:14268/api/traces" 47 | # resources: 48 | # requests: 49 | # cpu: 100m 50 | # memory: 64Mi 51 | # limits: 52 | # cpu: 200m 53 | # memory: 128Mi 54 | --- 55 | apiVersion: v1 56 | kind: Service 57 | metadata: 58 | name: checkout 59 | spec: 60 | type: ClusterIP 61 | selector: 62 | app: checkout 63 | ports: 64 | - name: grpc 65 | port: 5050 66 | targetPort: 5050 67 | -------------------------------------------------------------------------------- /infra/k8s/currencyservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: currency 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: currency 9 | template: 10 | metadata: 11 | labels: 12 | app: currency 13 | spec: 14 | serviceAccountName: go-micro 15 | terminationGracePeriodSeconds: 5 16 | containers: 17 | - name: server 18 | image: currency 19 | ports: 20 | - name: grpc 21 | containerPort: 7000 22 | env: 23 | - name: PORT 24 | value: "7000" 25 | - name: MICRO_REGISTRY 26 | value: "kubernetes" 27 | - name: TRACING_ENABLE 28 | value: "true" 29 | - name: TRACING_JAEGER_URL 30 | value: "http://jaeger-collector.default.svc.cluster.local:14268/api/traces" 31 | readinessProbe: 32 | exec: 33 | command: ["/bin/grpc_health_probe", "-addr=:7000"] 34 | livenessProbe: 35 | exec: 36 | command: ["/bin/grpc_health_probe", "-addr=:7000"] 37 | # resources: 38 | # requests: 39 | # cpu: 100m 40 | # memory: 64Mi 41 | # limits: 42 | # cpu: 200m 43 | # memory: 128Mi 44 | --- 45 | apiVersion: v1 46 | kind: Service 47 | metadata: 48 | name: currency 49 | spec: 50 | type: ClusterIP 51 | selector: 52 | app: currency 53 | ports: 54 | - name: grpc 55 | port: 7000 56 | targetPort: 7000 57 | -------------------------------------------------------------------------------- /infra/k8s/emailservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: email 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: email 9 | template: 10 | metadata: 11 | labels: 12 | app: email 13 | spec: 14 | serviceAccountName: go-micro 15 | terminationGracePeriodSeconds: 5 16 | containers: 17 | - name: server 18 | image: email 19 | ports: 20 | - containerPort: 8080 21 | env: 22 | - name: PORT 23 | value: "8080" 24 | - name: MICRO_REGISTRY 25 | value: "kubernetes" 26 | - name: TRACING_ENABLE 27 | value: "true" 28 | - name: TRACING_JAEGER_URL 29 | value: "http://jaeger-collector.default.svc.cluster.local:14268/api/traces" 30 | readinessProbe: 31 | periodSeconds: 5 32 | exec: 33 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 34 | livenessProbe: 35 | periodSeconds: 5 36 | exec: 37 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 38 | # resources: 39 | # requests: 40 | # cpu: 100m 41 | # memory: 64Mi 42 | # limits: 43 | # cpu: 200m 44 | # memory: 128Mi 45 | --- 46 | apiVersion: v1 47 | kind: Service 48 | metadata: 49 | name: email 50 | spec: 51 | type: ClusterIP 52 | selector: 53 | app: email 54 | ports: 55 | - name: grpc 56 | port: 5000 57 | targetPort: 8080 58 | -------------------------------------------------------------------------------- /infra/k8s/gomicro.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: go-micro 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: ClusterRole 8 | metadata: 9 | name: go-micro-registry 10 | rules: 11 | - apiGroups: 12 | - "" 13 | resources: 14 | - pods 15 | verbs: 16 | - list 17 | - patch 18 | - watch 19 | --- 20 | apiVersion: rbac.authorization.k8s.io/v1 21 | kind: RoleBinding 22 | metadata: 23 | name: go-micro-registry 24 | roleRef: 25 | apiGroup: rbac.authorization.k8s.io 26 | kind: ClusterRole 27 | name: go-micro-registry 28 | subjects: 29 | - kind: ServiceAccount 30 | name: go-micro 31 | --- 32 | kind: Deployment 33 | apiVersion: apps/v1 34 | metadata: 35 | name: go-micro-dashboard 36 | labels: 37 | app: go-micro-dashboard 38 | spec: 39 | replicas: 1 40 | selector: 41 | matchLabels: 42 | app: go-micro-dashboard 43 | template: 44 | metadata: 45 | labels: 46 | app: go-micro-dashboard 47 | spec: 48 | serviceAccountName: go-micro 49 | containers: 50 | - image: xpunch/go-micro-dashboard:latest 51 | imagePullPolicy: IfNotPresent 52 | name: dashboard 53 | ports: 54 | - containerPort: 80 55 | protocol: TCP 56 | env: 57 | - name: MICRO_REGISTRY 58 | value: "kubernetes" 59 | - name: MICRO_CLIENT_RETRIES 60 | value: "0" 61 | - name: SERVER_ADDRESS 62 | value: ":80" 63 | - name: SERVER_AUTH_USERNAME 64 | value: "admin" 65 | - name: SERVER_AUTH_PASSWORD 66 | value: "micro" 67 | # resources: 68 | # requests: 69 | # cpu: 100m 70 | # memory: 64Mi 71 | # limits: 72 | # cpu: 200m 73 | # memory: 128Mi 74 | --- 75 | apiVersion: v1 76 | kind: Service 77 | metadata: 78 | name: go-micro-dashboard 79 | spec: 80 | type: ClusterIP 81 | selector: 82 | app: go-micro-dashboard 83 | ports: 84 | - name: web 85 | port: 80 86 | targetPort: 80 -------------------------------------------------------------------------------- /infra/k8s/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 | serviceAccountName: go-micro 31 | terminationGracePeriodSeconds: 5 32 | restartPolicy: Always 33 | initContainers: 34 | - command: 35 | - /bin/sh 36 | - -exc 37 | - | 38 | echo "Init container pinging frontend: ${FRONTEND_ADDR}..." 39 | STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') 40 | if test $STATUSCODE -ne 200; then 41 | echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" 42 | exit 1 43 | fi 44 | name: frontend-check 45 | image: busybox:latest 46 | env: 47 | - name: FRONTEND_ADDR 48 | value: "frontend:80" 49 | containers: 50 | - name: main 51 | image: loadgenerator 52 | env: 53 | - name: FRONTEND_ADDR 54 | value: "frontend:80" 55 | - name: USERS 56 | value: "10" 57 | # resources: 58 | # requests: 59 | # cpu: 100m 60 | # memory: 64Mi 61 | # limits: 62 | # cpu: 200m 63 | # memory: 128Mi 64 | -------------------------------------------------------------------------------- /infra/k8s/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: payment 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: payment 23 | template: 24 | metadata: 25 | labels: 26 | app: payment 27 | spec: 28 | serviceAccountName: go-micro 29 | terminationGracePeriodSeconds: 5 30 | containers: 31 | - name: server 32 | image: payment 33 | ports: 34 | - containerPort: 50051 35 | env: 36 | - name: PORT 37 | value: "50051" 38 | - name: MICRO_REGISTRY 39 | value: "kubernetes" 40 | - name: TRACING_ENABLE 41 | value: "true" 42 | - name: TRACING_JAEGER_URL 43 | value: "http://jaeger-collector.default.svc.cluster.local:14268/api/traces" 44 | readinessProbe: 45 | exec: 46 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 47 | livenessProbe: 48 | exec: 49 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 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: payment 62 | spec: 63 | type: ClusterIP 64 | selector: 65 | app: payment 66 | ports: 67 | - name: grpc 68 | port: 50051 69 | targetPort: 50051 70 | -------------------------------------------------------------------------------- /infra/k8s/productcatalogservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: productcatalog 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: productcatalog 9 | template: 10 | metadata: 11 | labels: 12 | app: productcatalog 13 | spec: 14 | serviceAccountName: go-micro 15 | terminationGracePeriodSeconds: 5 16 | containers: 17 | - name: server 18 | image: productcatalog 19 | ports: 20 | - containerPort: 3550 21 | env: 22 | - name: PORT 23 | value: "3550" 24 | - name: MICRO_REGISTRY 25 | value: "kubernetes" 26 | - name: TRACING_ENABLE 27 | value: "true" 28 | - name: TRACING_JAEGER_URL 29 | value: "http://jaeger-collector.default.svc.cluster.local:14268/api/traces" 30 | readinessProbe: 31 | exec: 32 | command: ["/bin/grpc_health_probe", "-addr=:3550"] 33 | livenessProbe: 34 | exec: 35 | command: ["/bin/grpc_health_probe", "-addr=:3550"] 36 | # resources: 37 | # requests: 38 | # cpu: 100m 39 | # memory: 64Mi 40 | # limits: 41 | # cpu: 200m 42 | # memory: 128Mi 43 | --- 44 | apiVersion: v1 45 | kind: Service 46 | metadata: 47 | name: productcatalog 48 | spec: 49 | type: ClusterIP 50 | selector: 51 | app: productcatalog 52 | ports: 53 | - name: grpc 54 | port: 3550 55 | targetPort: 3550 56 | -------------------------------------------------------------------------------- /infra/k8s/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: recommendation 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: recommendation 23 | template: 24 | metadata: 25 | labels: 26 | app: recommendation 27 | spec: 28 | serviceAccountName: go-micro 29 | terminationGracePeriodSeconds: 5 30 | containers: 31 | - name: server 32 | image: recommendation 33 | ports: 34 | - containerPort: 8080 35 | readinessProbe: 36 | periodSeconds: 5 37 | exec: 38 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 39 | livenessProbe: 40 | periodSeconds: 5 41 | exec: 42 | command: ["/bin/grpc_health_probe", "-addr=:8080"] 43 | env: 44 | - name: PORT 45 | value: "8080" 46 | - name: MICRO_REGISTRY 47 | value: "kubernetes" 48 | - name: PRODUCTCATALOGSERVICE 49 | value: "productcatalog" 50 | - name: TRACING_ENABLE 51 | value: "true" 52 | - name: TRACING_JAEGER_URL 53 | value: "http://jaeger-collector.default.svc.cluster.local:14268/api/traces" 54 | # resources: 55 | # requests: 56 | # cpu: 100m 57 | # memory: 64Mi 58 | # limits: 59 | # cpu: 200m 60 | # memory: 128Mi 61 | --- 62 | apiVersion: v1 63 | kind: Service 64 | metadata: 65 | name: recommendation 66 | spec: 67 | type: ClusterIP 68 | selector: 69 | app: recommendation 70 | ports: 71 | - name: grpc 72 | port: 8080 73 | targetPort: 8080 74 | -------------------------------------------------------------------------------- /infra/k8s/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: tls-redis 65 | port: 6379 66 | targetPort: 6379 67 | -------------------------------------------------------------------------------- /infra/k8s/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: shipping 19 | spec: 20 | selector: 21 | matchLabels: 22 | app: shipping 23 | template: 24 | metadata: 25 | labels: 26 | app: shipping 27 | spec: 28 | serviceAccountName: go-micro 29 | containers: 30 | - name: server 31 | image: shipping 32 | ports: 33 | - containerPort: 50051 34 | env: 35 | - name: PORT 36 | value: "50051" 37 | - name: MICRO_REGISTRY 38 | value: "kubernetes" 39 | - name: TRACING_ENABLE 40 | value: "true" 41 | - name: TRACING_JAEGER_URL 42 | value: "http://jaeger-collector.default.svc.cluster.local:14268/api/traces" 43 | readinessProbe: 44 | periodSeconds: 5 45 | exec: 46 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 47 | livenessProbe: 48 | exec: 49 | command: ["/bin/grpc_health_probe", "-addr=:50051"] 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: shipping 62 | spec: 63 | type: ClusterIP 64 | selector: 65 | app: shipping 66 | ports: 67 | - name: grpc 68 | port: 50051 69 | targetPort: 50051 70 | -------------------------------------------------------------------------------- /service/ad/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | Dockerfile 3 | -------------------------------------------------------------------------------- /service/ad/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # IDE 18 | .vscode 19 | 20 | # don't commit the service binary to vcs 21 | ad 22 | -------------------------------------------------------------------------------- /service/ad/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.0-alpine AS builder 2 | 3 | # Set Go env 4 | ENV CGO_ENABLED=0 GOOS=linux 5 | WORKDIR /go/src/hipstershop 6 | 7 | # Install dependencies 8 | RUN apk --update --no-cache add ca-certificates make protoc 9 | 10 | # Download grpc_health_probe 11 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ 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 | # Build Go binary 16 | COPY Makefile go.mod go.sum ./ 17 | RUN go env -w GOPROXY=https://goproxy.io,direct/ 18 | RUN make init && go mod download 19 | COPY . . 20 | RUN make proto tidy 21 | 22 | # Skaffold passes in debug-oriented compiler flags 23 | ARG SKAFFOLD_GO_GCFLAGS 24 | RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/src/hipstershop/ad . 25 | 26 | # Deployment container 27 | FROM scratch 28 | 29 | WORKDIR /hipstershop 30 | 31 | # Definition of this variable is used by 'skaffold debug' to identify a golang binary. 32 | # Default behavior - a failure prints a stack trace for the current goroutine. 33 | # See https://golang.org/pkg/runtime/ 34 | ENV GOTRACEBACK=single 35 | COPY --from=builder /etc/ssl/certs /etc/ssl/certs 36 | COPY --from=builder /bin/grpc_health_probe /bin/ 37 | COPY --from=builder /go/src/hipstershop/ad /hipstershop/ 38 | 39 | ENTRYPOINT ["/hipstershop/ad"] -------------------------------------------------------------------------------- /service/ad/Makefile: -------------------------------------------------------------------------------- 1 | GOPATH:=$(shell go env GOPATH) 2 | 3 | .PHONY: init 4 | init: 5 | @go get -u google.golang.org/protobuf/proto 6 | @go install github.com/golang/protobuf/protoc-gen-go@latest 7 | @go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest 8 | 9 | .PHONY: proto 10 | proto: 11 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/ad.proto 12 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/health.proto 13 | 14 | .PHONY: update 15 | update: 16 | @go get -u 17 | 18 | .PHONY: tidy 19 | tidy: 20 | @go mod tidy 21 | 22 | .PHONY: build 23 | build: 24 | @go build -o ad *.go 25 | 26 | .PHONY: test 27 | test: 28 | @go test -v ./... -cover 29 | 30 | .PHONY: docker 31 | docker: 32 | @docker build -t ad:latest . -------------------------------------------------------------------------------- /service/ad/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "go-micro.dev/v5/config" 8 | "go-micro.dev/v5/config/source/env" 9 | ) 10 | 11 | type Config struct { 12 | Port int 13 | Tracing TracingConfig 14 | } 15 | 16 | type TracingConfig struct { 17 | Enable bool 18 | Jaeger JaegerConfig 19 | } 20 | 21 | type JaegerConfig struct { 22 | URL string 23 | } 24 | 25 | var cfg *Config = &Config{ 26 | Port: 9555, 27 | } 28 | 29 | func Address() string { 30 | return fmt.Sprintf(":%d", cfg.Port) 31 | } 32 | 33 | func Tracing() TracingConfig { 34 | return cfg.Tracing 35 | } 36 | 37 | func Load() error { 38 | configor, err := config.NewConfig(config.WithSource(env.NewSource())) 39 | if err != nil { 40 | return errors.Wrap(err, "configor.New") 41 | } 42 | if err := configor.Load(); err != nil { 43 | return errors.Wrap(err, "configor.Load") 44 | } 45 | if err := configor.Scan(cfg); err != nil { 46 | return errors.Wrap(err, "configor.Scan") 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /service/ad/handler/adservice.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | 7 | pb "github.com/go-micro/demo/ad/proto" 8 | ) 9 | 10 | const MAX_ADS_TO_SERVE = 2 11 | 12 | var adsMap = createAdsMap() 13 | 14 | type AdService struct{} 15 | 16 | func (s *AdService) GetAds(ctx context.Context, in *pb.AdRequest, out *pb.AdResponse) error { 17 | allAds := make([]*pb.Ad, 0) 18 | if len(in.ContextKeys) > 0 { 19 | for _, category := range in.ContextKeys { 20 | ads := getAdsByCategory(category) 21 | allAds = append(allAds, ads...) 22 | } 23 | if len(allAds) == 0 { 24 | allAds = getRandomAds() 25 | } 26 | } else { 27 | allAds = getRandomAds() 28 | } 29 | out.Ads = allAds 30 | return nil 31 | } 32 | 33 | func getAdsByCategory(category string) []*pb.Ad { 34 | return adsMap[category] 35 | } 36 | 37 | func getRandomAds() []*pb.Ad { 38 | ads := make([]*pb.Ad, 0, MAX_ADS_TO_SERVE) 39 | allAds := make([]*pb.Ad, 0, 7) 40 | for _, ads := range adsMap { 41 | allAds = append(allAds, ads...) 42 | } 43 | for i := 0; i < MAX_ADS_TO_SERVE; i++ { 44 | ads = append(ads, allAds[rand.Intn(len(allAds))]) 45 | } 46 | return ads 47 | } 48 | 49 | func createAdsMap() map[string][]*pb.Ad { 50 | hairdryer := &pb.Ad{RedirectUrl: "/product/2ZYFJ3GM2N", Text: "Hairdryer for sale. 50% off."} 51 | tankTop := &pb.Ad{RedirectUrl: "/product/66VCHSJNUP", Text: "Tank top for sale. 20% off."} 52 | candleHolder := &pb.Ad{RedirectUrl: "/product/0PUK6V6EV0", Text: "Candle holder for sale. 30% off."} 53 | bambooGlassJar := &pb.Ad{RedirectUrl: "/product/9SIQT8TOJO", Text: "Bamboo glass jar for sale. 10% off."} 54 | watch := &pb.Ad{RedirectUrl: "/product/1YMWWN1N4O", Text: "Watch for sale. Buy one, get second kit for free"} 55 | mug := &pb.Ad{RedirectUrl: "/product/6E92ZMYYFZ", Text: "Mug for sale. Buy two, get third one for free"} 56 | loafers := &pb.Ad{RedirectUrl: "/product/L9ECAV7KIM", Text: "Loafers for sale. Buy one, get second one for free"} 57 | return map[string][]*pb.Ad{ 58 | "clothing": {tankTop}, 59 | "accessories": {watch}, 60 | "footwear": {loafers}, 61 | "hair": {hairdryer}, 62 | "decor": {candleHolder}, 63 | "kitchen": {bambooGlassJar, mug}, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /service/ad/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | 9 | pb "github.com/go-micro/demo/ad/proto" 10 | ) 11 | 12 | type Health struct{} 13 | 14 | func (h *Health) Check(ctx context.Context, req *pb.HealthCheckRequest, rsp *pb.HealthCheckResponse) error { 15 | rsp.Status = pb.HealthCheckResponse_SERVING 16 | return nil 17 | } 18 | 19 | func (h *Health) Watch(ctx context.Context, req *pb.HealthCheckRequest, stream pb.Health_WatchStream) error { 20 | return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") 21 | } 22 | -------------------------------------------------------------------------------- /service/ad/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | grpcc "github.com/go-micro/plugins/v4/client/grpc" 9 | _ "github.com/go-micro/plugins/v4/registry/kubernetes" 10 | grpcs "github.com/go-micro/plugins/v4/server/grpc" 11 | "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry" 12 | "go-micro.dev/v5" 13 | "go-micro.dev/v5/logger" 14 | "go-micro.dev/v5/server" 15 | "go.opentelemetry.io/otel" 16 | "go.opentelemetry.io/otel/propagation" 17 | 18 | "github.com/go-micro/demo/ad/config" 19 | "github.com/go-micro/demo/ad/handler" 20 | pb "github.com/go-micro/demo/ad/proto" 21 | ) 22 | 23 | var ( 24 | name = "ad" 25 | version = "1.0.0" 26 | ) 27 | 28 | func main() { 29 | // Load conigurations 30 | if err := config.Load(); err != nil { 31 | logger.Fatal(err) 32 | } 33 | 34 | // Create service 35 | srv := micro.NewService( 36 | micro.Server(grpcs.NewServer()), 37 | micro.Client(grpcc.NewClient()), 38 | ) 39 | opts := []micro.Option{ 40 | micro.Name(name), 41 | micro.Version(version), 42 | micro.Address(config.Address()), 43 | } 44 | if cfg := config.Tracing(); cfg.Enable { 45 | tp, err := newTracerProvider(name, srv.Server().Options().Id, cfg.Jaeger.URL) 46 | if err != nil { 47 | logger.Fatal(err) 48 | } 49 | defer func() { 50 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 51 | defer cancel() 52 | if err := tp.Shutdown(ctx); err != nil { 53 | logger.Fatal(err) 54 | } 55 | }() 56 | otel.SetTracerProvider(tp) 57 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 58 | traceOpts := []opentelemetry.Option{ 59 | opentelemetry.WithHandleFilter(func(ctx context.Context, r server.Request) bool { 60 | if e := r.Endpoint(); strings.HasPrefix(e, "Health.") { 61 | return true 62 | } 63 | return false 64 | }), 65 | } 66 | opts = append(opts, micro.WrapHandler(opentelemetry.NewHandlerWrapper(traceOpts...))) 67 | } 68 | srv.Init(opts...) 69 | 70 | // Register handler 71 | if err := pb.RegisterAdServiceHandler(srv.Server(), new(handler.AdService)); err != nil { 72 | logger.Fatal(err) 73 | } 74 | if err := pb.RegisterHealthHandler(srv.Server(), new(handler.Health)); err != nil { 75 | logger.Fatal(err) 76 | } 77 | 78 | // Run service 79 | if err := srv.Run(); err != nil { 80 | logger.Fatal(err) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /service/ad/proto/adservice.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: proto/ad.proto 3 | 4 | package hipstershop 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "google.golang.org/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | api "go-micro.dev/v5/api" 15 | client "go-micro.dev/v5/client" 16 | server "go-micro.dev/v5/server" 17 | ) 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var _ = proto.Marshal 21 | var _ = fmt.Errorf 22 | var _ = math.Inf 23 | 24 | // Reference imports to suppress errors if they are not otherwise used. 25 | var _ api.Endpoint 26 | var _ context.Context 27 | var _ client.Option 28 | var _ server.Option 29 | 30 | // Api Endpoints for AdService service 31 | 32 | func NewAdServiceEndpoints() []*api.Endpoint { 33 | return []*api.Endpoint{} 34 | } 35 | 36 | // Client API for AdService service 37 | 38 | type AdService interface { 39 | GetAds(ctx context.Context, in *AdRequest, opts ...client.CallOption) (*AdResponse, error) 40 | } 41 | 42 | type adService struct { 43 | c client.Client 44 | name string 45 | } 46 | 47 | func NewAdService(name string, c client.Client) AdService { 48 | return &adService{ 49 | c: c, 50 | name: name, 51 | } 52 | } 53 | 54 | func (c *adService) GetAds(ctx context.Context, in *AdRequest, opts ...client.CallOption) (*AdResponse, error) { 55 | req := c.c.NewRequest(c.name, "AdService.GetAds", in) 56 | out := new(AdResponse) 57 | err := c.c.Call(ctx, req, out, opts...) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return out, nil 62 | } 63 | 64 | // Server API for AdService service 65 | 66 | type AdServiceHandler interface { 67 | GetAds(context.Context, *AdRequest, *AdResponse) error 68 | } 69 | 70 | func RegisterAdServiceHandler(s server.Server, hdlr AdServiceHandler, opts ...server.HandlerOption) error { 71 | type adService interface { 72 | GetAds(ctx context.Context, in *AdRequest, out *AdResponse) error 73 | } 74 | type AdService struct { 75 | adService 76 | } 77 | h := &adServiceHandler{hdlr} 78 | return s.Handle(s.NewHandler(&AdService{h}, opts...)) 79 | } 80 | 81 | type adServiceHandler struct { 82 | AdServiceHandler 83 | } 84 | 85 | func (h *adServiceHandler) GetAds(ctx context.Context, in *AdRequest, out *AdResponse) error { 86 | return h.AdServiceHandler.GetAds(ctx, in, out) 87 | } 88 | -------------------------------------------------------------------------------- /service/ad/proto/adservice.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | syntax = "proto3"; 16 | 17 | package hipstershop; 18 | option go_package = "./proto;hipstershop"; 19 | 20 | // ------------Ad service------------------ 21 | 22 | service AdService { 23 | rpc GetAds(AdRequest) returns (AdResponse) {} 24 | } 25 | 26 | message AdRequest { 27 | // List of important key words from the current page describing the context. 28 | repeated string context_keys = 1; 29 | } 30 | 31 | message AdResponse { 32 | repeated Ad ads = 1; 33 | } 34 | 35 | message Ad { 36 | // url to redirect to when an ad is clicked. 37 | string redirect_url = 1; 38 | 39 | // short advertisement text to display. 40 | string text = 2; 41 | } 42 | -------------------------------------------------------------------------------- /service/ad/proto/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 hipstershop; 21 | 22 | option go_package = "./proto;hipstershop"; 23 | 24 | message HealthCheckRequest { 25 | string service = 1; 26 | } 27 | 28 | message HealthCheckResponse { 29 | enum ServingStatus { 30 | UNKNOWN = 0; 31 | SERVING = 1; 32 | NOT_SERVING = 2; 33 | SERVICE_UNKNOWN = 3; // Used only by the Watch method. 34 | } 35 | ServingStatus status = 1; 36 | } 37 | 38 | service Health { 39 | // If the requested service is unknown, the call will fail with status 40 | // NOT_FOUND. 41 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse); 42 | 43 | // Performs a watch for the serving status of the requested service. 44 | // The server will immediately send back a message indicating the current 45 | // serving status. It will then subsequently send a new message whenever 46 | // the service's serving status changes. 47 | // 48 | // If the requested service is unknown when the call is received, the 49 | // server will send a message setting the serving status to 50 | // SERVICE_UNKNOWN but will *not* terminate the call. If at some 51 | // future point, the serving status of the service becomes known, the 52 | // server will send a new message with the service's serving status. 53 | // 54 | // If the call terminates with status UNIMPLEMENTED, then clients 55 | // should assume this method is not supported and should not retry the 56 | // call. If the call terminates with any other status (including OK), 57 | // clients should retry the call with appropriate exponential backoff. 58 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse); 59 | } -------------------------------------------------------------------------------- /service/ad/tracing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go.opentelemetry.io/otel/exporters/jaeger" 5 | "go.opentelemetry.io/otel/sdk/resource" 6 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 7 | semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 8 | ) 9 | 10 | // newTracerProvider returns an OpenTelemetry TracerProvider configured to use 11 | // the Jaeger exporter that will send spans to the provided url. The returned 12 | // TracerProvider will also use a Resource configured with all the information 13 | // about the application. 14 | func newTracerProvider(serviceName, serviceID, url string) (*tracesdk.TracerProvider, error) { 15 | // Create the Jaeger exporter 16 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 17 | if err != nil { 18 | return nil, err 19 | } 20 | tp := tracesdk.NewTracerProvider( 21 | tracesdk.WithBatcher(exp), 22 | tracesdk.WithResource(resource.NewWithAttributes( 23 | semconv.SchemaURL, 24 | semconv.ServiceNameKey.String(serviceName), 25 | semconv.ServiceVersionKey.String(version), 26 | semconv.ServiceInstanceIDKey.String(serviceID), 27 | )), 28 | ) 29 | return tp, nil 30 | } 31 | -------------------------------------------------------------------------------- /service/cart/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | Dockerfile 3 | -------------------------------------------------------------------------------- /service/cart/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # IDE 18 | .vscode 19 | 20 | # don't commit the service binary to vcs 21 | cart 22 | -------------------------------------------------------------------------------- /service/cart/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.0-alpine AS builder 2 | 3 | # Set Go env 4 | ENV CGO_ENABLED=0 GOOS=linux 5 | WORKDIR /go/src/hipstershop 6 | 7 | # Install dependencies 8 | RUN apk --update --no-cache add ca-certificates make protoc 9 | 10 | # Download grpc_health_probe 11 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ 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 | # Build Go binary 16 | COPY Makefile go.mod go.sum ./ 17 | RUN go env -w GOPROXY=https://goproxy.io,direct/ 18 | RUN make init && go mod download 19 | COPY . . 20 | RUN make proto tidy 21 | 22 | # Skaffold passes in debug-oriented compiler flags 23 | ARG SKAFFOLD_GO_GCFLAGS 24 | RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/src/hipstershop/cart . 25 | 26 | # Deployment container 27 | FROM scratch 28 | 29 | # Definition of this variable is used by 'skaffold debug' to identify a golang binary. 30 | # Default behavior - a failure prints a stack trace for the current goroutine. 31 | # See https://golang.org/pkg/runtime/ 32 | ENV GOTRACEBACK=single 33 | 34 | COPY --from=builder /etc/ssl/certs /etc/ssl/certs 35 | COPY --from=builder /bin/grpc_health_probe /bin/ 36 | COPY --from=builder /go/src/hipstershop/cart /hipstershop/cart 37 | 38 | ENTRYPOINT ["/hipstershop/cart"] 39 | -------------------------------------------------------------------------------- /service/cart/Makefile: -------------------------------------------------------------------------------- 1 | GOPATH:=$(shell go env GOPATH) 2 | 3 | .PHONY: init 4 | init: 5 | @go get -u google.golang.org/protobuf/proto 6 | @go install github.com/golang/protobuf/protoc-gen-go@latest 7 | @go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest 8 | 9 | .PHONY: proto 10 | proto: 11 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/cart.proto 12 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/health.proto 13 | 14 | 15 | .PHONY: update 16 | update: 17 | @go get -u 18 | 19 | .PHONY: tidy 20 | tidy: 21 | @go mod tidy 22 | 23 | .PHONY: build 24 | build: 25 | @go build -o cart *.go 26 | 27 | .PHONY: test 28 | test: 29 | @go test -v ./... -cover 30 | 31 | .PHONY: docker 32 | docker: 33 | @docker build -t cart:latest . -------------------------------------------------------------------------------- /service/cart/cartstore/interface.go: -------------------------------------------------------------------------------- 1 | package cartstore 2 | 3 | import ( 4 | "context" 5 | 6 | pb "github.com/go-micro/demo/cart/proto" 7 | ) 8 | 9 | type CartStore interface { 10 | AddItem(ctx context.Context, userID, productID string, quantity int32) error 11 | EmptyCart(ctx context.Context, userID string) error 12 | GetCart(ctx context.Context, userID string) (*pb.Cart, error) 13 | } 14 | 15 | func NewMemoryCartStore() CartStore { 16 | return &memoryCartStore{ 17 | carts: make(map[string]map[string]int32), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /service/cart/cartstore/memory.go: -------------------------------------------------------------------------------- 1 | package cartstore 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | pb "github.com/go-micro/demo/cart/proto" 8 | ) 9 | 10 | type memoryCartStore struct { 11 | sync.RWMutex 12 | 13 | carts map[string]map[string]int32 14 | } 15 | 16 | func (s *memoryCartStore) AddItem(ctx context.Context, userID, productID string, quantity int32) error { 17 | s.Lock() 18 | defer s.Unlock() 19 | 20 | if cart, ok := s.carts[userID]; ok { 21 | if currentQuantity, ok := cart[productID]; ok { 22 | cart[productID] = currentQuantity + quantity 23 | } else { 24 | cart[productID] = quantity 25 | } 26 | s.carts[userID] = cart 27 | } else { 28 | s.carts[userID] = map[string]int32{productID: quantity} 29 | } 30 | return nil 31 | } 32 | 33 | func (s *memoryCartStore) EmptyCart(ctx context.Context, userID string) error { 34 | s.Lock() 35 | defer s.Unlock() 36 | 37 | delete(s.carts, userID) 38 | return nil 39 | } 40 | 41 | func (s *memoryCartStore) GetCart(ctx context.Context, userID string) (*pb.Cart, error) { 42 | s.RLock() 43 | defer s.RUnlock() 44 | 45 | if cart, ok := s.carts[userID]; ok { 46 | items := make([]*pb.CartItem, 0, len(cart)) 47 | for p, q := range cart { 48 | items = append(items, &pb.CartItem{ProductId: p, Quantity: q}) 49 | } 50 | return &pb.Cart{UserId: userID, Items: items}, nil 51 | } 52 | return &pb.Cart{UserId: userID}, nil 53 | } 54 | -------------------------------------------------------------------------------- /service/cart/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "go-micro.dev/v5/config" 8 | "go-micro.dev/v5/config/source/env" 9 | ) 10 | 11 | type Config struct { 12 | Port int 13 | Redis RedisConfig 14 | Tracing TracingConfig 15 | } 16 | 17 | type RedisConfig struct { 18 | Addr string 19 | } 20 | 21 | type TracingConfig struct { 22 | Enable bool 23 | Jaeger JaegerConfig 24 | } 25 | 26 | type JaegerConfig struct { 27 | URL string 28 | } 29 | 30 | var cfg *Config = &Config{ 31 | Port: 7070, 32 | } 33 | 34 | func Address() string { 35 | return fmt.Sprintf(":%d", cfg.Port) 36 | } 37 | 38 | func Redis() RedisConfig { 39 | return cfg.Redis 40 | } 41 | 42 | func Tracing() TracingConfig { 43 | return cfg.Tracing 44 | } 45 | 46 | func Load() error { 47 | configor, err := config.NewConfig(config.WithSource(env.NewSource())) 48 | if err != nil { 49 | return errors.Wrap(err, "configor.New") 50 | } 51 | if err := configor.Load(); err != nil { 52 | return errors.Wrap(err, "configor.Load") 53 | } 54 | if err := configor.Scan(cfg); err != nil { 55 | return errors.Wrap(err, "configor.Scan") 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /service/cart/handler/cartservice.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-micro/demo/cart/cartstore" 7 | pb "github.com/go-micro/demo/cart/proto" 8 | ) 9 | 10 | type CartService struct { 11 | Store cartstore.CartStore 12 | } 13 | 14 | func (s *CartService) AddItem(ctx context.Context, in *pb.AddItemRequest, out *pb.Empty) error { 15 | return s.Store.AddItem(ctx, in.UserId, in.Item.ProductId, in.Item.Quantity) 16 | } 17 | 18 | func (s *CartService) GetCart(ctx context.Context, in *pb.GetCartRequest, out *pb.Cart) error { 19 | cart, err := s.Store.GetCart(ctx, in.UserId) 20 | if err != nil { 21 | return err 22 | } 23 | out.UserId = in.UserId 24 | out.Items = cart.Items 25 | return nil 26 | } 27 | 28 | func (s *CartService) EmptyCart(ctx context.Context, in *pb.EmptyCartRequest, out *pb.Empty) error { 29 | return s.Store.EmptyCart(ctx, in.UserId) 30 | } 31 | -------------------------------------------------------------------------------- /service/cart/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | pb "github.com/go-micro/demo/cart/proto" 7 | ) 8 | 9 | type Health struct{} 10 | 11 | func (h *Health) Check(ctx context.Context, req *pb.HealthCheckRequest, rsp *pb.HealthCheckResponse) error { 12 | rsp.Status = pb.HealthCheckResponse_SERVING 13 | return nil 14 | } 15 | 16 | func (h *Health) Watch(ctx context.Context, req *pb.HealthCheckRequest, stream pb.Health_WatchStream) error { 17 | stream.Send(&pb.HealthCheckResponse{ 18 | Status: pb.HealthCheckResponse_SERVING, 19 | }) 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /service/cart/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "strings" 7 | "time" 8 | 9 | grpcc "github.com/go-micro/plugins/v4/client/grpc" 10 | _ "github.com/go-micro/plugins/v4/registry/kubernetes" 11 | grpcs "github.com/go-micro/plugins/v4/server/grpc" 12 | "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry" 13 | "go-micro.dev/v5" 14 | "go-micro.dev/v5/logger" 15 | "go-micro.dev/v5/server" 16 | "go.opentelemetry.io/otel" 17 | "go.opentelemetry.io/otel/propagation" 18 | 19 | "github.com/go-micro/demo/cart/cartstore" 20 | "github.com/go-micro/demo/cart/config" 21 | "github.com/go-micro/demo/cart/handler" 22 | pb "github.com/go-micro/demo/cart/proto" 23 | ) 24 | 25 | var ( 26 | name = "cart" 27 | version = "1.0.0" 28 | ) 29 | 30 | func main() { 31 | // Load conigurations 32 | if err := config.Load(); err != nil { 33 | logger.Fatal(err) 34 | } 35 | 36 | // Create service 37 | srv := micro.NewService( 38 | micro.Server(grpcs.NewServer()), 39 | micro.Client(grpcc.NewClient()), 40 | ) 41 | opts := []micro.Option{ 42 | micro.Name(name), 43 | micro.Version(version), 44 | micro.Address(config.Address()), 45 | } 46 | if cfg := config.Tracing(); cfg.Enable { 47 | tp, err := newTracerProvider(name, srv.Server().Options().Id, cfg.Jaeger.URL) 48 | if err != nil { 49 | logger.Fatal(err) 50 | } 51 | defer func() { 52 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 53 | defer cancel() 54 | if err := tp.Shutdown(ctx); err != nil { 55 | logger.Fatal(err) 56 | } 57 | }() 58 | otel.SetTracerProvider(tp) 59 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 60 | traceOpts := []opentelemetry.Option{ 61 | opentelemetry.WithHandleFilter(func(ctx context.Context, r server.Request) bool { 62 | if e := r.Endpoint(); strings.HasPrefix(e, "Health.") { 63 | return true 64 | } 65 | return false 66 | }), 67 | } 68 | opts = append(opts, micro.WrapHandler(opentelemetry.NewHandlerWrapper(traceOpts...))) 69 | } 70 | srv.Init(opts...) 71 | 72 | // Register handler 73 | if err := pb.RegisterCartServiceHandler(srv.Server(), &handler.CartService{Store: cartstore.NewMemoryCartStore()}); err != nil { 74 | log.Fatal(err) 75 | } 76 | if err := pb.RegisterHealthHandler(srv.Server(), new(handler.Health)); err != nil { 77 | log.Fatal(err) 78 | } 79 | 80 | // Run service 81 | if err := srv.Run(); err != nil { 82 | logger.Fatal(err) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /service/cart/proto/cartservice.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | syntax = "proto3"; 16 | 17 | package hipstershop; 18 | 19 | option go_package = "./proto;hipstershop"; 20 | 21 | // -----------------Cart service----------------- 22 | 23 | service CartService { 24 | rpc AddItem(AddItemRequest) returns (Empty) {} 25 | rpc GetCart(GetCartRequest) returns (Cart) {} 26 | rpc EmptyCart(EmptyCartRequest) returns (Empty) {} 27 | } 28 | 29 | message CartItem { 30 | string product_id = 1; 31 | int32 quantity = 2; 32 | } 33 | 34 | message AddItemRequest { 35 | string user_id = 1; 36 | CartItem item = 2; 37 | } 38 | 39 | message EmptyCartRequest { 40 | string user_id = 1; 41 | } 42 | 43 | message GetCartRequest { 44 | string user_id = 1; 45 | } 46 | 47 | message Cart { 48 | string user_id = 1; 49 | repeated CartItem items = 2; 50 | } 51 | 52 | message Empty {} 53 | -------------------------------------------------------------------------------- /service/cart/proto/health.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hipstershop; 4 | 5 | option go_package = "./proto;hipstershop"; 6 | 7 | service Health { 8 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse) {} 9 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) {} 10 | } 11 | 12 | message HealthCheckRequest { 13 | string service = 1; 14 | } 15 | 16 | message HealthCheckResponse { 17 | enum ServingStatus { 18 | UNKNOWN = 0; 19 | SERVING = 1; 20 | NOT_SERVING = 2; 21 | SERVICE_UNKNOWN = 3; 22 | } 23 | ServingStatus status = 1; 24 | } 25 | -------------------------------------------------------------------------------- /service/cart/tracing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go.opentelemetry.io/otel/exporters/jaeger" 5 | "go.opentelemetry.io/otel/sdk/resource" 6 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 7 | semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 8 | ) 9 | 10 | // newTracerProvider returns an OpenTelemetry TracerProvider configured to use 11 | // the Jaeger exporter that will send spans to the provided url. The returned 12 | // TracerProvider will also use a Resource configured with all the information 13 | // about the application. 14 | func newTracerProvider(serviceName, serviceID, url string) (*tracesdk.TracerProvider, error) { 15 | // Create the Jaeger exporter 16 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 17 | if err != nil { 18 | return nil, err 19 | } 20 | tp := tracesdk.NewTracerProvider( 21 | tracesdk.WithBatcher(exp), 22 | tracesdk.WithResource(resource.NewWithAttributes( 23 | semconv.SchemaURL, 24 | semconv.ServiceNameKey.String(serviceName), 25 | semconv.ServiceVersionKey.String(version), 26 | semconv.ServiceInstanceIDKey.String(serviceID), 27 | )), 28 | ) 29 | return tp, nil 30 | } 31 | -------------------------------------------------------------------------------- /service/checkout/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | Dockerfile 3 | -------------------------------------------------------------------------------- /service/checkout/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # IDE 18 | .vscode 19 | 20 | # don't commit the service binary to vcs 21 | checkout 22 | -------------------------------------------------------------------------------- /service/checkout/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.0-alpine AS builder 2 | 3 | # Set Go env 4 | ENV CGO_ENABLED=0 GOOS=linux 5 | WORKDIR /go/src/hipstershop 6 | 7 | # Install dependencies 8 | RUN apk --update --no-cache add ca-certificates make protoc 9 | 10 | # Download grpc_health_probe 11 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ 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 | # Build Go binary 16 | COPY Makefile go.mod go.sum ./ 17 | RUN go env -w GOPROXY=https://goproxy.io,direct/ 18 | RUN make init && go mod download 19 | COPY . . 20 | RUN make proto tidy 21 | 22 | # Skaffold passes in debug-oriented compiler flags 23 | ARG SKAFFOLD_GO_GCFLAGS 24 | RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/src/hipstershop/checkout . 25 | 26 | # Deployment container 27 | FROM scratch 28 | 29 | # Definition of this variable is used by 'skaffold debug' to identify a golang binary. 30 | # Default behavior - a failure prints a stack trace for the current goroutine. 31 | # See https://golang.org/pkg/runtime/ 32 | ENV GOTRACEBACK=single 33 | 34 | COPY --from=builder /etc/ssl/certs /etc/ssl/certs 35 | COPY --from=builder /bin/grpc_health_probe /bin/ 36 | COPY --from=builder /go/src/hipstershop/checkout /hipstershop/checkout 37 | 38 | ENTRYPOINT ["/hipstershop/checkout"] 39 | -------------------------------------------------------------------------------- /service/checkout/Makefile: -------------------------------------------------------------------------------- 1 | GOPATH:=$(shell go env GOPATH) 2 | 3 | .PHONY: init 4 | init: 5 | @go get -u google.golang.org/protobuf/proto 6 | @go install github.com/golang/protobuf/protoc-gen-go@latest 7 | @go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest 8 | 9 | .PHONY: proto 10 | proto: 11 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/hipstershop.proto 12 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/health.proto 13 | 14 | 15 | .PHONY: update 16 | update: 17 | @go get -u 18 | 19 | .PHONY: tidy 20 | tidy: 21 | @go mod tidy 22 | 23 | .PHONY: build 24 | build: 25 | @go build -o checkout *.go 26 | 27 | .PHONY: test 28 | test: 29 | @go test -v ./... -cover 30 | 31 | .PHONY: docker 32 | docker: 33 | @docker build -t checkout:latest . -------------------------------------------------------------------------------- /service/checkout/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "go-micro.dev/v5/config" 8 | "go-micro.dev/v5/config/source/env" 9 | ) 10 | 11 | type Config struct { 12 | Port int 13 | Tracing TracingConfig 14 | CartService string 15 | CurrencyService string 16 | EmailService string 17 | PaymentService string 18 | ProductCatalogService string 19 | ShippingService string 20 | } 21 | 22 | type TracingConfig struct { 23 | Enable bool 24 | Jaeger JaegerConfig 25 | } 26 | 27 | type JaegerConfig struct { 28 | URL string 29 | } 30 | 31 | var cfg *Config = &Config{ 32 | Port: 5050, 33 | CartService: "cart", 34 | CurrencyService: "currency", 35 | EmailService: "email", 36 | PaymentService: "payment", 37 | ProductCatalogService: "productcatalog", 38 | ShippingService: "shipping", 39 | } 40 | 41 | func Get() Config { 42 | return *cfg 43 | } 44 | 45 | func Address() string { 46 | return fmt.Sprintf(":%d", cfg.Port) 47 | } 48 | 49 | func Tracing() TracingConfig { 50 | return cfg.Tracing 51 | } 52 | 53 | func Load() error { 54 | configor, err := config.NewConfig(config.WithSource(env.NewSource())) 55 | if err != nil { 56 | return errors.Wrap(err, "configor.New") 57 | } 58 | if err := configor.Load(); err != nil { 59 | return errors.Wrap(err, "configor.Load") 60 | } 61 | if err := configor.Scan(cfg); err != nil { 62 | return errors.Wrap(err, "configor.Scan") 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /service/checkout/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | 9 | pb "github.com/go-micro/demo/checkout/proto" 10 | ) 11 | 12 | type Health struct{} 13 | 14 | func (h *Health) Check(ctx context.Context, req *pb.HealthCheckRequest, rsp *pb.HealthCheckResponse) error { 15 | rsp.Status = pb.HealthCheckResponse_SERVING 16 | return nil 17 | } 18 | 19 | func (h *Health) Watch(ctx context.Context, req *pb.HealthCheckRequest, stream pb.Health_WatchStream) error { 20 | return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") 21 | } 22 | -------------------------------------------------------------------------------- /service/checkout/proto/health.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hipstershop; 4 | 5 | option go_package = "./proto;hipstershop"; 6 | 7 | service Health { 8 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse) {} 9 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) {} 10 | } 11 | 12 | message HealthCheckRequest { 13 | string service = 1; 14 | } 15 | 16 | message HealthCheckResponse { 17 | enum ServingStatus { 18 | UNKNOWN = 0; 19 | SERVING = 1; 20 | NOT_SERVING = 2; 21 | SERVICE_UNKNOWN = 3; 22 | } 23 | ServingStatus status = 1; 24 | } 25 | -------------------------------------------------------------------------------- /service/checkout/tracing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go.opentelemetry.io/otel/exporters/jaeger" 5 | "go.opentelemetry.io/otel/sdk/resource" 6 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 7 | semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 8 | ) 9 | 10 | // newTracerProvider returns an OpenTelemetry TracerProvider configured to use 11 | // the Jaeger exporter that will send spans to the provided url. The returned 12 | // TracerProvider will also use a Resource configured with all the information 13 | // about the application. 14 | func newTracerProvider(serviceName, serviceID, url string) (*tracesdk.TracerProvider, error) { 15 | // Create the Jaeger exporter 16 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 17 | if err != nil { 18 | return nil, err 19 | } 20 | tp := tracesdk.NewTracerProvider( 21 | tracesdk.WithBatcher(exp), 22 | tracesdk.WithResource(resource.NewWithAttributes( 23 | semconv.SchemaURL, 24 | semconv.ServiceNameKey.String(serviceName), 25 | semconv.ServiceVersionKey.String(version), 26 | semconv.ServiceInstanceIDKey.String(serviceID), 27 | )), 28 | ) 29 | return tp, nil 30 | } 31 | -------------------------------------------------------------------------------- /service/currency/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | Dockerfile 3 | -------------------------------------------------------------------------------- /service/currency/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with 'go test -c' 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # don't commit the service binary to vcs 18 | currency 19 | -------------------------------------------------------------------------------- /service/currency/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.0-alpine AS builder 2 | 3 | # Set Go env 4 | ENV CGO_ENABLED=0 GOOS=linux 5 | WORKDIR /go/src/hipstershop 6 | 7 | # Install dependencies 8 | RUN apk --update --no-cache add ca-certificates make protoc 9 | 10 | # Download grpc_health_probe 11 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ 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 | # Build Go binary 16 | COPY Makefile go.mod go.sum ./ 17 | RUN go env -w GOPROXY=https://goproxy.io,direct/ 18 | RUN make init && go mod download 19 | COPY . . 20 | RUN make proto tidy 21 | 22 | # Skaffold passes in debug-oriented compiler flags 23 | ARG SKAFFOLD_GO_GCFLAGS 24 | RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/src/hipstershop/currency . 25 | 26 | # Deployment container 27 | FROM scratch 28 | 29 | WORKDIR /hipstershop 30 | 31 | # Definition of this variable is used by 'skaffold debug' to identify a golang binary. 32 | # Default behavior - a failure prints a stack trace for the current goroutine. 33 | # See https://golang.org/pkg/runtime/ 34 | ENV GOTRACEBACK=single 35 | 36 | 37 | COPY ./data /hipstershop/data/ 38 | COPY --from=builder /etc/ssl/certs /etc/ssl/certs 39 | COPY --from=builder /bin/grpc_health_probe /bin/ 40 | COPY --from=builder /go/src/hipstershop/currency /hipstershop/currency 41 | 42 | ENTRYPOINT ["/hipstershop/currency"] 43 | -------------------------------------------------------------------------------- /service/currency/Makefile: -------------------------------------------------------------------------------- 1 | GOPATH:=$(shell go env GOPATH) 2 | 3 | .PHONY: init 4 | init: 5 | @go get -u google.golang.org/protobuf/proto 6 | @go install github.com/golang/protobuf/protoc-gen-go@latest 7 | @go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest 8 | 9 | .PHONY: proto 10 | proto: 11 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/currency.proto 12 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/health.proto 13 | 14 | 15 | .PHONY: update 16 | update: 17 | @go get -u 18 | 19 | .PHONY: tidy 20 | tidy: 21 | @go mod tidy 22 | 23 | .PHONY: build 24 | build: 25 | @go build -o currency *.go 26 | 27 | .PHONY: test 28 | test: 29 | @go test -v ./... -cover 30 | 31 | .PHONY: docker 32 | docker: 33 | @docker build -t currency:latest . -------------------------------------------------------------------------------- /service/currency/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "go-micro.dev/v5/config" 8 | "go-micro.dev/v5/config/source/env" 9 | ) 10 | 11 | type Config struct { 12 | Port int 13 | Tracing TracingConfig 14 | } 15 | 16 | type TracingConfig struct { 17 | Enable bool 18 | Jaeger JaegerConfig 19 | } 20 | 21 | type JaegerConfig struct { 22 | URL string 23 | } 24 | 25 | var cfg *Config = &Config{ 26 | Port: 7000, 27 | } 28 | 29 | func Address() string { 30 | return fmt.Sprintf(":%d", cfg.Port) 31 | } 32 | 33 | func Tracing() TracingConfig { 34 | return cfg.Tracing 35 | } 36 | 37 | func Load() error { 38 | configor, err := config.NewConfig(config.WithSource(env.NewSource())) 39 | if err != nil { 40 | return errors.Wrap(err, "configor.New") 41 | } 42 | if err := configor.Load(); err != nil { 43 | return errors.Wrap(err, "configor.Load") 44 | } 45 | if err := configor.Scan(cfg); err != nil { 46 | return errors.Wrap(err, "configor.Scan") 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /service/currency/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 | } -------------------------------------------------------------------------------- /service/currency/handler/currencyservice.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io/ioutil" 7 | "math" 8 | 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | 12 | pb "github.com/go-micro/demo/currency/proto" 13 | ) 14 | 15 | type CurrencyService struct{} 16 | 17 | func (s *CurrencyService) GetSupportedCurrencies(ctx context.Context, in *pb.Empty, out *pb.GetSupportedCurrenciesResponse) error { 18 | data, err := ioutil.ReadFile("data/currency_conversion.json") 19 | if err != nil { 20 | return status.Errorf(codes.Internal, "failed to load currency data: %+v", err) 21 | } 22 | currencies := make(map[string]float32) 23 | if err := json.Unmarshal(data, ¤cies); err != nil { 24 | return status.Errorf(codes.Internal, "failed to unmarshal currency data: %+v", err) 25 | } 26 | out.CurrencyCodes = make([]string, 0, len(currencies)) 27 | for k := range currencies { 28 | out.CurrencyCodes = append(out.CurrencyCodes, k) 29 | } 30 | return nil 31 | } 32 | 33 | func (s *CurrencyService) Convert(ctx context.Context, in *pb.CurrencyConversionRequest, out *pb.Money) error { 34 | data, err := ioutil.ReadFile("data/currency_conversion.json") 35 | if err != nil { 36 | return status.Errorf(codes.Internal, "failed to load currency data: %+v", err) 37 | } 38 | currencies := make(map[string]float64) 39 | if err := json.Unmarshal(data, ¤cies); err != nil { 40 | return status.Errorf(codes.Internal, "failed to unmarshal currency data: %+v", err) 41 | } 42 | fromCurrency, found := currencies[in.From.CurrencyCode] 43 | if !found { 44 | return status.Errorf(codes.InvalidArgument, "unsupported currency: %s", in.From.CurrencyCode) 45 | } 46 | toCurrency, found := currencies[in.ToCode] 47 | if !found { 48 | return status.Errorf(codes.InvalidArgument, "unsupported currency: %s", in.ToCode) 49 | } 50 | out.CurrencyCode = in.ToCode 51 | total := int64(math.Floor(float64(in.From.Units*10^9+int64(in.From.Nanos)) / fromCurrency * toCurrency)) 52 | out.Units = total / 1e9 53 | out.Nanos = int32(total % 1e9) 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /service/currency/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | 9 | pb "github.com/go-micro/demo/currency/proto" 10 | ) 11 | 12 | type Health struct{} 13 | 14 | func (h *Health) Check(ctx context.Context, req *pb.HealthCheckRequest, rsp *pb.HealthCheckResponse) error { 15 | rsp.Status = pb.HealthCheckResponse_SERVING 16 | return nil 17 | } 18 | 19 | func (h *Health) Watch(ctx context.Context, req *pb.HealthCheckRequest, stream pb.Health_WatchStream) error { 20 | return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") 21 | } 22 | -------------------------------------------------------------------------------- /service/currency/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | grpcc "github.com/go-micro/plugins/v4/client/grpc" 9 | _ "github.com/go-micro/plugins/v4/registry/kubernetes" 10 | grpcs "github.com/go-micro/plugins/v4/server/grpc" 11 | "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry" 12 | "go-micro.dev/v5" 13 | "go-micro.dev/v5/logger" 14 | "go-micro.dev/v5/server" 15 | "go.opentelemetry.io/otel" 16 | "go.opentelemetry.io/otel/propagation" 17 | 18 | "github.com/go-micro/demo/currency/config" 19 | "github.com/go-micro/demo/currency/handler" 20 | pb "github.com/go-micro/demo/currency/proto" 21 | ) 22 | 23 | var ( 24 | name = "currency" 25 | version = "1.0.0" 26 | ) 27 | 28 | func main() { 29 | // Load conigurations 30 | if err := config.Load(); err != nil { 31 | logger.Fatal(err) 32 | } 33 | 34 | // Create service 35 | srv := micro.NewService( 36 | micro.Server(grpcs.NewServer()), 37 | micro.Client(grpcc.NewClient()), 38 | ) 39 | opts := []micro.Option{ 40 | micro.Name(name), 41 | micro.Version(version), 42 | micro.Address(config.Address()), 43 | } 44 | if cfg := config.Tracing(); cfg.Enable { 45 | tp, err := newTracerProvider(name, srv.Server().Options().Id, cfg.Jaeger.URL) 46 | if err != nil { 47 | logger.Fatal(err) 48 | } 49 | defer func() { 50 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 51 | defer cancel() 52 | if err := tp.Shutdown(ctx); err != nil { 53 | logger.Fatal(err) 54 | } 55 | }() 56 | otel.SetTracerProvider(tp) 57 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 58 | traceOpts := []opentelemetry.Option{ 59 | opentelemetry.WithHandleFilter(func(ctx context.Context, r server.Request) bool { 60 | if e := r.Endpoint(); strings.HasPrefix(e, "Health.") { 61 | return true 62 | } 63 | return false 64 | }), 65 | } 66 | opts = append(opts, micro.WrapHandler(opentelemetry.NewHandlerWrapper(traceOpts...))) 67 | } 68 | srv.Init(opts...) 69 | 70 | // Register handler 71 | if err := pb.RegisterCurrencyServiceHandler(srv.Server(), new(handler.CurrencyService)); err != nil { 72 | logger.Fatal(err) 73 | } 74 | if err := pb.RegisterHealthHandler(srv.Server(), new(handler.Health)); err != nil { 75 | logger.Fatal(err) 76 | } 77 | 78 | // Run service 79 | if err := srv.Run(); err != nil { 80 | logger.Fatal(err) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /service/currency/proto/currencyservice.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | syntax = "proto3"; 16 | 17 | package hipstershop; 18 | 19 | option go_package = "./proto;hipstershop"; 20 | 21 | // -----------------Currency service----------------- 22 | 23 | service CurrencyService { 24 | rpc GetSupportedCurrencies(Empty) returns (GetSupportedCurrenciesResponse) {} 25 | rpc Convert(CurrencyConversionRequest) returns (Money) {} 26 | } 27 | 28 | message Empty {} 29 | 30 | // Represents an amount of money with its currency type. 31 | message Money { 32 | // The 3-letter currency code defined in ISO 4217. 33 | string currency_code = 1; 34 | 35 | // The whole units of the amount. 36 | // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. 37 | int64 units = 2; 38 | 39 | // Number of nano (10^-9) units of the amount. 40 | // The value must be between -999,999,999 and +999,999,999 inclusive. 41 | // If `units` is positive, `nanos` must be positive or zero. 42 | // If `units` is zero, `nanos` can be positive, zero, or negative. 43 | // If `units` is negative, `nanos` must be negative or zero. 44 | // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. 45 | int32 nanos = 3; 46 | } 47 | 48 | message GetSupportedCurrenciesResponse { 49 | // The 3-letter currency code defined in ISO 4217. 50 | repeated string currency_codes = 1; 51 | } 52 | 53 | message CurrencyConversionRequest { 54 | Money from = 1; 55 | 56 | // The 3-letter currency code defined in ISO 4217. 57 | string to_code = 2; 58 | } -------------------------------------------------------------------------------- /service/currency/proto/health.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hipstershop; 4 | 5 | option go_package = "./proto;hipstershop"; 6 | 7 | service Health { 8 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse) {} 9 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) {} 10 | } 11 | 12 | message HealthCheckRequest { string service = 1; } 13 | 14 | message HealthCheckResponse { 15 | enum ServingStatus { 16 | UNKNOWN = 0; 17 | SERVING = 1; 18 | NOT_SERVING = 2; 19 | SERVICE_UNKNOWN = 3; 20 | } 21 | ServingStatus status = 1; 22 | } 23 | -------------------------------------------------------------------------------- /service/currency/tracing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go.opentelemetry.io/otel/exporters/jaeger" 5 | "go.opentelemetry.io/otel/sdk/resource" 6 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 7 | semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 8 | ) 9 | 10 | // newTracerProvider returns an OpenTelemetry TracerProvider configured to use 11 | // the Jaeger exporter that will send spans to the provided url. The returned 12 | // TracerProvider will also use a Resource configured with all the information 13 | // about the application. 14 | func newTracerProvider(serviceName, serviceID, url string) (*tracesdk.TracerProvider, error) { 15 | // Create the Jaeger exporter 16 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 17 | if err != nil { 18 | return nil, err 19 | } 20 | tp := tracesdk.NewTracerProvider( 21 | tracesdk.WithBatcher(exp), 22 | tracesdk.WithResource(resource.NewWithAttributes( 23 | semconv.SchemaURL, 24 | semconv.ServiceNameKey.String(serviceName), 25 | semconv.ServiceVersionKey.String(version), 26 | semconv.ServiceInstanceIDKey.String(serviceID), 27 | )), 28 | ) 29 | return tp, nil 30 | } 31 | -------------------------------------------------------------------------------- /service/email/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | Dockerfile 3 | -------------------------------------------------------------------------------- /service/email/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with 'go test -c' 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # don't commit the service binary to vcs 18 | email 19 | -------------------------------------------------------------------------------- /service/email/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.0-alpine AS builder 2 | 3 | # Set Go env 4 | ENV CGO_ENABLED=0 GOOS=linux 5 | WORKDIR /go/src/hipstershop 6 | 7 | # Install dependencies 8 | RUN apk --update --no-cache add ca-certificates make protoc 9 | 10 | # Download grpc_health_probe 11 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ 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 | # Build Go binary 16 | COPY Makefile go.mod go.sum ./ 17 | RUN go env -w GOPROXY=https://goproxy.io,direct/ 18 | RUN make init && go mod download 19 | COPY . . 20 | RUN make proto tidy 21 | 22 | # Skaffold passes in debug-oriented compiler flags 23 | ARG SKAFFOLD_GO_GCFLAGS 24 | RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/src/hipstershop/email . 25 | 26 | # Deployment container 27 | FROM scratch 28 | 29 | # Definition of this variable is used by 'skaffold debug' to identify a golang binary. 30 | # Default behavior - a failure prints a stack trace for the current goroutine. 31 | # See https://golang.org/pkg/runtime/ 32 | ENV GOTRACEBACK=single 33 | 34 | # Definition of this variable is used by 'skaffold debug' to identify a golang binary. 35 | # Default behavior - a failure prints a stack trace for the current goroutine. 36 | # See https://golang.org/pkg/runtime/ 37 | ENV GOTRACEBACK=single 38 | 39 | COPY --from=builder /etc/ssl/certs /etc/ssl/certs 40 | COPY --from=builder /bin/grpc_health_probe /bin/ 41 | COPY --from=builder /go/src/hipstershop/email /hipstershop/email 42 | 43 | ENTRYPOINT ["/hipstershop/email"] 44 | -------------------------------------------------------------------------------- /service/email/Makefile: -------------------------------------------------------------------------------- 1 | GOPATH:=$(shell go env GOPATH) 2 | 3 | .PHONY: init 4 | init: 5 | @go get -u google.golang.org/protobuf/proto 6 | @go install github.com/golang/protobuf/protoc-gen-go@latest 7 | @go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest 8 | 9 | .PHONY: proto 10 | proto: 11 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/email.proto 12 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/health.proto 13 | 14 | 15 | .PHONY: update 16 | update: 17 | @go get -u 18 | 19 | .PHONY: tidy 20 | tidy: 21 | @go mod tidy 22 | 23 | .PHONY: build 24 | build: 25 | @go build -o email *.go 26 | 27 | .PHONY: test 28 | test: 29 | @go test -v ./... -cover 30 | 31 | .PHONY: docker 32 | docker: 33 | @docker build -t email:latest . -------------------------------------------------------------------------------- /service/email/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "go-micro.dev/v5/config" 8 | "go-micro.dev/v5/config/source/env" 9 | ) 10 | 11 | type Config struct { 12 | Port int 13 | Tracing TracingConfig 14 | } 15 | 16 | type TracingConfig struct { 17 | Enable bool 18 | Jaeger JaegerConfig 19 | } 20 | 21 | type JaegerConfig struct { 22 | URL string 23 | } 24 | 25 | var cfg *Config = &Config{ 26 | Port: 5000, 27 | } 28 | 29 | func Address() string { 30 | return fmt.Sprintf(":%d", cfg.Port) 31 | } 32 | 33 | func Tracing() TracingConfig { 34 | return cfg.Tracing 35 | } 36 | 37 | func Load() error { 38 | configor, err := config.NewConfig(config.WithSource(env.NewSource())) 39 | if err != nil { 40 | return errors.Wrap(err, "configor.New") 41 | } 42 | if err := configor.Load(); err != nil { 43 | return errors.Wrap(err, "configor.Load") 44 | } 45 | if err := configor.Scan(cfg); err != nil { 46 | return errors.Wrap(err, "configor.Scan") 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /service/email/handler/emailservice.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "go-micro.dev/v5/logger" 7 | 8 | pb "github.com/go-micro/demo/email/proto" 9 | ) 10 | 11 | type DummyEmailService struct{} 12 | 13 | func (s *DummyEmailService) SendOrderConfirmation(ctx context.Context, in *pb.SendOrderConfirmationRequest, out *pb.Empty) error { 14 | logger.Infof("A request to send order confirmation email to %s has been received.", in.Email) 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /service/email/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | 9 | pb "github.com/go-micro/demo/email/proto" 10 | ) 11 | 12 | type Health struct{} 13 | 14 | func (h *Health) Check(ctx context.Context, req *pb.HealthCheckRequest, rsp *pb.HealthCheckResponse) error { 15 | rsp.Status = pb.HealthCheckResponse_SERVING 16 | return nil 17 | } 18 | 19 | func (h *Health) Watch(ctx context.Context, req *pb.HealthCheckRequest, stream pb.Health_WatchStream) error { 20 | return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") 21 | } 22 | -------------------------------------------------------------------------------- /service/email/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | grpcc "github.com/go-micro/plugins/v4/client/grpc" 9 | _ "github.com/go-micro/plugins/v4/registry/kubernetes" 10 | grpcs "github.com/go-micro/plugins/v4/server/grpc" 11 | "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry" 12 | "go-micro.dev/v5" 13 | "go-micro.dev/v5/logger" 14 | "go-micro.dev/v5/server" 15 | "go.opentelemetry.io/otel" 16 | "go.opentelemetry.io/otel/propagation" 17 | 18 | "github.com/go-micro/demo/email/config" 19 | "github.com/go-micro/demo/email/handler" 20 | pb "github.com/go-micro/demo/email/proto" 21 | ) 22 | 23 | var ( 24 | name = "email" 25 | version = "1.0.0" 26 | ) 27 | 28 | func main() { 29 | // Load conigurations 30 | if err := config.Load(); err != nil { 31 | logger.Fatal(err) 32 | } 33 | 34 | // Create service 35 | srv := micro.NewService( 36 | micro.Server(grpcs.NewServer()), 37 | micro.Client(grpcc.NewClient()), 38 | ) 39 | opts := []micro.Option{ 40 | micro.Name(name), 41 | micro.Version(version), 42 | micro.Address(config.Address()), 43 | } 44 | if cfg := config.Tracing(); cfg.Enable { 45 | tp, err := newTracerProvider(name, srv.Server().Options().Id, cfg.Jaeger.URL) 46 | if err != nil { 47 | logger.Fatal(err) 48 | } 49 | defer func() { 50 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 51 | defer cancel() 52 | if err := tp.Shutdown(ctx); err != nil { 53 | logger.Fatal(err) 54 | } 55 | }() 56 | otel.SetTracerProvider(tp) 57 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 58 | traceOpts := []opentelemetry.Option{ 59 | opentelemetry.WithHandleFilter(func(ctx context.Context, r server.Request) bool { 60 | if e := r.Endpoint(); strings.HasPrefix(e, "Health.") { 61 | return true 62 | } 63 | return false 64 | }), 65 | } 66 | opts = append(opts, micro.WrapHandler(opentelemetry.NewHandlerWrapper(traceOpts...))) 67 | } 68 | srv.Init(opts...) 69 | 70 | // Register handler 71 | if err := pb.RegisterEmailServiceHandler(srv.Server(), new(handler.DummyEmailService)); err != nil { 72 | logger.Fatal(err) 73 | } 74 | if err := pb.RegisterHealthHandler(srv.Server(), new(handler.Health)); err != nil { 75 | logger.Fatal(err) 76 | } 77 | 78 | // Run service 79 | if err := srv.Run(); err != nil { 80 | logger.Fatal(err) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /service/email/proto/emailservice.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: proto/email.proto 3 | 4 | package hipstershop 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "google.golang.org/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | api "go-micro.dev/v5/api" 15 | client "go-micro.dev/v5/client" 16 | server "go-micro.dev/v5/server" 17 | ) 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var _ = proto.Marshal 21 | var _ = fmt.Errorf 22 | var _ = math.Inf 23 | 24 | // Reference imports to suppress errors if they are not otherwise used. 25 | var _ api.Endpoint 26 | var _ context.Context 27 | var _ client.Option 28 | var _ server.Option 29 | 30 | // Api Endpoints for EmailService service 31 | 32 | func NewEmailServiceEndpoints() []*api.Endpoint { 33 | return []*api.Endpoint{} 34 | } 35 | 36 | // Client API for EmailService service 37 | 38 | type EmailService interface { 39 | SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...client.CallOption) (*Empty, error) 40 | } 41 | 42 | type emailService struct { 43 | c client.Client 44 | name string 45 | } 46 | 47 | func NewEmailService(name string, c client.Client) EmailService { 48 | return &emailService{ 49 | c: c, 50 | name: name, 51 | } 52 | } 53 | 54 | func (c *emailService) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, opts ...client.CallOption) (*Empty, error) { 55 | req := c.c.NewRequest(c.name, "EmailService.SendOrderConfirmation", in) 56 | out := new(Empty) 57 | err := c.c.Call(ctx, req, out, opts...) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return out, nil 62 | } 63 | 64 | // Server API for EmailService service 65 | 66 | type EmailServiceHandler interface { 67 | SendOrderConfirmation(context.Context, *SendOrderConfirmationRequest, *Empty) error 68 | } 69 | 70 | func RegisterEmailServiceHandler(s server.Server, hdlr EmailServiceHandler, opts ...server.HandlerOption) error { 71 | type emailService interface { 72 | SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, out *Empty) error 73 | } 74 | type EmailService struct { 75 | emailService 76 | } 77 | h := &emailServiceHandler{hdlr} 78 | return s.Handle(s.NewHandler(&EmailService{h}, opts...)) 79 | } 80 | 81 | type emailServiceHandler struct { 82 | EmailServiceHandler 83 | } 84 | 85 | func (h *emailServiceHandler) SendOrderConfirmation(ctx context.Context, in *SendOrderConfirmationRequest, out *Empty) error { 86 | return h.EmailServiceHandler.SendOrderConfirmation(ctx, in, out) 87 | } 88 | -------------------------------------------------------------------------------- /service/email/proto/emailservice.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | syntax = "proto3"; 16 | 17 | package hipstershop; 18 | 19 | option go_package = "./proto;hipstershop"; 20 | 21 | message CartItem { 22 | string product_id = 1; 23 | int32 quantity = 2; 24 | } 25 | 26 | message Empty {} 27 | 28 | message Address { 29 | string street_address = 1; 30 | string city = 2; 31 | string state = 3; 32 | string country = 4; 33 | int32 zip_code = 5; 34 | } 35 | 36 | // Represents an amount of money with its currency type. 37 | message Money { 38 | // The 3-letter currency code defined in ISO 4217. 39 | string currency_code = 1; 40 | 41 | // The whole units of the amount. 42 | // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. 43 | int64 units = 2; 44 | 45 | // Number of nano (10^-9) units of the amount. 46 | // The value must be between -999,999,999 and +999,999,999 inclusive. 47 | // If `units` is positive, `nanos` must be positive or zero. 48 | // If `units` is zero, `nanos` can be positive, zero, or negative. 49 | // If `units` is negative, `nanos` must be negative or zero. 50 | // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. 51 | int32 nanos = 3; 52 | } 53 | 54 | // -------------Email service----------------- 55 | 56 | service EmailService { 57 | rpc SendOrderConfirmation(SendOrderConfirmationRequest) returns (Empty) {} 58 | } 59 | 60 | message OrderItem { 61 | CartItem item = 1; 62 | Money cost = 2; 63 | } 64 | 65 | message OrderResult { 66 | string order_id = 1; 67 | string shipping_tracking_id = 2; 68 | Money shipping_cost = 3; 69 | Address shipping_address = 4; 70 | repeated OrderItem items = 5; 71 | } 72 | 73 | message SendOrderConfirmationRequest { 74 | string email = 1; 75 | OrderResult order = 2; 76 | } -------------------------------------------------------------------------------- /service/email/proto/health.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hipstershop; 4 | 5 | option go_package = "./proto;hipstershop"; 6 | 7 | service Health { 8 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse) {} 9 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) {} 10 | } 11 | 12 | message HealthCheckRequest { 13 | string service = 1; 14 | } 15 | 16 | message HealthCheckResponse { 17 | enum ServingStatus { 18 | UNKNOWN = 0; 19 | SERVING = 1; 20 | NOT_SERVING = 2; 21 | SERVICE_UNKNOWN = 3; 22 | } 23 | ServingStatus status = 1; 24 | } 25 | -------------------------------------------------------------------------------- /service/email/template/confirmation.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | Your Order Confirmation 21 | 22 | 23 | 28 | 29 |

Your Order Confirmation

30 |

Thanks for shopping with us!

31 |

Order ID

32 |

#{{ order.order_id }}

33 |

Shipping

34 |

#{{ order.shipping_tracking_id }}

35 |

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

36 |

{{ 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}}

37 |

Items

38 | 39 | 40 | 41 | 42 | 43 | 44 | {% for item in order.items %} 45 | 46 | 47 | 48 | 49 | 50 | {% endfor %} 51 |
Item No.QuantityPrice
#{{ item.item.product_id }}{{ item.item.quantity }}{{ item.cost.units }}.{{ "%02d" | format(item.cost.nanos // 10000000) }} {{ item.cost.currency_code }}
52 | 53 | 54 | -------------------------------------------------------------------------------- /service/email/tracing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go.opentelemetry.io/otel/exporters/jaeger" 5 | "go.opentelemetry.io/otel/sdk/resource" 6 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 7 | semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 8 | ) 9 | 10 | // newTracerProvider returns an OpenTelemetry TracerProvider configured to use 11 | // the Jaeger exporter that will send spans to the provided url. The returned 12 | // TracerProvider will also use a Resource configured with all the information 13 | // about the application. 14 | func newTracerProvider(serviceName, serviceID, url string) (*tracesdk.TracerProvider, error) { 15 | // Create the Jaeger exporter 16 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 17 | if err != nil { 18 | return nil, err 19 | } 20 | tp := tracesdk.NewTracerProvider( 21 | tracesdk.WithBatcher(exp), 22 | tracesdk.WithResource(resource.NewWithAttributes( 23 | semconv.SchemaURL, 24 | semconv.ServiceNameKey.String(serviceName), 25 | semconv.ServiceVersionKey.String(version), 26 | semconv.ServiceInstanceIDKey.String(serviceID), 27 | )), 28 | ) 29 | return tp, nil 30 | } 31 | -------------------------------------------------------------------------------- /service/frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /service/frontend/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/.gitkeep -------------------------------------------------------------------------------- /service/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.0-alpine AS builder 2 | 3 | # Set Go env 4 | ENV CGO_ENABLED=0 GOOS=linux 5 | WORKDIR /go/src/hipstershop 6 | 7 | # Install dependencies 8 | RUN apk --update --no-cache add ca-certificates make protoc 9 | 10 | # Build Go binary 11 | COPY Makefile go.mod go.sum ./ 12 | RUN go env -w GOPROXY=https://goproxy.io,direct/ 13 | RUN make init && go mod download 14 | COPY . . 15 | RUN make proto tidy 16 | 17 | # Skaffold passes in debug-oriented compiler flags 18 | ARG SKAFFOLD_GO_GCFLAGS 19 | RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/src/hipstershop/frontend . 20 | 21 | # Deployment container 22 | FROM scratch 23 | 24 | COPY --from=builder /go/src/hipstershop/frontend /hipstershop/frontend 25 | COPY ./templates ./templates 26 | COPY ./static ./static 27 | 28 | # Definition of this variable is used by 'skaffold debug' to identify a golang binary. 29 | # Default behavior - a failure prints a stack trace for the current goroutine. 30 | # See https://golang.org/pkg/runtime/ 31 | ENV GOTRACEBACK=single 32 | 33 | ENTRYPOINT ["/hipstershop/frontend"] -------------------------------------------------------------------------------- /service/frontend/Makefile: -------------------------------------------------------------------------------- 1 | GOPATH:=$(shell go env GOPATH) 2 | 3 | .PHONY: init 4 | init: 5 | @go get -u google.golang.org/protobuf/proto 6 | @go install github.com/golang/protobuf/protoc-gen-go@latest 7 | @go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest 8 | 9 | .PHONY: proto 10 | proto: 11 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/hipstershop.proto 12 | 13 | .PHONY: update 14 | update: 15 | @go get -u 16 | 17 | .PHONY: tidy 18 | tidy: 19 | @go mod tidy 20 | 21 | .PHONY: build 22 | build: 23 | @go build -o frontend *.go 24 | 25 | .PHONY: test 26 | test: 27 | @go test -v ./... -cover 28 | 29 | .PHONY: docker 30 | docker: 31 | @docker build -t frontend:latest . -------------------------------------------------------------------------------- /service/frontend/README.md: -------------------------------------------------------------------------------- 1 | # frontend 2 | 3 | Run the following command to restore dependencies to `vendor/` directory: 4 | 5 | dep ensure --vendor-only 6 | -------------------------------------------------------------------------------- /service/frontend/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "go-micro.dev/v5/config" 6 | "go-micro.dev/v5/config/source/env" 7 | ) 8 | 9 | type Config struct { 10 | Address string 11 | Tracing TracingConfig 12 | AdService string 13 | CartService string 14 | CheckoutService string 15 | CurrencyService string 16 | ProductCatalogService string 17 | RecommendationService string 18 | ShippingService string 19 | } 20 | 21 | type TracingConfig struct { 22 | Enable bool 23 | Jaeger JaegerConfig 24 | } 25 | 26 | type JaegerConfig struct { 27 | URL string 28 | } 29 | 30 | var cfg *Config = &Config{ 31 | Address: ":8090", 32 | AdService: "ad", 33 | CartService: "cart", 34 | CheckoutService: "checkout", 35 | CurrencyService: "currency", 36 | ProductCatalogService: "productcatalog", 37 | RecommendationService: "recommendation", 38 | ShippingService: "shipping", 39 | } 40 | 41 | func Get() Config { 42 | return *cfg 43 | } 44 | 45 | func Address() string { 46 | return cfg.Address 47 | } 48 | 49 | func Tracing() TracingConfig { 50 | return cfg.Tracing 51 | } 52 | 53 | func Load() error { 54 | configor, err := config.NewConfig(config.WithSource(env.NewSource())) 55 | if err != nil { 56 | return errors.Wrap(err, "configor.New") 57 | } 58 | if err := configor.Load(); err != nil { 59 | return errors.Wrap(err, "configor.Load") 60 | } 61 | if err := configor.Scan(cfg); err != nil { 62 | return errors.Wrap(err, "configor.Scan") 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /service/frontend/deployment_details.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "time" 7 | 8 | "cloud.google.com/go/compute/metadata" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | var deploymentDetailsMap map[string]string 13 | var log *logrus.Logger 14 | 15 | func init() { 16 | initializeLogger() 17 | // Use a goroutine to ensure loadDeploymentDetails()'s GCP API 18 | // calls don't block non-GCP deployments. See issue #685. 19 | go loadDeploymentDetails() 20 | } 21 | 22 | func initializeLogger() { 23 | log = logrus.New() 24 | log.Level = logrus.DebugLevel 25 | log.Formatter = &logrus.JSONFormatter{ 26 | FieldMap: logrus.FieldMap{ 27 | logrus.FieldKeyTime: "timestamp", 28 | logrus.FieldKeyLevel: "severity", 29 | logrus.FieldKeyMsg: "message", 30 | }, 31 | TimestampFormat: time.RFC3339Nano, 32 | } 33 | log.Out = os.Stdout 34 | } 35 | 36 | func loadDeploymentDetails() { 37 | deploymentDetailsMap = make(map[string]string) 38 | var metaServerClient = metadata.NewClient(&http.Client{}) 39 | 40 | podHostname, err := os.Hostname() 41 | if err != nil { 42 | log.Error("Failed to fetch the hostname for the Pod", err) 43 | } 44 | 45 | podCluster, err := metaServerClient.InstanceAttributeValue("cluster-name") 46 | if err != nil { 47 | log.Error("Failed to fetch the name of the cluster in which the pod is running", err) 48 | } 49 | 50 | podZone, err := metaServerClient.Zone() 51 | if err != nil { 52 | log.Error("Failed to fetch the Zone of the node where the pod is scheduled", err) 53 | } 54 | 55 | deploymentDetailsMap["HOSTNAME"] = podHostname 56 | deploymentDetailsMap["CLUSTERNAME"] = podCluster 57 | deploymentDetailsMap["ZONE"] = podZone 58 | 59 | log.WithFields(logrus.Fields{ 60 | "cluster": podCluster, 61 | "zone": podZone, 62 | "hostname": podHostname, 63 | }).Debug("Loaded deployment details") 64 | } 65 | -------------------------------------------------------------------------------- /service/frontend/static/favicon-cymbal.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/favicon-cymbal.ico -------------------------------------------------------------------------------- /service/frontend/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/favicon.ico -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_CheckOutIcon.svg: -------------------------------------------------------------------------------- 1 | Hipster -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_CurrencyIcon.svg: -------------------------------------------------------------------------------- 1 | Hipster -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_DownArrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 47 | 49 | 51 | 52 | Hipster 54 | 57 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_FacebookIcon.svg: -------------------------------------------------------------------------------- 1 | Hipster -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_HelpIcon.svg: -------------------------------------------------------------------------------- 1 | Hipster -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_InstagramIcon.svg: -------------------------------------------------------------------------------- 1 | Hipster -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_PinterestIcon.svg: -------------------------------------------------------------------------------- 1 | Hipster -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_ProfileIcon.svg: -------------------------------------------------------------------------------- 1 | Hipster -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_SearchIcon.svg: -------------------------------------------------------------------------------- 1 | Hipster -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_TwitterIcon.svg: -------------------------------------------------------------------------------- 1 | Hipster -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_UpDownControl.svg: -------------------------------------------------------------------------------- 1 | 2 | Hipster 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /service/frontend/static/icons/Hipster_YoutubeIcon.svg: -------------------------------------------------------------------------------- 1 | Hipster -------------------------------------------------------------------------------- /service/frontend/static/images/Advert2BannerImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/images/Advert2BannerImage.png -------------------------------------------------------------------------------- /service/frontend/static/images/AdvertBannerImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/images/AdvertBannerImage.png -------------------------------------------------------------------------------- /service/frontend/static/images/HeroBannerImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/images/HeroBannerImage.png -------------------------------------------------------------------------------- /service/frontend/static/images/HeroBannerImage2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/images/HeroBannerImage2.png -------------------------------------------------------------------------------- /service/frontend/static/images/VRHeadsets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/images/VRHeadsets.png -------------------------------------------------------------------------------- /service/frontend/static/images/credits.txt: -------------------------------------------------------------------------------- 1 | folded-clothes-on-white-chair.jpg,,https://unsplash.com/photos/fr0J5-GIVyg 2 | folded-clothes-on-white-chair-wide.jpg,,https://unsplash.com/photos/fr0J5-GIVyg 3 | -------------------------------------------------------------------------------- /service/frontend/static/images/folded-clothes-on-white-chair-wide.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/images/folded-clothes-on-white-chair-wide.jpg -------------------------------------------------------------------------------- /service/frontend/static/images/folded-clothes-on-white-chair.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/images/folded-clothes-on-white-chair.jpg -------------------------------------------------------------------------------- /service/frontend/static/img/products/bamboo-glass-jar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/img/products/bamboo-glass-jar.jpg -------------------------------------------------------------------------------- /service/frontend/static/img/products/candle-holder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/img/products/candle-holder.jpg -------------------------------------------------------------------------------- /service/frontend/static/img/products/hairdryer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/img/products/hairdryer.jpg -------------------------------------------------------------------------------- /service/frontend/static/img/products/loafers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/img/products/loafers.jpg -------------------------------------------------------------------------------- /service/frontend/static/img/products/mug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/img/products/mug.jpg -------------------------------------------------------------------------------- /service/frontend/static/img/products/salt-and-pepper-shakers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/img/products/salt-and-pepper-shakers.jpg -------------------------------------------------------------------------------- /service/frontend/static/img/products/sunglasses.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/img/products/sunglasses.jpg -------------------------------------------------------------------------------- /service/frontend/static/img/products/tank-top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/img/products/tank-top.jpg -------------------------------------------------------------------------------- /service/frontend/static/img/products/watch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-micro/demo/b09ae408195d72a7a6985d633a0b025a1b8e35bd/service/frontend/static/img/products/watch.jpg -------------------------------------------------------------------------------- /service/frontend/static/styles/order.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2020 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 | .order { 18 | background: #F9F9F9; 19 | } 20 | 21 | .order-complete-section { 22 | max-width: 487px; 23 | padding-top: 56px; 24 | padding-bottom: 120px; 25 | } 26 | 27 | .order-complete-section h3 { 28 | margin: 0; 29 | font-size: 36px; 30 | font-weight: normal; 31 | } 32 | 33 | .order-complete-section p { 34 | margin-top: 8px; 35 | } 36 | 37 | .order-complete-section .padding-y-24 { 38 | padding-bottom: 24px; 39 | padding-top: 24px; 40 | } 41 | 42 | .order-complete-section .border-bottom-solid { 43 | border-bottom: 1px solid rgba(154, 160, 166, 0.5); 44 | } 45 | 46 | .order-complete-section .cymbal-button-primary { 47 | margin-top: 24px; 48 | } 49 | 50 | .order-complete-section a.cymbal-button-primary:hover { 51 | text-decoration: none; 52 | color: white; 53 | } 54 | -------------------------------------------------------------------------------- /service/frontend/templates/ad.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | {{ define "text_ad" }} 18 |
19 |
20 | Ad 21 | 22 | {{.Text}} 23 | 24 |
25 |
26 | {{ end }} -------------------------------------------------------------------------------- /service/frontend/templates/error.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | {{ define "error" }} 18 | {{ template "header" . }} 19 |
20 | 21 | {{$.platform_name}} 22 | 23 |
24 |
25 |
26 |
27 |

Uh, oh!

28 |

Something has failed. Below are some details for debugging.

29 | 30 |

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

31 |
33 |                     {{- .error -}}
34 |                 
35 |
36 |
37 |
38 | 39 | {{ template "footer" . }} 40 | {{ end }} -------------------------------------------------------------------------------- /service/frontend/templates/footer.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | {{ define "footer" }} 18 | 19 |
20 | 49 |
50 | 53 | 54 | 55 | 56 | {{ end }} 57 | -------------------------------------------------------------------------------- /service/frontend/templates/home.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | {{ define "home" }} 18 | 19 | {{ template "header" . }} 20 |
21 | 22 | {{$.platform_name}} 23 | 24 |
25 |
26 | 27 | 28 |
29 | 30 |
31 |
32 | 33 | 34 |
35 | 36 |
37 | 38 |
39 | 40 |
41 |

Hot Products

42 |
43 | 44 | {{ range $.products }} 45 |
46 | 47 | 48 |
49 |
50 |
51 |
{{ .Item.Name }}
52 |
{{ renderMoney .Price }}
53 |
54 |
55 | {{ end }} 56 | 57 |
58 | 59 | 60 | 65 | 66 |
67 | 68 |
69 |
70 | 71 |
72 | 73 | 74 |
75 | {{ template "footer" . }} 76 |
77 | 78 | {{ end }} -------------------------------------------------------------------------------- /service/frontend/templates/product.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | {{ define "product" }} 18 | {{ template "header" . }} 19 |
20 | 21 | {{$.platform_name}} 22 | 23 |
24 | 25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 | 34 |

{{ $.product.Item.Name }}

35 |

{{ renderMoney $.product.Price }}

36 |

{{ $.product.Item.Description }}

37 | 38 |
39 | 40 |
41 | 49 | 50 |
51 | 52 |
53 |
54 |
55 |
56 |
57 |
58 | {{ if $.recommendations}} 59 | {{ template "recommendations" $.recommendations }} 60 | {{ end }} 61 |
62 |
63 | {{ with $.ad }}{{ template "text_ad" . }}{{ end }} 64 |
65 |
66 | {{ template "footer" . }} 67 | {{ end }} -------------------------------------------------------------------------------- /service/frontend/templates/recommendations.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | {{ define "recommendations" }} 18 |
19 |
20 |
21 |
22 |

You May Also Like

23 |
24 | {{range . }} 25 |
26 |
27 | 28 | 29 | 30 |
31 |
32 | {{ .Name }} 33 |
34 |
35 |
36 |
37 | {{ end }} 38 |
39 |
40 |
41 |
42 |
43 | {{ end }} -------------------------------------------------------------------------------- /service/frontend/tracing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go-micro.dev/v5/metadata" 7 | "go.opentelemetry.io/otel" 8 | "go.opentelemetry.io/otel/exporters/jaeger" 9 | "go.opentelemetry.io/otel/propagation" 10 | "go.opentelemetry.io/otel/sdk/resource" 11 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 12 | semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 13 | ) 14 | 15 | // newTracerProvider returns an OpenTelemetry TracerProvider configured to use 16 | // the Jaeger exporter that will send spans to the provided url. The returned 17 | // TracerProvider will also use a Resource configured with all the information 18 | // about the application. 19 | func newTracerProvider(serviceName, serviceID, url string) (*tracesdk.TracerProvider, error) { 20 | // Create the Jaeger exporter 21 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 22 | if err != nil { 23 | return nil, err 24 | } 25 | tp := tracesdk.NewTracerProvider( 26 | tracesdk.WithBatcher(exp), 27 | tracesdk.WithResource(resource.NewWithAttributes( 28 | semconv.SchemaURL, 29 | semconv.ServiceNameKey.String(serviceName), 30 | semconv.ServiceVersionKey.String(version), 31 | semconv.ServiceInstanceIDKey.String(serviceID), 32 | )), 33 | tracesdk.WithSampler(tracesdk.AlwaysSample()), 34 | ) 35 | otel.SetTracerProvider(tp) 36 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 37 | return tp, nil 38 | } 39 | 40 | func tracingContextWrapper(next http.Handler) http.Handler { 41 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 42 | propagator, md := otel.GetTextMapPropagator(), make(metadata.Metadata) 43 | propagator.Inject(r.Context(), propagation.MapCarrier(md)) 44 | r2 := r.WithContext(metadata.NewContext(r.Context(), md)) 45 | next.ServeHTTP(w, r2) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /service/loadgenerator/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2020 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 | FROM python:3.8-slim as base 16 | 17 | FROM base as builder 18 | 19 | RUN apt-get -qq update \ 20 | && apt-get install -y --no-install-recommends \ 21 | g++ 22 | 23 | COPY requirements.txt . 24 | 25 | RUN pip install --prefix="/install" -r requirements.txt 26 | 27 | FROM base 28 | 29 | WORKDIR /loadgen 30 | 31 | COPY --from=builder /install /usr/local 32 | 33 | # Add application code. 34 | COPY locustfile.py . 35 | 36 | # enable gevent support in debugger 37 | ENV GEVENT_SUPPORT=True 38 | 39 | ENTRYPOINT locust --host="http://${FRONTEND_ADDR}" --headless -u "${USERS:-10}" 2>&1 40 | -------------------------------------------------------------------------------- /service/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 HttpUser, TaskSet, between 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(HttpUser): 80 | tasks = [UserBehavior] 81 | wait_time = between(1, 10) 82 | -------------------------------------------------------------------------------- /service/loadgenerator/requirements.in: -------------------------------------------------------------------------------- 1 | locust==1.6.0 2 | requests==2.28.0 3 | urllib3==1.26.9 -------------------------------------------------------------------------------- /service/loadgenerator/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with python 3.7 3 | # To update, run: 4 | # 5 | # pip-compile requirements.in 6 | # 7 | brotli==1.0.9 8 | # via geventhttpclient 9 | certifi==2022.6.15 10 | # via 11 | # geventhttpclient 12 | # requests 13 | charset-normalizer==2.0.10 14 | # via requests 15 | click==8.1.3 16 | # via flask 17 | configargparse==1.5.3 18 | # via locust 19 | flask==1.1.2 20 | # via 21 | # flask-basicauth 22 | # flask-cors 23 | # locust 24 | flask-basicauth==0.2.0 25 | # via locust 26 | flask-cors==3.0.10 27 | # via locust 28 | gevent==20.12.1 29 | # via 30 | # geventhttpclient 31 | # locust 32 | geventhttpclient==1.5.3 33 | # via locust 34 | greenlet==0.4.17 35 | # via gevent 36 | idna==2.10 37 | # via requests 38 | itsdangerous==1.1.0 39 | # via flask 40 | jinja2==2.11.3 41 | # via flask 42 | locust==1.6.0 43 | # via -r requirements.in 44 | markupsafe==1.1.1 45 | # via jinja2 46 | msgpack==1.0.4 47 | # via locust 48 | psutil==5.9.1 49 | # via locust 50 | pyzmq==19.0.2 51 | # via locust 52 | requests==2.28.0 53 | # via 54 | # -r requirements.in 55 | # locust 56 | six==1.16.0 57 | # via 58 | # flask-cors 59 | # geventhttpclient 60 | urllib3==1.26.9 61 | # via 62 | # -r requirements.in 63 | # requests 64 | werkzeug==1.0.1 65 | # via 66 | # flask 67 | # locust 68 | zope-event==4.5.0 69 | # via gevent 70 | zope-interface==5.4.0 71 | # via gevent 72 | 73 | # The following packages are considered to be unsafe in a requirements file: 74 | # setuptools 75 | -------------------------------------------------------------------------------- /service/payment/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | Dockerfile 3 | -------------------------------------------------------------------------------- /service/payment/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with 'go test -c' 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # don't commit the service binary to vcs 18 | payment 19 | -------------------------------------------------------------------------------- /service/payment/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.0-alpine AS builder 2 | 3 | # Set Go env 4 | ENV CGO_ENABLED=0 GOOS=linux 5 | WORKDIR /go/src/hipstershop 6 | 7 | # Install dependencies 8 | RUN apk --update --no-cache add ca-certificates make protoc 9 | 10 | # Download grpc_health_probe 11 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ 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 | # Build Go binary 16 | COPY Makefile go.mod go.sum ./ 17 | RUN go env -w GOPROXY=https://goproxy.io,direct/ 18 | RUN make init && go mod download 19 | COPY . . 20 | RUN make proto tidy 21 | 22 | # Skaffold passes in debug-oriented compiler flags 23 | ARG SKAFFOLD_GO_GCFLAGS 24 | RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/src/hipstershop/payment . 25 | 26 | # Deployment container 27 | FROM scratch 28 | 29 | # Definition of this variable is used by 'skaffold debug' to identify a golang binary. 30 | # Default behavior - a failure prints a stack trace for the current goroutine. 31 | # See https://golang.org/pkg/runtime/ 32 | ENV GOTRACEBACK=single 33 | 34 | COPY --from=builder /etc/ssl/certs /etc/ssl/certs 35 | COPY --from=builder /bin/grpc_health_probe /bin/ 36 | COPY --from=builder /go/src/hipstershop/payment /hipstershop/payment 37 | 38 | ENTRYPOINT ["/hipstershop/payment"] 39 | -------------------------------------------------------------------------------- /service/payment/Makefile: -------------------------------------------------------------------------------- 1 | GOPATH:=$(shell go env GOPATH) 2 | 3 | .PHONY: init 4 | init: 5 | @go get -u google.golang.org/protobuf/proto 6 | @go install github.com/golang/protobuf/protoc-gen-go@latest 7 | @go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest 8 | .PHONY: proto 9 | proto: 10 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/payment.proto 11 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/health.proto 12 | 13 | 14 | .PHONY: update 15 | update: 16 | @go get -u 17 | 18 | .PHONY: tidy 19 | tidy: 20 | @go mod tidy 21 | 22 | .PHONY: build 23 | build: 24 | @go build -o payment *.go 25 | 26 | .PHONY: test 27 | test: 28 | @go test -v ./... -cover 29 | 30 | .PHONY: docker 31 | docker: 32 | @docker build -t payment:latest . -------------------------------------------------------------------------------- /service/payment/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "go-micro.dev/v5/config" 8 | "go-micro.dev/v5/config/source/env" 9 | ) 10 | 11 | type Config struct { 12 | Port int 13 | Tracing TracingConfig 14 | } 15 | 16 | type TracingConfig struct { 17 | Enable bool 18 | Jaeger JaegerConfig 19 | } 20 | 21 | type JaegerConfig struct { 22 | URL string 23 | } 24 | 25 | var cfg *Config = &Config{ 26 | Port: 50052, 27 | } 28 | 29 | func Address() string { 30 | return fmt.Sprintf(":%d", cfg.Port) 31 | } 32 | 33 | func Tracing() TracingConfig { 34 | return cfg.Tracing 35 | } 36 | 37 | func Load() error { 38 | configor, err := config.NewConfig(config.WithSource(env.NewSource())) 39 | if err != nil { 40 | return errors.Wrap(err, "configor.New") 41 | } 42 | if err := configor.Load(); err != nil { 43 | return errors.Wrap(err, "configor.Load") 44 | } 45 | if err := configor.Scan(cfg); err != nil { 46 | return errors.Wrap(err, "configor.Scan") 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /service/payment/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | 9 | pb "github.com/go-micro/demo/payment/proto" 10 | ) 11 | 12 | type Health struct{} 13 | 14 | func (h *Health) Check(ctx context.Context, req *pb.HealthCheckRequest, rsp *pb.HealthCheckResponse) error { 15 | rsp.Status = pb.HealthCheckResponse_SERVING 16 | return nil 17 | } 18 | 19 | func (h *Health) Watch(ctx context.Context, req *pb.HealthCheckRequest, stream pb.Health_WatchStream) error { 20 | return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") 21 | } 22 | -------------------------------------------------------------------------------- /service/payment/handler/paymentservice.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | 7 | creditcard "github.com/durango/go-credit-card" 8 | "github.com/google/uuid" 9 | "go-micro.dev/v5/logger" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/status" 12 | 13 | pb "github.com/go-micro/demo/payment/proto" 14 | ) 15 | 16 | type PaymentService struct{} 17 | 18 | func (s *PaymentService) Charge(ctx context.Context, in *pb.ChargeRequest, out *pb.ChargeResponse) error { 19 | card := creditcard.Card{ 20 | Number: in.CreditCard.CreditCardNumber, 21 | Cvv: strconv.FormatInt(int64(in.CreditCard.CreditCardCvv), 10), 22 | Year: strconv.FormatInt(int64(in.CreditCard.CreditCardExpirationYear), 10), 23 | Month: strconv.FormatInt(int64(in.CreditCard.CreditCardExpirationMonth), 10), 24 | } 25 | if err := card.Validate(); err != nil { 26 | return status.Errorf(codes.InvalidArgument, err.Error()) 27 | } 28 | 29 | // TODO: 30 | // Only VISA and mastercard is accepted, other card types (AMEX, dinersclub) will 31 | // throw UnacceptedCreditCard error. 32 | 33 | logger.Infof(`Transaction processed: %s, Amount: %s%d.%d`, in.CreditCard.CreditCardNumber, in.Amount.CurrencyCode, in.Amount.Units, in.Amount.Nanos) 34 | 35 | out.TransactionId = uuid.NewString() 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /service/payment/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | grpcc "github.com/go-micro/plugins/v4/client/grpc" 9 | _ "github.com/go-micro/plugins/v4/registry/kubernetes" 10 | grpcs "github.com/go-micro/plugins/v4/server/grpc" 11 | "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry" 12 | "go-micro.dev/v5" 13 | "go-micro.dev/v5/logger" 14 | "go-micro.dev/v5/server" 15 | "go.opentelemetry.io/otel" 16 | "go.opentelemetry.io/otel/propagation" 17 | 18 | "github.com/go-micro/demo/payment/config" 19 | "github.com/go-micro/demo/payment/handler" 20 | pb "github.com/go-micro/demo/payment/proto" 21 | ) 22 | 23 | var ( 24 | name = "payment" 25 | version = "1.0.0" 26 | ) 27 | 28 | func main() { 29 | // Load conigurations 30 | if err := config.Load(); err != nil { 31 | logger.Fatal(err) 32 | } 33 | 34 | // Create service 35 | srv := micro.NewService( 36 | micro.Server(grpcs.NewServer()), 37 | micro.Client(grpcc.NewClient()), 38 | ) 39 | opts := []micro.Option{ 40 | micro.Name(name), 41 | micro.Version(version), 42 | micro.Address(config.Address()), 43 | } 44 | if cfg := config.Tracing(); cfg.Enable { 45 | tp, err := newTracerProvider(name, srv.Server().Options().Id, cfg.Jaeger.URL) 46 | if err != nil { 47 | logger.Fatal(err) 48 | } 49 | defer func() { 50 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 51 | defer cancel() 52 | if err := tp.Shutdown(ctx); err != nil { 53 | logger.Fatal(err) 54 | } 55 | }() 56 | otel.SetTracerProvider(tp) 57 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 58 | traceOpts := []opentelemetry.Option{ 59 | opentelemetry.WithHandleFilter(func(ctx context.Context, r server.Request) bool { 60 | if e := r.Endpoint(); strings.HasPrefix(e, "Health.") { 61 | return true 62 | } 63 | return false 64 | }), 65 | } 66 | opts = append(opts, micro.WrapHandler(opentelemetry.NewHandlerWrapper(traceOpts...))) 67 | } 68 | srv.Init(opts...) 69 | 70 | // Register handler 71 | if err := pb.RegisterPaymentServiceHandler(srv.Server(), new(handler.PaymentService)); err != nil { 72 | logger.Fatal(err) 73 | } 74 | if err := pb.RegisterHealthHandler(srv.Server(), new(handler.Health)); err != nil { 75 | logger.Fatal(err) 76 | } 77 | 78 | // Run service 79 | if err := srv.Run(); err != nil { 80 | logger.Fatal(err) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /service/payment/proto/health.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hipstershop; 4 | 5 | option go_package = "./proto;hipstershop"; 6 | 7 | service Health { 8 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse) {} 9 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) {} 10 | } 11 | 12 | message HealthCheckRequest { 13 | string service = 1; 14 | } 15 | 16 | message HealthCheckResponse { 17 | enum ServingStatus { 18 | UNKNOWN = 0; 19 | SERVING = 1; 20 | NOT_SERVING = 2; 21 | SERVICE_UNKNOWN = 3; 22 | } 23 | ServingStatus status = 1; 24 | } 25 | -------------------------------------------------------------------------------- /service/payment/proto/paymentservice.pb.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: proto/payment.proto 3 | 4 | package hipstershop 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "google.golang.org/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | api "go-micro.dev/v5/api" 15 | client "go-micro.dev/v5/client" 16 | server "go-micro.dev/v5/server" 17 | ) 18 | 19 | // Reference imports to suppress errors if they are not otherwise used. 20 | var _ = proto.Marshal 21 | var _ = fmt.Errorf 22 | var _ = math.Inf 23 | 24 | // Reference imports to suppress errors if they are not otherwise used. 25 | var _ api.Endpoint 26 | var _ context.Context 27 | var _ client.Option 28 | var _ server.Option 29 | 30 | // Api Endpoints for PaymentService service 31 | 32 | func NewPaymentServiceEndpoints() []*api.Endpoint { 33 | return []*api.Endpoint{} 34 | } 35 | 36 | // Client API for PaymentService service 37 | 38 | type PaymentService interface { 39 | Charge(ctx context.Context, in *ChargeRequest, opts ...client.CallOption) (*ChargeResponse, error) 40 | } 41 | 42 | type paymentService struct { 43 | c client.Client 44 | name string 45 | } 46 | 47 | func NewPaymentService(name string, c client.Client) PaymentService { 48 | return &paymentService{ 49 | c: c, 50 | name: name, 51 | } 52 | } 53 | 54 | func (c *paymentService) Charge(ctx context.Context, in *ChargeRequest, opts ...client.CallOption) (*ChargeResponse, error) { 55 | req := c.c.NewRequest(c.name, "PaymentService.Charge", in) 56 | out := new(ChargeResponse) 57 | err := c.c.Call(ctx, req, out, opts...) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return out, nil 62 | } 63 | 64 | // Server API for PaymentService service 65 | 66 | type PaymentServiceHandler interface { 67 | Charge(context.Context, *ChargeRequest, *ChargeResponse) error 68 | } 69 | 70 | func RegisterPaymentServiceHandler(s server.Server, hdlr PaymentServiceHandler, opts ...server.HandlerOption) error { 71 | type paymentService interface { 72 | Charge(ctx context.Context, in *ChargeRequest, out *ChargeResponse) error 73 | } 74 | type PaymentService struct { 75 | paymentService 76 | } 77 | h := &paymentServiceHandler{hdlr} 78 | return s.Handle(s.NewHandler(&PaymentService{h}, opts...)) 79 | } 80 | 81 | type paymentServiceHandler struct { 82 | PaymentServiceHandler 83 | } 84 | 85 | func (h *paymentServiceHandler) Charge(ctx context.Context, in *ChargeRequest, out *ChargeResponse) error { 86 | return h.PaymentServiceHandler.Charge(ctx, in, out) 87 | } 88 | -------------------------------------------------------------------------------- /service/payment/proto/paymentservice.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | syntax = "proto3"; 16 | 17 | package hipstershop; 18 | 19 | option go_package = "./proto;hipstershop"; 20 | 21 | // Represents an amount of money with its currency type. 22 | message Money { 23 | // The 3-letter currency code defined in ISO 4217. 24 | string currency_code = 1; 25 | 26 | // The whole units of the amount. 27 | // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. 28 | int64 units = 2; 29 | 30 | // Number of nano (10^-9) units of the amount. 31 | // The value must be between -999,999,999 and +999,999,999 inclusive. 32 | // If `units` is positive, `nanos` must be positive or zero. 33 | // If `units` is zero, `nanos` can be positive, zero, or negative. 34 | // If `units` is negative, `nanos` must be negative or zero. 35 | // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. 36 | int32 nanos = 3; 37 | } 38 | 39 | // -------------Payment service----------------- 40 | 41 | service PaymentService { 42 | rpc Charge(ChargeRequest) returns (ChargeResponse) {} 43 | } 44 | 45 | message CreditCardInfo { 46 | string credit_card_number = 1; 47 | int32 credit_card_cvv = 2; 48 | int32 credit_card_expiration_year = 3; 49 | int32 credit_card_expiration_month = 4; 50 | } 51 | 52 | message ChargeRequest { 53 | Money amount = 1; 54 | CreditCardInfo credit_card = 2; 55 | } 56 | 57 | message ChargeResponse { string transaction_id = 1; } -------------------------------------------------------------------------------- /service/payment/tracing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go.opentelemetry.io/otel/exporters/jaeger" 5 | "go.opentelemetry.io/otel/sdk/resource" 6 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 7 | semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 8 | ) 9 | 10 | // newTracerProvider returns an OpenTelemetry TracerProvider configured to use 11 | // the Jaeger exporter that will send spans to the provided url. The returned 12 | // TracerProvider will also use a Resource configured with all the information 13 | // about the application. 14 | func newTracerProvider(serviceName, serviceID, url string) (*tracesdk.TracerProvider, error) { 15 | // Create the Jaeger exporter 16 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 17 | if err != nil { 18 | return nil, err 19 | } 20 | tp := tracesdk.NewTracerProvider( 21 | tracesdk.WithBatcher(exp), 22 | tracesdk.WithResource(resource.NewWithAttributes( 23 | semconv.SchemaURL, 24 | semconv.ServiceNameKey.String(serviceName), 25 | semconv.ServiceVersionKey.String(version), 26 | semconv.ServiceInstanceIDKey.String(serviceID), 27 | )), 28 | ) 29 | return tp, nil 30 | } 31 | -------------------------------------------------------------------------------- /service/productcatalog/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | Dockerfile 3 | -------------------------------------------------------------------------------- /service/productcatalog/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with 'go test -c' 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # don't commit the service binary to vcs 18 | productcatalog 19 | -------------------------------------------------------------------------------- /service/productcatalog/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.0-alpine AS builder 2 | 3 | # Set Go env 4 | ENV CGO_ENABLED=0 GOOS=linux 5 | WORKDIR /go/src/hipstershop 6 | 7 | # Install dependencies 8 | RUN apk --update --no-cache add ca-certificates make protoc 9 | 10 | # Download grpc_health_probe 11 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ 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 | # Build Go binary 16 | COPY Makefile go.mod go.sum ./ 17 | RUN go env -w GOPROXY=https://goproxy.io,direct/ 18 | RUN make init && go mod download 19 | COPY . . 20 | RUN make proto tidy 21 | 22 | # Skaffold passes in debug-oriented compiler flags 23 | ARG SKAFFOLD_GO_GCFLAGS 24 | RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/src/hipstershop/productcatalog . 25 | 26 | # Deployment container 27 | FROM scratch 28 | 29 | WORKDIR /hipstershop 30 | 31 | # Definition of this variable is used by 'skaffold debug' to identify a golang binary. 32 | # Default behavior - a failure prints a stack trace for the current goroutine. 33 | # See https://golang.org/pkg/runtime/ 34 | ENV GOTRACEBACK=single 35 | 36 | COPY ./data /hipstershop/data/ 37 | COPY --from=builder /etc/ssl/certs /etc/ssl/certs 38 | COPY --from=builder /bin/grpc_health_probe /bin/ 39 | COPY --from=builder /go/src/hipstershop/productcatalog /hipstershop/productcatalog 40 | 41 | ENTRYPOINT ["/hipstershop/productcatalog"] 42 | -------------------------------------------------------------------------------- /service/productcatalog/Makefile: -------------------------------------------------------------------------------- 1 | GOPATH:=$(shell go env GOPATH) 2 | 3 | .PHONY: init 4 | init: 5 | @go get -u google.golang.org/protobuf/proto 6 | @go install github.com/golang/protobuf/protoc-gen-go@latest 7 | @go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest 8 | 9 | .PHONY: proto 10 | proto: 11 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/productcatalog.proto 12 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/health.proto 13 | 14 | 15 | .PHONY: update 16 | update: 17 | @go get -u 18 | 19 | .PHONY: tidy 20 | tidy: 21 | @go mod tidy 22 | 23 | .PHONY: build 24 | build: 25 | @go build -o productcatalog *.go 26 | 27 | .PHONY: test 28 | test: 29 | @go test -v ./... -cover 30 | 31 | .PHONY: docker 32 | docker: 33 | @docker build -t productcatalog:latest . -------------------------------------------------------------------------------- /service/productcatalog/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "go-micro.dev/v5/config" 8 | "go-micro.dev/v5/config/source/env" 9 | ) 10 | 11 | type Config struct { 12 | Port int 13 | Tracing TracingConfig 14 | } 15 | 16 | type TracingConfig struct { 17 | Enable bool 18 | Jaeger JaegerConfig 19 | } 20 | 21 | type JaegerConfig struct { 22 | URL string 23 | } 24 | 25 | var cfg *Config = &Config{ 26 | Port: 3550, 27 | } 28 | 29 | func Address() string { 30 | return fmt.Sprintf(":%d", cfg.Port) 31 | } 32 | 33 | func Tracing() TracingConfig { 34 | return cfg.Tracing 35 | } 36 | 37 | func Load() error { 38 | configor, err := config.NewConfig(config.WithSource(env.NewSource())) 39 | if err != nil { 40 | return errors.Wrap(err, "configor.New") 41 | } 42 | if err := configor.Load(); err != nil { 43 | return errors.Wrap(err, "configor.Load") 44 | } 45 | if err := configor.Scan(cfg); err != nil { 46 | return errors.Wrap(err, "configor.Scan") 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /service/productcatalog/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | 9 | pb "github.com/go-micro/demo/productcatalog/proto" 10 | ) 11 | 12 | type Health struct{} 13 | 14 | func (h *Health) Check(ctx context.Context, req *pb.HealthCheckRequest, rsp *pb.HealthCheckResponse) error { 15 | rsp.Status = pb.HealthCheckResponse_SERVING 16 | return nil 17 | } 18 | 19 | func (h *Health) Watch(ctx context.Context, req *pb.HealthCheckRequest, stream pb.Health_WatchStream) error { 20 | return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") 21 | } 22 | -------------------------------------------------------------------------------- /service/productcatalog/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | grpcc "github.com/go-micro/plugins/v4/client/grpc" 9 | _ "github.com/go-micro/plugins/v4/registry/kubernetes" 10 | grpcs "github.com/go-micro/plugins/v4/server/grpc" 11 | "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry" 12 | "go-micro.dev/v5" 13 | "go-micro.dev/v5/logger" 14 | "go-micro.dev/v5/server" 15 | "go.opentelemetry.io/otel" 16 | "go.opentelemetry.io/otel/propagation" 17 | 18 | "github.com/go-micro/demo/productcatalog/config" 19 | "github.com/go-micro/demo/productcatalog/handler" 20 | pb "github.com/go-micro/demo/productcatalog/proto" 21 | ) 22 | 23 | var ( 24 | name = "productcatalog" 25 | version = "1.0.0" 26 | ) 27 | 28 | func main() { 29 | // Load conigurations 30 | if err := config.Load(); err != nil { 31 | logger.Fatal(err) 32 | } 33 | 34 | // Create service 35 | srv := micro.NewService( 36 | micro.Server(grpcs.NewServer()), 37 | micro.Client(grpcc.NewClient()), 38 | ) 39 | opts := []micro.Option{ 40 | micro.Name(name), 41 | micro.Version(version), 42 | micro.Address(config.Address()), 43 | } 44 | if cfg := config.Tracing(); cfg.Enable { 45 | tp, err := newTracerProvider(name, srv.Server().Options().Id, cfg.Jaeger.URL) 46 | if err != nil { 47 | logger.Fatal(err) 48 | } 49 | defer func() { 50 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 51 | defer cancel() 52 | if err := tp.Shutdown(ctx); err != nil { 53 | logger.Fatal(err) 54 | } 55 | }() 56 | otel.SetTracerProvider(tp) 57 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 58 | traceOpts := []opentelemetry.Option{ 59 | opentelemetry.WithHandleFilter(func(ctx context.Context, r server.Request) bool { 60 | if e := r.Endpoint(); strings.HasPrefix(e, "Health.") { 61 | return true 62 | } 63 | return false 64 | }), 65 | } 66 | opts = append(opts, micro.WrapHandler(opentelemetry.NewHandlerWrapper(traceOpts...))) 67 | } 68 | srv.Init(opts...) 69 | 70 | // Register handler 71 | if err := pb.RegisterProductCatalogServiceHandler(srv.Server(), new(handler.ProductCatalogService)); err != nil { 72 | logger.Fatal(err) 73 | } 74 | if err := pb.RegisterHealthHandler(srv.Server(), new(handler.Health)); err != nil { 75 | logger.Fatal(err) 76 | } 77 | 78 | // Run service 79 | if err := srv.Run(); err != nil { 80 | logger.Fatal(err) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /service/productcatalog/proto/health.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hipstershop; 4 | 5 | option go_package = "./proto;hipstershop"; 6 | 7 | service Health { 8 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse) {} 9 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) {} 10 | } 11 | 12 | message HealthCheckRequest { 13 | string service = 1; 14 | } 15 | 16 | message HealthCheckResponse { 17 | enum ServingStatus { 18 | UNKNOWN = 0; 19 | SERVING = 1; 20 | NOT_SERVING = 2; 21 | SERVICE_UNKNOWN = 3; 22 | } 23 | ServingStatus status = 1; 24 | } 25 | -------------------------------------------------------------------------------- /service/productcatalog/proto/productcatalogservice.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | syntax = "proto3"; 16 | 17 | package hipstershop; 18 | 19 | option go_package = "./proto;hipstershop"; 20 | 21 | // Represents an amount of money with its currency type. 22 | message Money { 23 | // The 3-letter currency code defined in ISO 4217. 24 | string currency_code = 1; 25 | 26 | // The whole units of the amount. 27 | // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. 28 | int64 units = 2; 29 | 30 | // Number of nano (10^-9) units of the amount. 31 | // The value must be between -999,999,999 and +999,999,999 inclusive. 32 | // If `units` is positive, `nanos` must be positive or zero. 33 | // If `units` is zero, `nanos` can be positive, zero, or negative. 34 | // If `units` is negative, `nanos` must be negative or zero. 35 | // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. 36 | int32 nanos = 3; 37 | } 38 | 39 | message Empty {} 40 | 41 | // ---------------Product Catalog---------------- 42 | 43 | service ProductCatalogService { 44 | rpc ListProducts(Empty) returns (ListProductsResponse) {} 45 | rpc GetProduct(GetProductRequest) returns (Product) {} 46 | rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {} 47 | } 48 | 49 | message Product { 50 | string id = 1; 51 | string name = 2; 52 | string description = 3; 53 | string picture = 4; 54 | Money price_usd = 5; 55 | 56 | // Categories such as "clothing" or "kitchen" that can be used to look up 57 | // other related products. 58 | repeated string categories = 6; 59 | } 60 | 61 | message ListProductsResponse { 62 | repeated Product products = 1; 63 | } 64 | 65 | message GetProductRequest { 66 | string id = 1; 67 | } 68 | 69 | message SearchProductsRequest { 70 | string query = 1; 71 | } 72 | 73 | message SearchProductsResponse { 74 | repeated Product results = 1; 75 | } -------------------------------------------------------------------------------- /service/productcatalog/tracing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go.opentelemetry.io/otel/exporters/jaeger" 5 | "go.opentelemetry.io/otel/sdk/resource" 6 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 7 | semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 8 | ) 9 | 10 | // newTracerProvider returns an OpenTelemetry TracerProvider configured to use 11 | // the Jaeger exporter that will send spans to the provided url. The returned 12 | // TracerProvider will also use a Resource configured with all the information 13 | // about the application. 14 | func newTracerProvider(serviceName, serviceID, url string) (*tracesdk.TracerProvider, error) { 15 | // Create the Jaeger exporter 16 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 17 | if err != nil { 18 | return nil, err 19 | } 20 | tp := tracesdk.NewTracerProvider( 21 | tracesdk.WithBatcher(exp), 22 | tracesdk.WithResource(resource.NewWithAttributes( 23 | semconv.SchemaURL, 24 | semconv.ServiceNameKey.String(serviceName), 25 | semconv.ServiceVersionKey.String(version), 26 | semconv.ServiceInstanceIDKey.String(serviceID), 27 | )), 28 | ) 29 | return tp, nil 30 | } 31 | -------------------------------------------------------------------------------- /service/recommendation/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | Dockerfile 3 | -------------------------------------------------------------------------------- /service/recommendation/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with 'go test -c' 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # don't commit the service binary to vcs 18 | recommendation 19 | -------------------------------------------------------------------------------- /service/recommendation/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.0-alpine AS builder 2 | 3 | # Set Go env 4 | ENV CGO_ENABLED=0 GOOS=linux 5 | WORKDIR /go/src/hipstershop 6 | 7 | # Install dependencies 8 | RUN apk --update --no-cache add ca-certificates make protoc 9 | 10 | # Download grpc_health_probe 11 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ 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 | # Build Go binary 16 | COPY Makefile go.mod go.sum ./ 17 | RUN go env -w GOPROXY=https://goproxy.io,direct/ 18 | RUN make init && go mod download 19 | COPY . . 20 | RUN make proto tidy 21 | 22 | # Skaffold passes in debug-oriented compiler flags 23 | ARG SKAFFOLD_GO_GCFLAGS 24 | RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/src/hipstershop/recommendation . 25 | 26 | # Deployment container 27 | FROM scratch 28 | 29 | WORKDIR /hipstershop 30 | 31 | # Definition of this variable is used by 'skaffold debug' to identify a golang binary. 32 | # Default behavior - a failure prints a stack trace for the current goroutine. 33 | # See https://golang.org/pkg/runtime/ 34 | ENV GOTRACEBACK=single 35 | 36 | COPY --from=builder /etc/ssl/certs /etc/ssl/certs 37 | COPY --from=builder /bin/grpc_health_probe /bin/ 38 | COPY --from=builder /go/src/hipstershop/recommendation /hipstershop/recommendation 39 | 40 | ENTRYPOINT ["/hipstershop/recommendation"] 41 | -------------------------------------------------------------------------------- /service/recommendation/Makefile: -------------------------------------------------------------------------------- 1 | GOPATH:=$(shell go env GOPATH) 2 | 3 | .PHONY: init 4 | init: 5 | @go get -u google.golang.org/protobuf/proto 6 | @go install github.com/golang/protobuf/protoc-gen-go@latest 7 | @go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest 8 | .PHONY: proto 9 | proto: 10 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/recommendation.proto 11 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/health.proto 12 | 13 | 14 | .PHONY: update 15 | update: 16 | @go get -u 17 | 18 | .PHONY: tidy 19 | tidy: 20 | @go mod tidy 21 | 22 | .PHONY: build 23 | build: 24 | @go build -o recommendation *.go 25 | 26 | .PHONY: test 27 | test: 28 | @go test -v ./... -cover 29 | 30 | .PHONY: docker 31 | docker: 32 | @docker build -t recommendation:latest . -------------------------------------------------------------------------------- /service/recommendation/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "go-micro.dev/v5/config" 8 | "go-micro.dev/v5/config/source/env" 9 | ) 10 | 11 | type Config struct { 12 | Port int 13 | Tracing TracingConfig 14 | ProductCatalogService string 15 | } 16 | 17 | type TracingConfig struct { 18 | Enable bool 19 | Jaeger JaegerConfig 20 | } 21 | 22 | type JaegerConfig struct { 23 | URL string 24 | } 25 | 26 | var cfg *Config = &Config{ 27 | Port: 8080, 28 | ProductCatalogService: "productcatalog", 29 | } 30 | 31 | func Get() Config { 32 | return *cfg 33 | } 34 | 35 | func Address() string { 36 | return fmt.Sprintf(":%d", cfg.Port) 37 | } 38 | 39 | func Tracing() TracingConfig { 40 | return cfg.Tracing 41 | } 42 | 43 | func Load() error { 44 | configor, err := config.NewConfig(config.WithSource(env.NewSource())) 45 | if err != nil { 46 | return errors.Wrap(err, "configor.New") 47 | } 48 | if err := configor.Load(); err != nil { 49 | return errors.Wrap(err, "configor.Load") 50 | } 51 | if err := configor.Scan(cfg); err != nil { 52 | return errors.Wrap(err, "configor.Scan") 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /service/recommendation/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | 9 | pb "github.com/go-micro/demo/recommendation/proto" 10 | ) 11 | 12 | type Health struct{} 13 | 14 | func (h *Health) Check(ctx context.Context, req *pb.HealthCheckRequest, rsp *pb.HealthCheckResponse) error { 15 | rsp.Status = pb.HealthCheckResponse_SERVING 16 | return nil 17 | } 18 | 19 | func (h *Health) Watch(ctx context.Context, req *pb.HealthCheckRequest, stream pb.Health_WatchStream) error { 20 | return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") 21 | } 22 | -------------------------------------------------------------------------------- /service/recommendation/handler/recommendationservice.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | 7 | "go-micro.dev/v5/logger" 8 | 9 | pb "github.com/go-micro/demo/recommendation/proto" 10 | ) 11 | 12 | type RecommendationService struct { 13 | ProductCatalogService pb.ProductCatalogService 14 | } 15 | 16 | func (s *RecommendationService) ListRecommendations(ctx context.Context, in *pb.ListRecommendationsRequest, out *pb.ListRecommendationsResponse) error { 17 | maxResponsesCount := 5 18 | // # fetch list of products from product catalog stub 19 | catalog, err := s.ProductCatalogService.ListProducts(ctx, &pb.Empty{}) 20 | if err != nil { 21 | return err 22 | } 23 | filteredProductsIDs := make([]string, 0, len(catalog.Products)) 24 | for _, p := range catalog.Products { 25 | if contains(p.Id, in.ProductIds) { 26 | continue 27 | } 28 | filteredProductsIDs = append(filteredProductsIDs, p.Id) 29 | } 30 | productIDs := sample(filteredProductsIDs, maxResponsesCount) 31 | logger.Infof("[Recv ListRecommendations] product_ids=%v", productIDs) 32 | out.ProductIds = productIDs 33 | return nil 34 | } 35 | 36 | func contains(target string, source []string) bool { 37 | for _, s := range source { 38 | if target == s { 39 | return true 40 | } 41 | } 42 | return false 43 | } 44 | 45 | func sample(source []string, c int) []string { 46 | n := len(source) 47 | if n <= c { 48 | return source 49 | } 50 | indices := make([]int, n) 51 | for i := 0; i < n; i++ { 52 | indices[i] = i 53 | } 54 | for i := n - 1; i > 0; i-- { 55 | j := rand.Intn(i + 1) 56 | indices[i], indices[j] = indices[j], indices[i] 57 | } 58 | result := make([]string, 0, c) 59 | for i := 0; i < c; i++ { 60 | result = append(result, source[indices[i]]) 61 | } 62 | return result 63 | } 64 | -------------------------------------------------------------------------------- /service/recommendation/proto/health.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hipstershop; 4 | 5 | option go_package = "./proto;hipstershop"; 6 | 7 | service Health { 8 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse) {} 9 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) {} 10 | } 11 | 12 | message HealthCheckRequest { 13 | string service = 1; 14 | } 15 | 16 | message HealthCheckResponse { 17 | enum ServingStatus { 18 | UNKNOWN = 0; 19 | SERVING = 1; 20 | NOT_SERVING = 2; 21 | SERVICE_UNKNOWN = 3; 22 | } 23 | ServingStatus status = 1; 24 | } 25 | -------------------------------------------------------------------------------- /service/recommendation/tracing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go.opentelemetry.io/otel/exporters/jaeger" 5 | "go.opentelemetry.io/otel/sdk/resource" 6 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 7 | semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 8 | ) 9 | 10 | // newTracerProvider returns an OpenTelemetry TracerProvider configured to use 11 | // the Jaeger exporter that will send spans to the provided url. The returned 12 | // TracerProvider will also use a Resource configured with all the information 13 | // about the application. 14 | func newTracerProvider(serviceName, serviceID, url string) (*tracesdk.TracerProvider, error) { 15 | // Create the Jaeger exporter 16 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 17 | if err != nil { 18 | return nil, err 19 | } 20 | tp := tracesdk.NewTracerProvider( 21 | tracesdk.WithBatcher(exp), 22 | tracesdk.WithResource(resource.NewWithAttributes( 23 | semconv.SchemaURL, 24 | semconv.ServiceNameKey.String(serviceName), 25 | semconv.ServiceVersionKey.String(version), 26 | semconv.ServiceInstanceIDKey.String(serviceID), 27 | )), 28 | ) 29 | return tp, nil 30 | } 31 | -------------------------------------------------------------------------------- /service/shipping/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | Dockerfile 3 | -------------------------------------------------------------------------------- /service/shipping/.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with 'go test -c' 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # don't commit the service binary to vcs 18 | shipping 19 | -------------------------------------------------------------------------------- /service/shipping/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.0-alpine AS builder 2 | 3 | # Set Go env 4 | ENV CGO_ENABLED=0 GOOS=linux 5 | WORKDIR /go/src/hipstershop 6 | 7 | # Install dependencies 8 | RUN apk --update --no-cache add ca-certificates make protoc 9 | 10 | # Download grpc_health_probe 11 | RUN GRPC_HEALTH_PROBE_VERSION=v0.4.11 && \ 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 | # Build Go binary 16 | COPY Makefile go.mod go.sum ./ 17 | RUN go env -w GOPROXY=https://goproxy.io,direct/ 18 | RUN make init && go mod download 19 | COPY . . 20 | RUN make proto tidy 21 | 22 | # Skaffold passes in debug-oriented compiler flags 23 | ARG SKAFFOLD_GO_GCFLAGS 24 | RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -o /go/src/hipstershop/shipping . 25 | 26 | # Deployment container 27 | FROM scratch 28 | 29 | # Definition of this variable is used by 'skaffold debug' to identify a golang binary. 30 | # Default behavior - a failure prints a stack trace for the current goroutine. 31 | # See https://golang.org/pkg/runtime/ 32 | ENV GOTRACEBACK=single 33 | 34 | COPY --from=builder /etc/ssl/certs /etc/ssl/certs 35 | COPY --from=builder /bin/grpc_health_probe /bin/ 36 | COPY --from=builder /go/src/hipstershop/shipping /hipstershop/shipping 37 | 38 | ENTRYPOINT ["/hipstershop/shipping"] 39 | -------------------------------------------------------------------------------- /service/shipping/Makefile: -------------------------------------------------------------------------------- 1 | GOPATH:=$(shell go env GOPATH) 2 | 3 | .PHONY: init 4 | init: 5 | @go get -u google.golang.org/protobuf/proto 6 | @go install github.com/golang/protobuf/protoc-gen-go@latest 7 | @go install github.com/go-micro/generator/cmd/protoc-gen-micro@latest 8 | 9 | .PHONY: proto 10 | proto: 11 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/shipping.proto 12 | @protoc --proto_path=. --micro_out=. --go_out=:. proto/health.proto 13 | 14 | 15 | .PHONY: update 16 | update: 17 | @go get -u 18 | 19 | .PHONY: tidy 20 | tidy: 21 | @go mod tidy 22 | 23 | .PHONY: build 24 | build: 25 | @go build -o shipping *.go 26 | 27 | .PHONY: test 28 | test: 29 | @go test -v ./... -cover 30 | 31 | .PHONY: docker 32 | docker: 33 | @docker build -t shipping:latest . -------------------------------------------------------------------------------- /service/shipping/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pkg/errors" 7 | "go-micro.dev/v5/config" 8 | "go-micro.dev/v5/config/source/env" 9 | ) 10 | 11 | type Config struct { 12 | Port int 13 | Tracing TracingConfig 14 | } 15 | 16 | type TracingConfig struct { 17 | Enable bool 18 | Jaeger JaegerConfig 19 | } 20 | 21 | type JaegerConfig struct { 22 | URL string 23 | } 24 | 25 | var cfg *Config = &Config{ 26 | Port: 50051, 27 | } 28 | 29 | func Address() string { 30 | return fmt.Sprintf(":%d", cfg.Port) 31 | } 32 | 33 | func Tracing() TracingConfig { 34 | return cfg.Tracing 35 | } 36 | 37 | func Load() error { 38 | configor, err := config.NewConfig(config.WithSource(env.NewSource())) 39 | if err != nil { 40 | return errors.Wrap(err, "configor.New") 41 | } 42 | if err := configor.Load(); err != nil { 43 | return errors.Wrap(err, "configor.Load") 44 | } 45 | if err := configor.Scan(cfg); err != nil { 46 | return errors.Wrap(err, "configor.Scan") 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /service/shipping/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | 9 | pb "github.com/go-micro/demo/shipping/proto" 10 | ) 11 | 12 | type Health struct{} 13 | 14 | func (h *Health) Check(ctx context.Context, req *pb.HealthCheckRequest, rsp *pb.HealthCheckResponse) error { 15 | rsp.Status = pb.HealthCheckResponse_SERVING 16 | return nil 17 | } 18 | 19 | func (h *Health) Watch(ctx context.Context, req *pb.HealthCheckRequest, stream pb.Health_WatchStream) error { 20 | return status.Errorf(codes.Unimplemented, "health check via Watch not implemented") 21 | } 22 | -------------------------------------------------------------------------------- /service/shipping/handler/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 handler 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(8.99) 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 | -------------------------------------------------------------------------------- /service/shipping/handler/shippingservice.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "go-micro.dev/v5/logger" 8 | 9 | pb "github.com/go-micro/demo/shipping/proto" 10 | ) 11 | 12 | type ShippingService struct{} 13 | 14 | func (s *ShippingService) GetQuote(ctx context.Context, in *pb.GetQuoteRequest, out *pb.GetQuoteResponse) error { 15 | logger.Info("[GetQuote] received request") 16 | defer logger.Info("[GetQuote] completed request") 17 | 18 | // 1. Generate a quote based on the total number of items to be shipped. 19 | quote := CreateQuoteFromCount(0) 20 | 21 | // 2. Generate a response. 22 | out.CostUsd = &pb.Money{ 23 | CurrencyCode: "USD", 24 | Units: int64(quote.Dollars), 25 | Nanos: int32(quote.Cents * 10000000), 26 | } 27 | return nil 28 | } 29 | 30 | func (s *ShippingService) ShipOrder(ctx context.Context, in *pb.ShipOrderRequest, out *pb.ShipOrderResponse) error { 31 | logger.Info("[ShipOrder] received request") 32 | defer logger.Info("[ShipOrder] completed request") 33 | // 1. Create a Tracking ID 34 | baseAddress := fmt.Sprintf("%s, %s, %s", in.Address.StreetAddress, in.Address.City, in.Address.State) 35 | id := CreateTrackingId(baseAddress) 36 | 37 | // 2. Generate a response. 38 | out.TrackingId = id 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /service/shipping/handler/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 handler 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 | -------------------------------------------------------------------------------- /service/shipping/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | grpcc "github.com/go-micro/plugins/v4/client/grpc" 9 | _ "github.com/go-micro/plugins/v4/registry/kubernetes" 10 | grpcs "github.com/go-micro/plugins/v4/server/grpc" 11 | "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry" 12 | "go-micro.dev/v5" 13 | "go-micro.dev/v5/logger" 14 | "go-micro.dev/v5/server" 15 | "go.opentelemetry.io/otel" 16 | "go.opentelemetry.io/otel/propagation" 17 | 18 | "github.com/go-micro/demo/shipping/config" 19 | "github.com/go-micro/demo/shipping/handler" 20 | pb "github.com/go-micro/demo/shipping/proto" 21 | ) 22 | 23 | var ( 24 | name = "shipping" 25 | version = "1.0.0" 26 | ) 27 | 28 | func main() { 29 | // Load conigurations 30 | if err := config.Load(); err != nil { 31 | logger.Fatal(err) 32 | } 33 | 34 | // Create service 35 | srv := micro.NewService( 36 | micro.Server(grpcs.NewServer()), 37 | micro.Client(grpcc.NewClient()), 38 | ) 39 | opts := []micro.Option{ 40 | micro.Name(name), 41 | micro.Version(version), 42 | micro.Address(config.Address()), 43 | } 44 | if cfg := config.Tracing(); cfg.Enable { 45 | tp, err := newTracerProvider(name, srv.Server().Options().Id, cfg.Jaeger.URL) 46 | if err != nil { 47 | logger.Fatal(err) 48 | } 49 | defer func() { 50 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 51 | defer cancel() 52 | if err := tp.Shutdown(ctx); err != nil { 53 | logger.Fatal(err) 54 | } 55 | }() 56 | otel.SetTracerProvider(tp) 57 | otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 58 | traceOpts := []opentelemetry.Option{ 59 | opentelemetry.WithHandleFilter(func(ctx context.Context, r server.Request) bool { 60 | if e := r.Endpoint(); strings.HasPrefix(e, "Health.") { 61 | return true 62 | } 63 | return false 64 | }), 65 | } 66 | opts = append(opts, micro.WrapHandler(opentelemetry.NewHandlerWrapper(traceOpts...))) 67 | } 68 | srv.Init(opts...) 69 | 70 | // Register handler 71 | if err := pb.RegisterShippingServiceHandler(srv.Server(), new(handler.ShippingService)); err != nil { 72 | logger.Fatal(err) 73 | } 74 | if err := pb.RegisterHealthHandler(srv.Server(), new(handler.Health)); err != nil { 75 | logger.Fatal(err) 76 | } 77 | 78 | // Run service 79 | if err := srv.Run(); err != nil { 80 | logger.Fatal(err) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /service/shipping/proto/health.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package hipstershop; 4 | 5 | option go_package = "./proto;hipstershop"; 6 | 7 | service Health { 8 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse) {} 9 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) {} 10 | } 11 | 12 | message HealthCheckRequest { string service = 1; } 13 | 14 | message HealthCheckResponse { 15 | enum ServingStatus { 16 | UNKNOWN = 0; 17 | SERVING = 1; 18 | NOT_SERVING = 2; 19 | SERVICE_UNKNOWN = 3; 20 | } 21 | ServingStatus status = 1; 22 | } 23 | -------------------------------------------------------------------------------- /service/shipping/proto/shippingservice.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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 | syntax = "proto3"; 16 | 17 | package hipstershop; 18 | 19 | option go_package = "./proto;hipstershop"; 20 | 21 | message CartItem { 22 | string product_id = 1; 23 | int32 quantity = 2; 24 | } 25 | 26 | // Represents an amount of money with its currency type. 27 | message Money { 28 | // The 3-letter currency code defined in ISO 4217. 29 | string currency_code = 1; 30 | 31 | // The whole units of the amount. 32 | // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. 33 | int64 units = 2; 34 | 35 | // Number of nano (10^-9) units of the amount. 36 | // The value must be between -999,999,999 and +999,999,999 inclusive. 37 | // If `units` is positive, `nanos` must be positive or zero. 38 | // If `units` is zero, `nanos` can be positive, zero, or negative. 39 | // If `units` is negative, `nanos` must be negative or zero. 40 | // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. 41 | int32 nanos = 3; 42 | } 43 | 44 | // ---------------Shipping Service---------- 45 | 46 | service ShippingService { 47 | rpc GetQuote(GetQuoteRequest) returns (GetQuoteResponse) {} 48 | rpc ShipOrder(ShipOrderRequest) returns (ShipOrderResponse) {} 49 | } 50 | 51 | message GetQuoteRequest { 52 | Address address = 1; 53 | repeated CartItem items = 2; 54 | } 55 | 56 | message GetQuoteResponse { Money cost_usd = 1; } 57 | 58 | message ShipOrderRequest { 59 | Address address = 1; 60 | repeated CartItem items = 2; 61 | } 62 | 63 | message ShipOrderResponse { string tracking_id = 1; } 64 | 65 | message Address { 66 | string street_address = 1; 67 | string city = 2; 68 | string state = 3; 69 | string country = 4; 70 | int32 zip_code = 5; 71 | } -------------------------------------------------------------------------------- /service/shipping/tracing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go.opentelemetry.io/otel/exporters/jaeger" 5 | "go.opentelemetry.io/otel/sdk/resource" 6 | tracesdk "go.opentelemetry.io/otel/sdk/trace" 7 | semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 8 | ) 9 | 10 | // newTracerProvider returns an OpenTelemetry TracerProvider configured to use 11 | // the Jaeger exporter that will send spans to the provided url. The returned 12 | // TracerProvider will also use a Resource configured with all the information 13 | // about the application. 14 | func newTracerProvider(serviceName, serviceID, url string) (*tracesdk.TracerProvider, error) { 15 | // Create the Jaeger exporter 16 | exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 17 | if err != nil { 18 | return nil, err 19 | } 20 | tp := tracesdk.NewTracerProvider( 21 | tracesdk.WithBatcher(exp), 22 | tracesdk.WithResource(resource.NewWithAttributes( 23 | semconv.SchemaURL, 24 | semconv.ServiceNameKey.String(serviceName), 25 | semconv.ServiceVersionKey.String(version), 26 | semconv.ServiceInstanceIDKey.String(serviceID), 27 | )), 28 | ) 29 | return tp, nil 30 | } 31 | -------------------------------------------------------------------------------- /skaffold.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: skaffold/v2beta27 2 | kind: Config 3 | metadata: 4 | name: hipstershop 5 | build: 6 | artifacts: 7 | # image tags are relative; to specify an image repo (e.g. GCR), you 8 | # must provide a "default repo" using one of the methods described 9 | # here: 10 | # https://skaffold.dev/docs/concepts/#image-repository-handling 11 | - image: email 12 | context: service/email 13 | - image: productcatalog 14 | context: service/productcatalog 15 | - image: recommendation 16 | context: service/recommendation 17 | - image: shipping 18 | context: service/shipping 19 | - image: checkout 20 | context: service/checkout 21 | - image: payment 22 | context: service/payment 23 | - image: currency 24 | context: service/currency 25 | - image: cart 26 | context: service/cart 27 | docker: 28 | dockerfile: Dockerfile 29 | - image: frontend 30 | context: service/frontend 31 | - image: ad 32 | context: service/ad 33 | tagPolicy: 34 | gitCommit: {} 35 | local: 36 | useBuildkit: false 37 | concurrency: 10 38 | deploy: 39 | kubectl: 40 | manifests: 41 | - ./infra/k8s/ad.yaml 42 | - ./infra/k8s/cart.yaml 43 | - ./infra/k8s/checkout.yaml 44 | - ./infra/k8s/currency.yaml 45 | - ./infra/k8s/email.yaml 46 | - ./infra/k8s/frontend.yaml 47 | - ./infra/k8s/payment.yaml 48 | - ./infra/k8s/productcatalog.yaml 49 | - ./infra/k8s/recommendation.yaml 50 | - ./infra/k8s/shipping.yaml 51 | - ./infra/k8s/gomicro.yaml 52 | # profiles: 53 | # # "debug" profile replaces the default Dockerfile in cart with Dockerfile.debug, 54 | # # which enables debugging via skaffold. 55 | # # 56 | # # This profile is used by default when running skaffold debug. 57 | # - name: debug 58 | # activation: 59 | # - command: debug 60 | # patches: 61 | # - op: replace 62 | # path: /build/artifacts/7/docker/dockerfile 63 | # value: Dockerfile.debug 64 | 65 | --- 66 | apiVersion: skaffold/v2beta27 67 | kind: Config 68 | metadata: 69 | name: loadgenerator 70 | requires: 71 | - configs: [hipstershop] 72 | build: 73 | artifacts: 74 | - image: loadgenerator 75 | context: service/loadgenerator 76 | deploy: 77 | kubectl: 78 | manifests: 79 | - ./infra/k8s/loadgenerator.yaml 80 | --------------------------------------------------------------------------------