├── .gitignore ├── LICENSE ├── README.md ├── Tiltfile_golang ├── Tiltfile_micronaut ├── Tiltfile_quarkus ├── grpc-beer-client ├── Dockerfile ├── Makefile ├── README.md ├── buf.gen.yaml ├── buf.yaml ├── go.mod ├── go.sum ├── k8s │ └── cronjob.yaml ├── main.go └── proto │ ├── beer.pb.go │ ├── beer.proto │ └── beer_grpc.pb.go ├── grpc-beer-envoy ├── Dockerfile ├── Makefile ├── README.md ├── envoy.yaml └── k8s │ ├── deployment.yaml │ └── service.yaml ├── grpc-beer-gateway ├── Dockerfile ├── Makefile ├── buf.gen.yaml ├── buf.yaml ├── go.mod ├── go.sum ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── main.go ├── openapiv2 │ ├── beer.swagger.json │ ├── google │ │ └── api │ │ │ ├── annotations.swagger.json │ │ │ └── http.swagger.json │ └── protoc-gen-openapiv2 │ │ └── options │ │ ├── annotations.swagger.json │ │ └── openapiv2.swagger.json ├── proto │ ├── beer.pb.go │ ├── beer.pb.gw.go │ ├── beer.proto │ ├── beer_grpc.pb.go │ ├── google │ │ └── api │ │ │ ├── annotations.pb.go │ │ │ ├── annotations.proto │ │ │ ├── http.pb.go │ │ │ └── http.proto │ └── protoc-gen-openapiv2 │ │ └── options │ │ ├── annotations.pb.go │ │ ├── annotations.proto │ │ ├── openapiv2.pb.go │ │ └── openapiv2.proto └── tools.go ├── grpc-beer-javascript ├── .gitignore ├── Makefile ├── README.md ├── beer_client.js ├── beer_grpc_web_pb.d.ts ├── beer_grpc_web_pb.js ├── beer_pb.d.ts ├── beer_pb.js ├── buf.gen.yaml ├── buf.yaml ├── dist │ └── main.js ├── index.html ├── package-lock.json ├── package.json ├── proto │ └── beer.proto └── webpack.config.js ├── grpc-beer-nginx ├── Dockerfile ├── k8s │ ├── deployment.yaml │ └── service.yaml └── nginx.conf ├── grpc-beer-service ├── Dockerfile ├── Makefile ├── README.md ├── beer.go ├── buf.gen.yaml ├── buf.yaml ├── go.mod ├── go.sum ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── main.go ├── proto │ ├── beer.pb.go │ ├── beer.proto │ └── beer_grpc.pb.go └── server.go ├── grpc-beer-ui └── k8s │ ├── deployment.yaml │ └── service.yaml ├── micronaut-beer-grpc ├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── micronaut-cli.yml ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── hands │ │ │ └── on │ │ │ └── grpc │ │ │ ├── Application.java │ │ │ └── BeerGrpcService.java │ ├── proto │ │ └── beer.proto │ └── resources │ │ ├── application.yml │ │ └── simplelogger.properties │ └── test │ └── java │ └── hands │ └── on │ └── grpc │ └── MicronautBeerGrpcTest.java ├── micronaut-beer-rest ├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── micronaut-cli.yml ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── hands │ │ │ └── on │ │ │ └── grpc │ │ │ ├── Application.java │ │ │ ├── Beer.java │ │ │ ├── BeerController.java │ │ │ ├── BeerRepository.java │ │ │ └── ProtoBeerController.java │ ├── proto │ │ └── beer.proto │ └── resources │ │ ├── application.yml │ │ └── logback.xml │ └── test │ └── java │ └── hands │ └── on │ └── grpc │ ├── BeerControllerTest.java │ ├── MicronautBeerRestTest.java │ └── ProtoBeerControllerTest.java ├── node-beer-graphql ├── .gitignore ├── beer-1.0.graphql ├── package-lock.json ├── package.json └── src │ └── server.js ├── quarkus-beer-grpc ├── .dockerignore ├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── settings.gradle └── src │ ├── main │ ├── docker │ │ ├── Dockerfile.jvm │ │ ├── Dockerfile.legacy-jar │ │ ├── Dockerfile.native │ │ └── Dockerfile.native-micro │ ├── java │ │ └── hands │ │ │ └── on │ │ │ └── grpc │ │ │ ├── BeerGrpcService.java │ │ │ ├── HelloGrpcService.java │ │ │ └── HelloResource.java │ ├── proto │ │ ├── beer.proto │ │ └── hello.proto │ └── resources │ │ ├── META-INF │ │ └── resources │ │ │ └── index.html │ │ └── application.properties │ ├── native-test │ └── java │ │ └── hands │ │ └── on │ │ └── grpc │ │ └── NativeHelloResourceIT.java │ └── test │ └── java │ └── hands │ └── on │ └── grpc │ ├── BeerGrpcServiceTest.java │ ├── HelloGrpcServiceTest.java │ └── HelloResourceTest.java ├── quarkus-beer-rest ├── .dockerignore ├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── settings.gradle └── src │ ├── main │ ├── docker │ │ ├── Dockerfile.jvm │ │ ├── Dockerfile.legacy-jar │ │ ├── Dockerfile.native │ │ └── Dockerfile.native-micro │ ├── java │ │ └── hands │ │ │ └── on │ │ │ └── grpc │ │ │ ├── Beer.java │ │ │ ├── BeerApplication.java │ │ │ ├── BeerRepository.java │ │ │ ├── BeerResource.java │ │ │ ├── ProtoMapper.java │ │ │ ├── ProtoResource.java │ │ │ └── protobuf │ │ │ ├── InvalidProtocolBufferExceptionMapper.java │ │ │ ├── ProtocolBufferMediaType.java │ │ │ ├── ProtocolBufferMessageBodyReader.java │ │ │ └── ProtocolBufferMessageBodyWriter.java │ ├── proto │ │ └── beer.proto │ └── resources │ │ ├── META-INF │ │ └── resources │ │ │ └── index.html │ │ └── application.properties │ ├── native-test │ └── java │ │ └── hands │ │ └── on │ │ └── grpc │ │ └── NativeBeerResourceIT.java │ └── test │ └── java │ └── hands │ └── on │ └── grpc │ ├── BeerResourceTest.java │ ├── ProtoMapperTest.java │ └── ProtoResourceTest.java ├── rest-beer-client ├── Beer-Service.postman_collection.json ├── README.md └── august.json ├── rest-beer-golang ├── Dockerfile ├── Makefile ├── README.md ├── beer.go ├── go.mod ├── k8s │ ├── deployment.yaml │ └── service.yaml └── main.go ├── rest-beer-service ├── Dockerfile ├── Makefile ├── README.md ├── beer.go ├── buf.gen.yaml ├── buf.yaml ├── favicon.ico ├── go.mod ├── go.sum ├── k8s │ ├── deployment.yaml │ └── service.yaml ├── main.go ├── proto │ ├── beer.pb.go │ └── beer.proto └── templates │ └── index.html ├── skaffold_golang.yaml ├── skaffold_micronaut.yaml └── skaffold_quarkus.yaml /.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 | .dccache 18 | .vscode/ 19 | .idea/ 20 | .gradle/ 21 | build/ 22 | bin/ 23 | 24 | rest-beer-golang/rest-beer-golang 25 | rest-beer-service/rest-beer-service 26 | rest-beer-client/rest-beer-client 27 | grpc-beer-client/grpc-beer-client 28 | grpc-beer-gateway/grpc-beer-gateway 29 | grpc-beer-service/grpc-beer-service 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 M.-Leander Reimer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REST in Peace. Long live gRCP! 2 | 3 | Demo repository for different REST to gRPC showcases. It contains several different service implementations as well as matching clients. 4 | 5 | ## Running 6 | 7 | ```shell 8 | # this repo contains several different implementations, choose one 9 | 10 | # use Skaffold to spin up the services 11 | skaffold dev --no-prune=false --cache-artifacts=false -f skaffold_quarkus 12 | skaffold dev --no-prune=false --cache-artifacts=false -f skaffold_micronaut 13 | skaffold dev --no-prune=false --cache-artifacts=false -f skaffold_golang 14 | 15 | # use Tilt to spin up the services 16 | tilt up -f Tiltfile_quarkus 17 | tilt up -f Tiltfile_micronaut 18 | tilt up -f Tiltfile_golang 19 | ``` 20 | 21 | ## Usage 22 | 23 | ```shell 24 | # call the plain REST service endpoint 25 | http get localhost:18080/api/beers 26 | 27 | # call the gRPC service endpoint 28 | grpcurl -plaintext -proto micronaut-beer-grpc/src/main/proto/beer.proto localhost:19090 beer.BeerService/AllBeers 29 | 30 | # call the NGINX proxy for the gRPC endpoint 31 | grpcurl -plaintext -proto micronaut-beer-grpc/src/main/proto/beer.proto localhost:18888 beer.BeerService/AllBeers 32 | 33 | # call the gRPC gateway service endpoint 34 | http get localhost:18090/api/beers 35 | ``` 36 | 37 | ## Maintainer 38 | 39 | M.-Leander Reimer (@lreimer), 40 | 41 | ## License 42 | 43 | This software is provided under the MIT open source license, read the `LICENSE` 44 | file for details. 45 | -------------------------------------------------------------------------------- /Tiltfile_golang: -------------------------------------------------------------------------------- 1 | # -*- mode: Python -*- 2 | update_settings(suppress_unused_image_warnings=["lreimer/rest-beer-service", "lreimer/grpc-beer-service"]) 3 | 4 | # for the Golang beer services 5 | docker_build('lreimer/grpc-beer-service', './grpc-beer-service/', dockerfile='grpc-beer-service/Dockerfile') 6 | k8s_yaml(['grpc-beer-service/k8s/deployment.yaml', 'grpc-beer-service/k8s/service.yaml']) 7 | k8s_resource(workload='grpc-beer-service', port_forwards=[port_forward(19090, 9090, 'gRPC API')], labels=['Golang']) 8 | 9 | docker_build('lreimer/rest-beer-service', './rest-beer-service/', dockerfile='rest-beer-service/Dockerfile') 10 | k8s_yaml(['rest-beer-service/k8s/deployment.yaml', 'rest-beer-service/k8s/service.yaml']) 11 | k8s_resource(workload='rest-beer-service', port_forwards=[port_forward(18080, 8080, 'REST API')], labels=['Golang']) 12 | 13 | # the gRPC beer Gateway 14 | docker_build('lreimer/grpc-beer-gateway', './grpc-beer-gateway/', dockerfile='grpc-beer-gateway/Dockerfile') 15 | k8s_yaml(['grpc-beer-gateway/k8s/deployment.yaml', 'grpc-beer-gateway/k8s/service.yaml']) 16 | k8s_resource(workload='grpc-beer-gateway', port_forwards=[port_forward(18090, 8090, 'REST API')], labels=['gRPC']) 17 | 18 | # the gRPC beer Envoy 19 | docker_build('lreimer/grpc-beer-envoy', './grpc-beer-envoy/', dockerfile='grpc-beer-envoy/Dockerfile') 20 | k8s_yaml(['grpc-beer-envoy/k8s/deployment.yaml', 'grpc-beer-envoy/k8s/service.yaml']) 21 | k8s_resource(workload='grpc-beer-envoy', port_forwards=[port_forward(18091, 8091, 'gRPC Web')], labels=['gRPC']) 22 | 23 | # the gRPC beer Nginx 24 | docker_build('lreimer/grpc-beer-nginx', './grpc-beer-nginx/', dockerfile='grpc-beer-nginx/Dockerfile') 25 | k8s_yaml(['grpc-beer-nginx/k8s/deployment.yaml', 'grpc-beer-nginx/k8s/service.yaml']) 26 | k8s_resource(workload='grpc-beer-nginx', port_forwards=[port_forward(18888, 8888, 'gRPC Proxy')], labels=['gRPC']) 27 | 28 | # the gRPC beer UI 29 | k8s_yaml(['grpc-beer-ui/k8s/deployment.yaml', 'grpc-beer-ui/k8s/service.yaml']) 30 | k8s_resource(workload='grpc-beer-ui', port_forwards=[port_forward(16969, 6969, 'gRPC UI')], labels=['gRPC']) 31 | -------------------------------------------------------------------------------- /Tiltfile_micronaut: -------------------------------------------------------------------------------- 1 | # -*- mode: Python -*- 2 | update_settings(suppress_unused_image_warnings=["lreimer/micronaut-beer-grpc"]) 3 | 4 | # for the Micronaut beer service 5 | custom_build('lreimer/micronaut-beer-grpc', 'cd micronaut-beer-grpc && ./gradlew jibDockerBuild --image $EXPECTED_REF', 6 | ['./micronaut-beer-grpc/build.gradle', './micronaut-beer-grpc/src/']) 7 | k8s_yaml(['micronaut-beer-grpc/k8s/deployment.yaml', 'micronaut-beer-grpc/k8s/service.yaml']) 8 | k8s_resource(workload='grpc-beer-service', port_forwards=[port_forward(19090, 9090, 'gRPC API')], labels=['Micronaut']) 9 | 10 | custom_build('lreimer/micronaut-beer-rest', 'cd micronaut-beer-rest && ./gradlew jibDockerBuild --image $EXPECTED_REF', 11 | ['./micronaut-beer-rest/build.gradle', './micronaut-beer-rest/src/']) 12 | k8s_yaml(['micronaut-beer-rest/k8s/deployment.yaml', 'micronaut-beer-rest/k8s/service.yaml']) 13 | k8s_resource(workload='rest-beer-service', port_forwards=[port_forward(18080, 8080, 'REST API')], labels=['Micronaut']) 14 | 15 | # the gRPC beer Gateway 16 | docker_build('lreimer/grpc-beer-gateway', './grpc-beer-gateway/', dockerfile='grpc-beer-gateway/Dockerfile') 17 | k8s_yaml(['grpc-beer-gateway/k8s/deployment.yaml', 'grpc-beer-gateway/k8s/service.yaml']) 18 | k8s_resource(workload='grpc-beer-gateway', port_forwards=[port_forward(18090, 8090, 'REST API')], labels=['gRPC']) 19 | 20 | # the gRPC beer Envoy 21 | docker_build('lreimer/grpc-beer-envoy', './grpc-beer-envoy/', dockerfile='grpc-beer-envoy/Dockerfile') 22 | k8s_yaml(['grpc-beer-envoy/k8s/deployment.yaml', 'grpc-beer-envoy/k8s/service.yaml']) 23 | k8s_resource(workload='grpc-beer-envoy', port_forwards=[port_forward(18091, 8091, 'gRPC Web')], labels=['gRPC']) 24 | 25 | # the gRPC beer Nginx 26 | docker_build('lreimer/grpc-beer-nginx', './grpc-beer-nginx/', dockerfile='grpc-beer-nginx/Dockerfile') 27 | k8s_yaml(['grpc-beer-nginx/k8s/deployment.yaml', 'grpc-beer-nginx/k8s/service.yaml']) 28 | k8s_resource(workload='grpc-beer-nginx', port_forwards=[port_forward(18888, 8888, 'gRPC Proxy')], labels=['gRPC']) 29 | 30 | # the gRPC beer UI 31 | k8s_yaml(['grpc-beer-ui/k8s/deployment.yaml', 'grpc-beer-ui/k8s/service.yaml']) 32 | k8s_resource(workload='grpc-beer-ui', port_forwards=[port_forward(16969, 6969, 'gRPC UI')], labels=['gRPC']) 33 | -------------------------------------------------------------------------------- /Tiltfile_quarkus: -------------------------------------------------------------------------------- 1 | # -*- mode: Python -*- 2 | update_settings(suppress_unused_image_warnings=["lreimer/quarkus-beer-grpc"]) 3 | 4 | # for the Quarkus beer services 5 | local_resource('quarkus-beer-grpc-build', './gradlew assemble', dir='quarkus-beer-grpc', 6 | deps=['./quarkus-beer-grpc/build.gradle', './quarkus-beer-grpc/src/'], labels=['Quarkus']) 7 | docker_build('lreimer/quarkus-beer-grpc', './quarkus-beer-grpc/', 8 | dockerfile='quarkus-beer-grpc/src/main/docker/Dockerfile.jvm', only=['./build/']) 9 | 10 | k8s_yaml(['quarkus-beer-grpc/k8s/deployment.yaml', 'quarkus-beer-grpc/k8s/service.yaml']) 11 | k8s_resource(workload='grpc-beer-service', port_forwards=[port_forward(19090, 9090, 'gRPC API')], labels=['Quarkus']) 12 | 13 | local_resource('quarkus-beer-rest-build', './gradlew assemble', dir='quarkus-beer-rest', 14 | deps=['./quarkus-beer-rest/build.gradle', './quarkus-beer-rest/src/'], labels=['Quarkus']) 15 | docker_build('lreimer/quarkus-beer-rest', './quarkus-beer-rest/', 16 | dockerfile='quarkus-beer-rest/src/main/docker/Dockerfile.jvm', only=['./build/']) 17 | 18 | k8s_yaml(['quarkus-beer-rest/k8s/deployment.yaml', 'quarkus-beer-rest/k8s/service.yaml']) 19 | k8s_resource(workload='rest-beer-service', port_forwards=[port_forward(18080, 8080, 'REST API')], labels=['Quarkus']) 20 | 21 | # the gRPC beer Gateway 22 | docker_build('lreimer/grpc-beer-gateway', './grpc-beer-gateway/', dockerfile='grpc-beer-gateway/Dockerfile') 23 | k8s_yaml(['grpc-beer-gateway/k8s/deployment.yaml', 'grpc-beer-gateway/k8s/service.yaml']) 24 | k8s_resource(workload='grpc-beer-gateway', port_forwards=[port_forward(18090, 8090, 'REST API')], labels=['gRPC']) 25 | 26 | # the gRPC beer Envoy 27 | docker_build('lreimer/grpc-beer-envoy', './grpc-beer-envoy/', dockerfile='grpc-beer-envoy/Dockerfile') 28 | k8s_yaml(['grpc-beer-envoy/k8s/deployment.yaml', 'grpc-beer-envoy/k8s/service.yaml']) 29 | k8s_resource(workload='grpc-beer-envoy', port_forwards=[port_forward(18091, 8091, 'gRPC Web')], labels=['gRPC']) 30 | 31 | # the gRPC beer Nginx 32 | docker_build('lreimer/grpc-beer-nginx', './grpc-beer-nginx/', dockerfile='grpc-beer-nginx/Dockerfile') 33 | k8s_yaml(['grpc-beer-nginx/k8s/deployment.yaml', 'grpc-beer-nginx/k8s/service.yaml']) 34 | k8s_resource(workload='grpc-beer-nginx', port_forwards=[port_forward(18888, 8888, 'gRPC Proxy')], labels=['gRPC']) 35 | 36 | # the gRPC beer UI 37 | k8s_yaml(['grpc-beer-ui/k8s/deployment.yaml', 'grpc-beer-ui/k8s/service.yaml']) 38 | k8s_resource(workload='grpc-beer-ui', port_forwards=[port_forward(16969, 6969, 'gRPC UI')], labels=['gRPC']) 39 | -------------------------------------------------------------------------------- /grpc-beer-client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17-bullseye as build 2 | 3 | WORKDIR /go/src/app 4 | ADD . /go/src/app 5 | 6 | RUN go get -d -v ./... 7 | RUN go build -o /go/bin/grpc-beer-client 8 | 9 | FROM gcr.io/distroless/base-debian11 10 | 11 | ENV HOST=grpc-beer-service 12 | ENV PORT=9090 13 | 14 | COPY --from=build /go/bin/grpc-beer-client / 15 | 16 | CMD ["/grpc-beer-client"] 17 | 18 | -------------------------------------------------------------------------------- /grpc-beer-client/Makefile: -------------------------------------------------------------------------------- 1 | NAME = grpc-beer-client 2 | 3 | default: build 4 | 5 | image: 6 | @docker build -t lreimer/$(NAME) . 7 | 8 | build: 9 | @buf generate 10 | @go build 11 | 12 | clean: 13 | @rm -f $(NAME) -------------------------------------------------------------------------------- /grpc-beer-client/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lreimer/from-rest-to-grpc/4bee9f8579ad3f9802e5b618ec35351976c0d3d2/grpc-beer-client/README.md -------------------------------------------------------------------------------- /grpc-beer-client/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | plugins: 3 | - name: go 4 | out: proto 5 | opt: paths=source_relative 6 | - name: go-grpc 7 | out: proto 8 | opt: paths=source_relative -------------------------------------------------------------------------------- /grpc-beer-client/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | name: github.com/lreimer/from-rest-to-grpc 3 | deps: 4 | - buf.build/beta/googleapis 5 | build: 6 | roots: 7 | - proto 8 | -------------------------------------------------------------------------------- /grpc-beer-client/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lreimer/from-rest-to-grpc/grpc-beer-client 2 | 3 | go 1.17 4 | 5 | require ( 6 | google.golang.org/grpc v1.46.0 7 | google.golang.org/protobuf v1.28.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.2 // indirect 12 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect 13 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect 14 | golang.org/x/text v0.3.7 // indirect 15 | google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /grpc-beer-client/k8s/cronjob.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: CronJob 3 | metadata: 4 | name: grpc-beer-client 5 | spec: 6 | schedule: "*/1 * * * *" 7 | jobTemplate: 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: grpc-beer-client 13 | image: lreimer/grpc-beer-client 14 | resources: 15 | requests: 16 | memory: "64Mi" 17 | cpu: "100m" 18 | limits: 19 | memory: "128Mi" 20 | cpu: "250m" 21 | env: 22 | - name: HOST 23 | value: "grpc-beer-service" 24 | - name: PORT 25 | value: "9090" 26 | restartPolicy: OnFailure -------------------------------------------------------------------------------- /grpc-beer-client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | pb "github.com/lreimer/from-rest-to-grpc/grpc-beer-client/proto" 10 | "google.golang.org/grpc" 11 | "google.golang.org/protobuf/types/known/emptypb" 12 | ) 13 | 14 | func main() { 15 | // Set up a connection to the server. 16 | conn, err := grpc.Dial(address(), grpc.WithInsecure()) 17 | if err != nil { 18 | log.Fatalf("Could not connect to gRPC beer service: %v", err) 19 | } 20 | defer conn.Close() 21 | 22 | client := pb.NewBeerServiceClient(conn) 23 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 24 | defer cancel() 25 | 26 | // get list of all beers 27 | log.Printf("Getting list of all beers") 28 | beers, err := client.AllBeers(ctx, &emptypb.Empty{}) 29 | if err != nil { 30 | log.Fatalf("Could not get all beers: %v", err) 31 | } 32 | log.Printf("Beers %s", beers.GetBeers()) 33 | 34 | // create a new beer 35 | log.Printf("Creating new beer") 36 | _, err = client.CreateBeer(ctx, &pb.CreateBeerRequest{Beer: &pb.Beer{ 37 | Asin: "B01CJPU3XW", 38 | Name: "Blauer August", 39 | Brand: "Augustiner Brauerei München", 40 | Country: "Germany", 41 | Alcohol: 5.2, 42 | Type: pb.Beer_Lager, 43 | }}) 44 | if err != nil { 45 | log.Fatalf("Could not create new beer: %v", err) 46 | } 47 | 48 | // get the new beer 49 | log.Printf("Getting new beer") 50 | beer, err := client.GetBeer(ctx, &pb.GetBeerRequest{Asin: "B01CJPU3XW"}) 51 | if err != nil { 52 | log.Fatalf("Could not get beer: %v", err) 53 | } 54 | log.Printf("Beer %s", beer.GetBeer()) 55 | 56 | // delete the new beer 57 | log.Printf("Deleting new beer") 58 | _, err = client.DeleteBeer(ctx, &pb.DeleteBeerRequest{Asin: "B01CJPU3XW"}) 59 | if err != nil { 60 | log.Fatalf("Could not delete beer: %v", err) 61 | } 62 | } 63 | 64 | func address() string { 65 | host := os.Getenv("HOST") 66 | if len(host) == 0 { 67 | host = "localhost" 68 | } 69 | 70 | port := os.Getenv("PORT") 71 | if len(port) == 0 { 72 | port = "9090" 73 | } 74 | 75 | return host + ":" + port 76 | } 77 | -------------------------------------------------------------------------------- /grpc-beer-client/proto/beer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/lreimer/from-rest-to-grpc/grpc-beer-client/proto"; 4 | 5 | import "google/protobuf/empty.proto"; 6 | package beer; 7 | 8 | service BeerService { 9 | // Get the list of all beers 10 | rpc AllBeers (google.protobuf.Empty) returns (GetBeersResponse) {} 11 | // Get a single beer by Asin 12 | rpc GetBeer (GetBeerRequest) returns (GetBeerResponse) {} 13 | // Create a new beer 14 | rpc CreateBeer (CreateBeerRequest) returns (google.protobuf.Empty) {} 15 | // Update an existing beer 16 | rpc UpdateBeer (UpdateBeerRequest) returns (google.protobuf.Empty) {} 17 | // Delete an existing beeer 18 | rpc DeleteBeer (DeleteBeerRequest) returns (google.protobuf.Empty) {} 19 | } 20 | 21 | message Beer { 22 | string asin = 1; 23 | string name = 2; 24 | string brand = 3; 25 | string country = 4; 26 | float alcohol = 5; 27 | enum BeerType{ 28 | IndianPaleAle = 0; 29 | SessionIpa = 1; 30 | Lager = 2; 31 | } 32 | BeerType type = 6; 33 | } 34 | 35 | message GetBeersResponse { 36 | repeated Beer beers = 1; 37 | } 38 | 39 | message GetBeerRequest { 40 | string asin = 1; 41 | } 42 | 43 | message GetBeerResponse { 44 | Beer beer = 1; 45 | } 46 | 47 | message CreateBeerRequest { 48 | Beer beer = 1; 49 | } 50 | 51 | message UpdateBeerRequest { 52 | string asin = 1; 53 | Beer beer = 2; 54 | } 55 | 56 | message DeleteBeerRequest { 57 | string asin = 1; 58 | } -------------------------------------------------------------------------------- /grpc-beer-envoy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM envoyproxy/envoy:v1.17.0 2 | EXPOSE 8091 3 | COPY envoy.yaml /etc/envoy/envoy.yaml 4 | CMD ["/usr/local/bin/envoy", "-c", "/etc/envoy/envoy.yaml", "-l", "trace", "--log-path", "/tmp/envoy_info.log"] -------------------------------------------------------------------------------- /grpc-beer-envoy/Makefile: -------------------------------------------------------------------------------- 1 | NAME = grpc-beer-envoy 2 | 3 | default: image 4 | 5 | image: 6 | @docker build -t lreimer/$(NAME) . 7 | -------------------------------------------------------------------------------- /grpc-beer-envoy/README.md: -------------------------------------------------------------------------------- 1 | # gRPC Web Proxy with Envoy 2 | -------------------------------------------------------------------------------- /grpc-beer-envoy/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: listener_0 4 | address: 5 | socket_address: { address: 0.0.0.0, port_value: 8091 } 6 | filter_chains: 7 | - filters: 8 | - name: envoy.filters.network.http_connection_manager 9 | typed_config: 10 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 11 | codec_type: auto 12 | stat_prefix: ingress_http 13 | route_config: 14 | name: local_route 15 | virtual_hosts: 16 | - name: local_service 17 | domains: ["*"] 18 | routes: 19 | - match: { prefix: "/" } 20 | route: 21 | cluster: beer_service 22 | max_stream_duration: 23 | grpc_timeout_header_max: 0s 24 | cors: 25 | allow_origin_string_match: 26 | - prefix: "*" 27 | allow_methods: GET, PUT, DELETE, POST, OPTIONS 28 | allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout 29 | max_age: "1728000" 30 | expose_headers: grpc-status,grpc-message 31 | http_filters: 32 | - name: envoy.filters.http.grpc_web 33 | - name: envoy.filters.http.cors 34 | - name: envoy.filters.http.router 35 | clusters: 36 | - name: beer_service 37 | connect_timeout: 0.25s 38 | type: logical_dns 39 | http2_protocol_options: {} 40 | lb_policy: round_robin 41 | load_assignment: 42 | cluster_name: cluster_0 43 | endpoints: 44 | - lb_endpoints: 45 | - endpoint: 46 | address: 47 | socket_address: 48 | address: grpc-beer-service 49 | port_value: 9090 50 | -------------------------------------------------------------------------------- /grpc-beer-envoy/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grpc-beer-envoy 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: grpc-beer-envoy 10 | template: 11 | metadata: 12 | labels: 13 | app: grpc-beer-envoy 14 | spec: 15 | containers: 16 | - name: grpc-beer-envoy 17 | image: lreimer/grpc-beer-envoy 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "100m" 22 | limits: 23 | memory: "128Mi" 24 | cpu: "250m" 25 | ports: 26 | - name: http 27 | containerPort: 8091 -------------------------------------------------------------------------------- /grpc-beer-envoy/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grpc-beer-envoy 5 | spec: 6 | selector: 7 | app: grpc-beer-envoy 8 | type: ClusterIP 9 | sessionAffinity: None 10 | ports: 11 | - protocol: TCP 12 | port: 8091 13 | targetPort: http -------------------------------------------------------------------------------- /grpc-beer-gateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17-bullseye as build 2 | 3 | WORKDIR /go/src/app 4 | ADD . /go/src/app 5 | 6 | RUN go get -d -v ./... 7 | RUN go build -o /go/bin/grpc-beer-gateway 8 | 9 | FROM gcr.io/distroless/base-debian11 10 | 11 | ENV GRPC_SERVER_ENDPOINT=grpc-beer-service:9090 12 | ENV PORT=8090 13 | 14 | COPY --from=build /go/bin/grpc-beer-gateway / 15 | 16 | CMD ["/grpc-beer-gateway"] 17 | -------------------------------------------------------------------------------- /grpc-beer-gateway/Makefile: -------------------------------------------------------------------------------- 1 | NAME = grpc-beer-gateway 2 | 3 | default: build 4 | 5 | image: 6 | @docker build -t lreimer/$(NAME) . 7 | 8 | build: 9 | @buf generate 10 | @go build 11 | 12 | clean: 13 | @rm -f $(NAME) -------------------------------------------------------------------------------- /grpc-beer-gateway/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | plugins: 3 | - name: go 4 | out: proto 5 | opt: paths=source_relative 6 | - name: go-grpc 7 | out: proto 8 | opt: paths=source_relative 9 | - name: grpc-gateway 10 | out: proto 11 | opt: 12 | - paths=source_relative 13 | - generate_unbound_methods=true 14 | - name: openapiv2 15 | out: openapiv2 -------------------------------------------------------------------------------- /grpc-beer-gateway/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | name: github.com/lreimer/from-rest-to-grpc 3 | deps: 4 | - buf.build/beta/googleapis 5 | - buf.build/grpc-ecosystem/grpc-gateway 6 | build: 7 | roots: 8 | - proto 9 | -------------------------------------------------------------------------------- /grpc-beer-gateway/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lreimer/from-rest-to-grpc/grpc-beer-gateway 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/golang/glog v1.0.0 7 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 8 | google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 9 | google.golang.org/grpc v1.42.0 10 | google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 11 | google.golang.org/protobuf v1.27.1 12 | ) 13 | 14 | require ( 15 | github.com/ghodss/yaml v1.0.0 // indirect 16 | github.com/golang/protobuf v1.5.2 // indirect 17 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect 18 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect 19 | golang.org/x/text v0.3.5 // indirect 20 | gopkg.in/yaml.v2 v2.2.3 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /grpc-beer-gateway/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grpc-beer-gateway 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: grpc-beer-gateway 10 | template: 11 | metadata: 12 | labels: 13 | app: grpc-beer-gateway 14 | spec: 15 | containers: 16 | - name: grpc-beer-gateway 17 | image: lreimer/grpc-beer-gateway 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "100m" 22 | limits: 23 | memory: "128Mi" 24 | cpu: "250m" 25 | env: 26 | - name: GRPC_SERVER_ENDPOINT 27 | value: "grpc-beer-service:9090" 28 | - name: PORT 29 | value: "8090" 30 | ports: 31 | - name: http 32 | containerPort: 8090 -------------------------------------------------------------------------------- /grpc-beer-gateway/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grpc-beer-gateway 5 | spec: 6 | selector: 7 | app: grpc-beer-gateway 8 | type: ClusterIP 9 | sessionAffinity: None 10 | ports: 11 | - protocol: TCP 12 | port: 8090 13 | targetPort: http -------------------------------------------------------------------------------- /grpc-beer-gateway/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/golang/glog" 9 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 10 | "google.golang.org/grpc" 11 | 12 | gw "github.com/lreimer/from-rest-to-grpc/grpc-beer-gateway/proto" 13 | ) 14 | 15 | func main() { 16 | defer glog.Flush() 17 | 18 | if err := run(); err != nil { 19 | glog.Fatal(err) 20 | } 21 | } 22 | 23 | func run() error { 24 | ctx := context.Background() 25 | ctx, cancel := context.WithCancel(ctx) 26 | defer cancel() 27 | 28 | // Register gRPC server endpoint 29 | // Note: Make sure the gRPC server is running properly and accessible 30 | mux := runtime.NewServeMux() 31 | opts := []grpc.DialOption{grpc.WithInsecure()} 32 | err := gw.RegisterBeerServiceHandlerFromEndpoint(ctx, mux, grpcServerEndpoint(), opts) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | // Start HTTP server (and proxy calls to gRPC server endpoint) 38 | return http.ListenAndServe(port(), mux) 39 | } 40 | 41 | func port() string { 42 | port := os.Getenv("PORT") 43 | if len(port) == 0 { 44 | port = "8090" 45 | } 46 | return ":" + port 47 | } 48 | 49 | func grpcServerEndpoint() string { 50 | endpoint := os.Getenv("GRPC_SERVER_ENDPOINT") 51 | if len(endpoint) == 0 { 52 | endpoint = "localhost:9090" 53 | } 54 | return endpoint 55 | } 56 | -------------------------------------------------------------------------------- /grpc-beer-gateway/openapiv2/google/api/annotations.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "google/api/annotations.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "typeUrl": { 19 | "type": "string" 20 | }, 21 | "value": { 22 | "type": "string", 23 | "format": "byte" 24 | } 25 | } 26 | }, 27 | "rpcStatus": { 28 | "type": "object", 29 | "properties": { 30 | "code": { 31 | "type": "integer", 32 | "format": "int32" 33 | }, 34 | "message": { 35 | "type": "string" 36 | }, 37 | "details": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "#/definitions/protobufAny" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /grpc-beer-gateway/openapiv2/google/api/http.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "google/api/http.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "typeUrl": { 19 | "type": "string" 20 | }, 21 | "value": { 22 | "type": "string", 23 | "format": "byte" 24 | } 25 | } 26 | }, 27 | "rpcStatus": { 28 | "type": "object", 29 | "properties": { 30 | "code": { 31 | "type": "integer", 32 | "format": "int32" 33 | }, 34 | "message": { 35 | "type": "string" 36 | }, 37 | "details": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "#/definitions/protobufAny" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /grpc-beer-gateway/openapiv2/protoc-gen-openapiv2/options/annotations.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "protoc-gen-openapiv2/options/annotations.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "typeUrl": { 19 | "type": "string" 20 | }, 21 | "value": { 22 | "type": "string", 23 | "format": "byte" 24 | } 25 | } 26 | }, 27 | "rpcStatus": { 28 | "type": "object", 29 | "properties": { 30 | "code": { 31 | "type": "integer", 32 | "format": "int32" 33 | }, 34 | "message": { 35 | "type": "string" 36 | }, 37 | "details": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "#/definitions/protobufAny" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /grpc-beer-gateway/openapiv2/protoc-gen-openapiv2/options/openapiv2.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "protoc-gen-openapiv2/options/openapiv2.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "typeUrl": { 19 | "type": "string" 20 | }, 21 | "value": { 22 | "type": "string", 23 | "format": "byte" 24 | } 25 | } 26 | }, 27 | "rpcStatus": { 28 | "type": "object", 29 | "properties": { 30 | "code": { 31 | "type": "integer", 32 | "format": "int32" 33 | }, 34 | "message": { 35 | "type": "string" 36 | }, 37 | "details": { 38 | "type": "array", 39 | "items": { 40 | "$ref": "#/definitions/protobufAny" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /grpc-beer-gateway/proto/beer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/lreimer/from-rest-to-grpc/grpc-beer-gateway/proto"; 4 | 5 | import "google/protobuf/empty.proto"; 6 | import "google/api/annotations.proto"; 7 | import "protoc-gen-openapiv2/options/annotations.proto"; 8 | 9 | package beer; 10 | 11 | service BeerService { 12 | // Get the list of all beers 13 | rpc AllBeers (google.protobuf.Empty) returns (GetBeersResponse) { 14 | option (google.api.http) = { 15 | get: "/api/beers" 16 | }; 17 | } 18 | // Get a single beer by Asin 19 | rpc GetBeer (GetBeerRequest) returns (GetBeerResponse) { 20 | option (google.api.http) = { 21 | get: "/api/beers/{asin}" 22 | response_body: "beer" 23 | }; 24 | } 25 | // Create a new beer 26 | rpc CreateBeer (CreateBeerRequest) returns (google.protobuf.Empty) { 27 | option (google.api.http) = { 28 | post: "/api/beers" 29 | body: "*" 30 | }; 31 | } 32 | // Update an existing beer 33 | rpc UpdateBeer (UpdateBeerRequest) returns (google.protobuf.Empty) { 34 | option (google.api.http) = { 35 | put: "/api/beers/{asin}" 36 | body: "beer" 37 | }; 38 | } 39 | // Delete an existing beeer 40 | rpc DeleteBeer (DeleteBeerRequest) returns (google.protobuf.Empty) { 41 | option (google.api.http) = { 42 | delete: "/api/beers/{asin}" 43 | }; 44 | } 45 | } 46 | 47 | option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { 48 | info: { 49 | title: "gRPC Beer Gateway"; 50 | version: "1.0"; 51 | contact: { 52 | name: "Mario-Leander Reimer"; 53 | url: "https://lreimer.github.io"; 54 | email: "mario-leander.reimer@qaware.de"; 55 | }; 56 | license: { 57 | name: "MIT"; 58 | url: "https://github.com/lreimer/from-rest-to-grpc/blob/master/LICENSE"; 59 | }; 60 | }; 61 | external_docs: { 62 | url: "https://github.com/lreimer/from-rest-to-grpc/grpc-beer-gateway"; 63 | description: "Beer Service gRPC Gateway"; 64 | } 65 | schemes: HTTP; 66 | schemes: HTTPS; 67 | consumes: "application/json"; 68 | produces: "application/json"; 69 | }; 70 | 71 | message Beer { 72 | string asin = 1; 73 | string name = 2; 74 | string brand = 3; 75 | string country = 4; 76 | float alcohol = 5; 77 | enum BeerType{ 78 | IndianPaleAle = 0; 79 | SessionIpa = 1; 80 | Lager = 2; 81 | } 82 | BeerType type = 6; 83 | } 84 | 85 | message GetBeersResponse { 86 | repeated Beer beers = 1; 87 | } 88 | 89 | message GetBeerRequest { 90 | string asin = 1; 91 | } 92 | 93 | message GetBeerResponse { 94 | Beer beer = 1; 95 | } 96 | 97 | message CreateBeerRequest { 98 | Beer beer = 1; 99 | } 100 | 101 | message UpdateBeerRequest { 102 | string asin = 1; 103 | Beer beer = 2; 104 | } 105 | 106 | message DeleteBeerRequest { 107 | string asin = 1; 108 | } -------------------------------------------------------------------------------- /grpc-beer-gateway/proto/google/api/annotations.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 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 | // Code generated by protoc-gen-go. DO NOT EDIT. 16 | // versions: 17 | // protoc-gen-go v1.27.1 18 | // protoc v3.17.3 19 | // source: google/api/annotations.proto 20 | 21 | package annotations 22 | 23 | import ( 24 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 25 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 26 | descriptorpb "google.golang.org/protobuf/types/descriptorpb" 27 | reflect "reflect" 28 | ) 29 | 30 | const ( 31 | // Verify that this generated code is sufficiently up-to-date. 32 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 33 | // Verify that runtime/protoimpl is sufficiently up-to-date. 34 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 35 | ) 36 | 37 | var file_google_api_annotations_proto_extTypes = []protoimpl.ExtensionInfo{ 38 | { 39 | ExtendedType: (*descriptorpb.MethodOptions)(nil), 40 | ExtensionType: (*HttpRule)(nil), 41 | Field: 72295728, 42 | Name: "google.api.http", 43 | Tag: "bytes,72295728,opt,name=http", 44 | Filename: "google/api/annotations.proto", 45 | }, 46 | } 47 | 48 | // Extension fields to descriptorpb.MethodOptions. 49 | var ( 50 | // See `HttpRule`. 51 | // 52 | // optional google.api.HttpRule http = 72295728; 53 | E_Http = &file_google_api_annotations_proto_extTypes[0] 54 | ) 55 | 56 | var File_google_api_annotations_proto protoreflect.FileDescriptor 57 | 58 | var file_google_api_annotations_proto_rawDesc = []byte{ 59 | 0x0a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 60 | 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 61 | 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x1a, 0x15, 0x67, 0x6f, 0x6f, 0x67, 62 | 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 63 | 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 64 | 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 65 | 0x6f, 0x74, 0x6f, 0x3a, 0x4b, 0x0a, 0x04, 0x68, 0x74, 0x74, 0x70, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 66 | 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 67 | 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xb0, 0xca, 0xbc, 0x22, 68 | 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, 69 | 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 70 | 0x42, 0x6e, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 71 | 0x70, 0x69, 0x42, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x50, 72 | 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x41, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, 73 | 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 74 | 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x61, 0x70, 75 | 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3b, 0x61, 0x6e, 76 | 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0xa2, 0x02, 0x04, 0x47, 0x41, 0x50, 0x49, 77 | 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 78 | } 79 | 80 | var file_google_api_annotations_proto_goTypes = []interface{}{ 81 | (*descriptorpb.MethodOptions)(nil), // 0: google.protobuf.MethodOptions 82 | (*HttpRule)(nil), // 1: google.api.HttpRule 83 | } 84 | var file_google_api_annotations_proto_depIdxs = []int32{ 85 | 0, // 0: google.api.http:extendee -> google.protobuf.MethodOptions 86 | 1, // 1: google.api.http:type_name -> google.api.HttpRule 87 | 2, // [2:2] is the sub-list for method output_type 88 | 2, // [2:2] is the sub-list for method input_type 89 | 1, // [1:2] is the sub-list for extension type_name 90 | 0, // [0:1] is the sub-list for extension extendee 91 | 0, // [0:0] is the sub-list for field type_name 92 | } 93 | 94 | func init() { file_google_api_annotations_proto_init() } 95 | func file_google_api_annotations_proto_init() { 96 | if File_google_api_annotations_proto != nil { 97 | return 98 | } 99 | file_google_api_http_proto_init() 100 | type x struct{} 101 | out := protoimpl.TypeBuilder{ 102 | File: protoimpl.DescBuilder{ 103 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 104 | RawDescriptor: file_google_api_annotations_proto_rawDesc, 105 | NumEnums: 0, 106 | NumMessages: 0, 107 | NumExtensions: 1, 108 | NumServices: 0, 109 | }, 110 | GoTypes: file_google_api_annotations_proto_goTypes, 111 | DependencyIndexes: file_google_api_annotations_proto_depIdxs, 112 | ExtensionInfos: file_google_api_annotations_proto_extTypes, 113 | }.Build() 114 | File_google_api_annotations_proto = out.File 115 | file_google_api_annotations_proto_rawDesc = nil 116 | file_google_api_annotations_proto_goTypes = nil 117 | file_google_api_annotations_proto_depIdxs = nil 118 | } 119 | -------------------------------------------------------------------------------- /grpc-beer-gateway/proto/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 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 google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } 32 | -------------------------------------------------------------------------------- /grpc-beer-gateway/proto/protoc-gen-openapiv2/options/annotations.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package grpc.gateway.protoc_gen_openapiv2.options; 4 | 5 | option go_package = "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"; 6 | 7 | import "google/protobuf/descriptor.proto"; 8 | import "protoc-gen-openapiv2/options/openapiv2.proto"; 9 | 10 | extend google.protobuf.FileOptions { 11 | // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. 12 | // 13 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 14 | // different descriptor messages. 15 | Swagger openapiv2_swagger = 1042; 16 | } 17 | extend google.protobuf.MethodOptions { 18 | // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. 19 | // 20 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 21 | // different descriptor messages. 22 | Operation openapiv2_operation = 1042; 23 | } 24 | extend google.protobuf.MessageOptions { 25 | // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. 26 | // 27 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 28 | // different descriptor messages. 29 | Schema openapiv2_schema = 1042; 30 | } 31 | extend google.protobuf.ServiceOptions { 32 | // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. 33 | // 34 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 35 | // different descriptor messages. 36 | Tag openapiv2_tag = 1042; 37 | } 38 | extend google.protobuf.FieldOptions { 39 | // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. 40 | // 41 | // All IDs are the same, as assigned. It is okay that they are the same, as they extend 42 | // different descriptor messages. 43 | JSONSchema openapiv2_field = 1042; 44 | } 45 | -------------------------------------------------------------------------------- /grpc-beer-gateway/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway" 8 | _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2" 9 | _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" 10 | _ "google.golang.org/protobuf/cmd/protoc-gen-go" 11 | ) 12 | -------------------------------------------------------------------------------- /grpc-beer-javascript/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /grpc-beer-javascript/Makefile: -------------------------------------------------------------------------------- 1 | NAME = grpc-beer-javascript 2 | 3 | default: build 4 | 5 | build: 6 | @buf generate 7 | -------------------------------------------------------------------------------- /grpc-beer-javascript/README.md: -------------------------------------------------------------------------------- 1 | # gRPC JavaScript Client Code 2 | -------------------------------------------------------------------------------- /grpc-beer-javascript/beer_client.js: -------------------------------------------------------------------------------- 1 | const {Empty} = require('./node_modules/google-protobuf/google/protobuf/empty_pb.js') 2 | const {GetBeerRequest, GetBeerResponse} = require('./beer_pb.js') 3 | const {BeerServiceClient} = require('./beer_grpc_web_pb.js') 4 | 5 | // use this if we run in browser 6 | // var beerClient = new BeerServiceClient('http://'+window.location.hostname+':18091') 7 | 8 | var beerClient = new BeerServiceClient('http://localhost:18091'); 9 | 10 | var empty = new Empty(); 11 | beerClient.allBeers(empty, {}, function(err, response){ 12 | if (err) { 13 | console.log(err.code); 14 | console.log(err.message); 15 | } else { 16 | console.log(response.getBeersList()); 17 | } 18 | }); 19 | 20 | var request = new GetBeerRequest(); 21 | request.setAsin('B079V9ZDNY') 22 | 23 | beerClient.getBeer(request, {}, function(err, response){ 24 | if (err) { 25 | console.log(err.code); 26 | console.log(err.message); 27 | } else { 28 | console.log(response.getBeer()); 29 | } 30 | }); -------------------------------------------------------------------------------- /grpc-beer-javascript/beer_grpc_web_pb.d.ts: -------------------------------------------------------------------------------- 1 | import * as grpcWeb from 'grpc-web'; 2 | 3 | import * as beer_pb from './beer_pb'; 4 | import * as google_protobuf_empty_pb from 'google-protobuf/google/protobuf/empty_pb'; 5 | 6 | 7 | export class BeerServiceClient { 8 | constructor (hostname: string, 9 | credentials?: null | { [index: string]: string; }, 10 | options?: null | { [index: string]: any; }); 11 | 12 | allBeers( 13 | request: google_protobuf_empty_pb.Empty, 14 | metadata: grpcWeb.Metadata | undefined, 15 | callback: (err: grpcWeb.RpcError, 16 | response: beer_pb.GetBeersResponse) => void 17 | ): grpcWeb.ClientReadableStream; 18 | 19 | getBeer( 20 | request: beer_pb.GetBeerRequest, 21 | metadata: grpcWeb.Metadata | undefined, 22 | callback: (err: grpcWeb.RpcError, 23 | response: beer_pb.GetBeerResponse) => void 24 | ): grpcWeb.ClientReadableStream; 25 | 26 | createBeer( 27 | request: beer_pb.CreateBeerRequest, 28 | metadata: grpcWeb.Metadata | undefined, 29 | callback: (err: grpcWeb.RpcError, 30 | response: google_protobuf_empty_pb.Empty) => void 31 | ): grpcWeb.ClientReadableStream; 32 | 33 | updateBeer( 34 | request: beer_pb.UpdateBeerRequest, 35 | metadata: grpcWeb.Metadata | undefined, 36 | callback: (err: grpcWeb.RpcError, 37 | response: google_protobuf_empty_pb.Empty) => void 38 | ): grpcWeb.ClientReadableStream; 39 | 40 | deleteBeer( 41 | request: beer_pb.DeleteBeerRequest, 42 | metadata: grpcWeb.Metadata | undefined, 43 | callback: (err: grpcWeb.RpcError, 44 | response: google_protobuf_empty_pb.Empty) => void 45 | ): grpcWeb.ClientReadableStream; 46 | 47 | } 48 | 49 | export class BeerServicePromiseClient { 50 | constructor (hostname: string, 51 | credentials?: null | { [index: string]: string; }, 52 | options?: null | { [index: string]: any; }); 53 | 54 | allBeers( 55 | request: google_protobuf_empty_pb.Empty, 56 | metadata?: grpcWeb.Metadata 57 | ): Promise; 58 | 59 | getBeer( 60 | request: beer_pb.GetBeerRequest, 61 | metadata?: grpcWeb.Metadata 62 | ): Promise; 63 | 64 | createBeer( 65 | request: beer_pb.CreateBeerRequest, 66 | metadata?: grpcWeb.Metadata 67 | ): Promise; 68 | 69 | updateBeer( 70 | request: beer_pb.UpdateBeerRequest, 71 | metadata?: grpcWeb.Metadata 72 | ): Promise; 73 | 74 | deleteBeer( 75 | request: beer_pb.DeleteBeerRequest, 76 | metadata?: grpcWeb.Metadata 77 | ): Promise; 78 | 79 | } 80 | 81 | -------------------------------------------------------------------------------- /grpc-beer-javascript/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | plugins: 3 | # this required grpc-web plugin installed 4 | - name: js 5 | out: . 6 | opt: import_style=commonjs 7 | - name: grpc-web 8 | out: . 9 | opt: import_style=commonjs+dts,mode=grpcwebtext -------------------------------------------------------------------------------- /grpc-beer-javascript/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | name: github.com/lreimer/from-rest-to-grpc 3 | deps: 4 | - buf.build/beta/googleapis 5 | build: 6 | roots: 7 | - proto 8 | -------------------------------------------------------------------------------- /grpc-beer-javascript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | gRPC-Web Beer Client 6 | 7 | 8 | 9 |

Open up the developer console and see the logs for the output.

10 | 11 | -------------------------------------------------------------------------------- /grpc-beer-javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grpc-beer-javascript", 3 | "version": "1.0.0", 4 | "description": "A JavaScript client for the gRPC beer service", 5 | "main": "./beer_client.js", 6 | "scripts": { 7 | "build": "node beer_client.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/lreimer/from-rest-to-grpc.git" 12 | }, 13 | "keywords": [ 14 | "grpc-web" 15 | ], 16 | "author": "M.-Leander Reimer", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/lreimer/from-rest-to-grpc/issues" 20 | }, 21 | "homepage": "https://github.com/lreimer/from-rest-to-grpc", 22 | "dependencies": { 23 | "google-protobuf": "^3.20.1", 24 | "grpc-web": "^1.3.0" 25 | }, 26 | "devDependencies": { 27 | "webpack": "^5.63.0", 28 | "webpack-cli": "^4.9.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /grpc-beer-javascript/proto/beer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/lreimer/from-rest-to-grpc/grpc-beer-service/proto"; 4 | 5 | import "google/protobuf/empty.proto"; 6 | package beer; 7 | 8 | service BeerService { 9 | // Get the list of all beers 10 | rpc AllBeers (google.protobuf.Empty) returns (GetBeersResponse) {} 11 | // Get a single beer by Asin 12 | rpc GetBeer (GetBeerRequest) returns (GetBeerResponse) {} 13 | // Create a new beer 14 | rpc CreateBeer (CreateBeerRequest) returns (google.protobuf.Empty) {} 15 | // Update an existing beer 16 | rpc UpdateBeer (UpdateBeerRequest) returns (google.protobuf.Empty) {} 17 | // Delete an existing beeer 18 | rpc DeleteBeer (DeleteBeerRequest) returns (google.protobuf.Empty) {} 19 | } 20 | 21 | message Beer { 22 | string asin = 1; 23 | string name = 2; 24 | string brand = 3; 25 | string country = 4; 26 | float alcohol = 5; 27 | enum BeerType{ 28 | IndianPaleAle = 0; 29 | SessionIpa = 1; 30 | Lager = 2; 31 | } 32 | BeerType type = 6; 33 | } 34 | 35 | message GetBeersResponse { 36 | repeated Beer beers = 1; 37 | } 38 | 39 | message GetBeerRequest { 40 | string asin = 1; 41 | } 42 | 43 | message GetBeerResponse { 44 | Beer beer = 1; 45 | } 46 | 47 | message CreateBeerRequest { 48 | Beer beer = 1; 49 | } 50 | 51 | message UpdateBeerRequest { 52 | string asin = 1; 53 | Beer beer = 2; 54 | } 55 | 56 | message DeleteBeerRequest { 57 | string asin = 1; 58 | } -------------------------------------------------------------------------------- /grpc-beer-javascript/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: "production", 3 | entry: "./beer_client.js", 4 | }; -------------------------------------------------------------------------------- /grpc-beer-nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:stable 2 | COPY nginx.conf /etc/nginx/nginx.conf 3 | EXPOSE 8888 -------------------------------------------------------------------------------- /grpc-beer-nginx/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grpc-beer-nginx 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: grpc-beer-nginx 10 | template: 11 | metadata: 12 | labels: 13 | app: grpc-beer-nginx 14 | spec: 15 | containers: 16 | - name: grpc-beer-nginx 17 | image: lreimer/grpc-beer-nginx 18 | resources: 19 | requests: 20 | memory: "32Mi" 21 | cpu: "100m" 22 | limits: 23 | memory: "128Mi" 24 | cpu: "250m" 25 | ports: 26 | - name: http 27 | containerPort: 8888 28 | -------------------------------------------------------------------------------- /grpc-beer-nginx/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grpc-beer-nginx 5 | spec: 6 | selector: 7 | app: grpc-beer-nginx 8 | type: ClusterIP 9 | sessionAffinity: None 10 | ports: 11 | - protocol: TCP 12 | port: 8888 13 | targetPort: http 14 | -------------------------------------------------------------------------------- /grpc-beer-nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | error_log /var/log/nginx/error.log notice; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | include /etc/nginx/mime.types; 13 | default_type application/octet-stream; 14 | 15 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 16 | '$status $body_bytes_sent "$http_referer" ' 17 | '"$http_user_agent" "$http_x_forwarded_for"'; 18 | 19 | access_log /var/log/nginx/access.log main; 20 | 21 | sendfile on; 22 | #tcp_nopush on; 23 | 24 | keepalive_timeout 65; 25 | 26 | gzip on; 27 | 28 | include /etc/nginx/conf.d/*.conf; 29 | 30 | upstream grpc_server { 31 | server grpc-beer-service:9090; 32 | } 33 | 34 | server { 35 | listen 8888 http2; 36 | 37 | location /beer.BeerService/ { 38 | grpc_pass grpc://grpc_server; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /grpc-beer-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17-bullseye as build 2 | 3 | WORKDIR /go/src/app 4 | ADD . /go/src/app 5 | 6 | RUN go get -d -v ./... 7 | RUN go build -o /go/bin/grpc-beer-service 8 | 9 | FROM gcr.io/distroless/base-debian11 10 | 11 | ENV HOST=0.0.0.0 12 | ENV PORT=9090 13 | 14 | COPY --from=build /go/bin/grpc-beer-service / 15 | 16 | CMD ["/grpc-beer-service"] 17 | -------------------------------------------------------------------------------- /grpc-beer-service/Makefile: -------------------------------------------------------------------------------- 1 | NAME = grpc-beer-service 2 | 3 | default: build 4 | 5 | image: 6 | @docker build -t lreimer/$(NAME) . 7 | 8 | build: 9 | @buf generate 10 | @go build 11 | 12 | clean: 13 | @rm -f $(NAME) -------------------------------------------------------------------------------- /grpc-beer-service/README.md: -------------------------------------------------------------------------------- 1 | # Beer gRPC Service 2 | 3 | ```bash 4 | # for Protocol Buffers we need to install the latest generator 5 | $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 6 | $ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 7 | 8 | $ export SRC_DIR=$PWD/proto 9 | $ export DST_DIR=$PWD/proto 10 | 11 | # call the protoc compiler to generate the Go files 12 | $ protoc --go_out=$DST_DIR --go_opt=paths=source_relative \ 13 | --go-grpc_out=$DST_DIR --go-grpc_opt=paths=source_relative \ 14 | -I=$SRC_DIR $SRC_DIR/beer.proto 15 | 16 | # alternatively, we use the Buf utility to do the same 17 | # see https://buf.build 18 | $ buf generate 19 | ``` 20 | -------------------------------------------------------------------------------- /grpc-beer-service/beer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | pb "github.com/lreimer/from-rest-to-grpc/grpc-beer-service/proto" 5 | ) 6 | 7 | var beers = map[string]*pb.Beer{ 8 | "B079V9ZDNY": {Asin: "B079V9ZDNY", Name: "Drunken Sailor", Brand: "CREW Republic", Country: "Germany", Alcohol: 6.4, Type: pb.Beer_IndianPaleAle}, 9 | "B07B2YW1TW": {Asin: "B07B2YW1TW", Name: "Hop Junkie", Brand: "CREW Republic", Country: "Germany", Alcohol: 3.4, Type: pb.Beer_SessionIpa}, 10 | "B01AU6LWNC": {Asin: "B01AU6LWNC", Name: "Edelstoff Exportbier", Brand: "Augustiner Brauerei München", Country: "Germany", Alcohol: 5.6, Type: pb.Beer_Lager}, 11 | } 12 | 13 | // AllBeers returns a slice of all Beers 14 | func allBeers() []*pb.Beer { 15 | values := make([]*pb.Beer, len(beers)) 16 | idx := 0 17 | for _, beer := range beers { 18 | values[idx] = beer 19 | idx++ 20 | } 21 | return values 22 | } 23 | 24 | // GetBeer returns the beer for a given ASIN 25 | func getBeer(asin string) (*pb.Beer, bool) { 26 | beer, found := beers[asin] 27 | return beer, found 28 | } 29 | 30 | // CreateBeer creates a new Beer if it does not exist 31 | func createBeer(beer *pb.Beer) (string, bool) { 32 | _, exists := beers[beer.Asin] 33 | if exists { 34 | return beer.Asin, false 35 | } 36 | beers[beer.Asin] = beer 37 | return beer.Asin, true 38 | } 39 | 40 | // UpdateBeer updates an existing beer 41 | func updateBeer(asin string, beer *pb.Beer) bool { 42 | _, exists := beers[asin] 43 | if exists { 44 | beers[asin] = beer 45 | } 46 | return exists 47 | } 48 | 49 | // DeleteBeer removes a beer from the map by ASIN key 50 | func deleteBeer(asin string) { 51 | delete(beers, asin) 52 | } 53 | -------------------------------------------------------------------------------- /grpc-beer-service/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | plugins: 3 | - name: go 4 | out: proto 5 | opt: paths=source_relative 6 | - name: go-grpc 7 | out: proto 8 | opt: paths=source_relative -------------------------------------------------------------------------------- /grpc-beer-service/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | name: github.com/lreimer/from-rest-to-grpc 3 | deps: 4 | - buf.build/beta/googleapis 5 | build: 6 | roots: 7 | - proto 8 | -------------------------------------------------------------------------------- /grpc-beer-service/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lreimer/from-rest-to-grpc/grpc-beer-service 2 | 3 | go 1.17 4 | 5 | require ( 6 | google.golang.org/grpc v1.46.0 7 | google.golang.org/protobuf v1.28.0 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.2 // indirect 12 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect 13 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect 14 | golang.org/x/text v0.3.7 // indirect 15 | google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /grpc-beer-service/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grpc-beer-service 5 | spec: 6 | replicas: 2 7 | selector: 8 | matchLabels: 9 | app: grpc-beer-service 10 | template: 11 | metadata: 12 | labels: 13 | app: grpc-beer-service 14 | spec: 15 | containers: 16 | - name: grpc-beer-service 17 | image: lreimer/grpc-beer-service 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "100m" 22 | limits: 23 | memory: "128Mi" 24 | cpu: "250m" 25 | env: 26 | - name: HOST 27 | value: "0.0.0.0" 28 | - name: PORT 29 | value: "9090" 30 | ports: 31 | - name: grpc 32 | containerPort: 9090 33 | -------------------------------------------------------------------------------- /grpc-beer-service/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grpc-beer-service 5 | spec: 6 | selector: 7 | app: grpc-beer-service 8 | type: ClusterIP 9 | sessionAffinity: ClientIP 10 | ports: 11 | - protocol: TCP 12 | port: 9090 13 | targetPort: grpc 14 | -------------------------------------------------------------------------------- /grpc-beer-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "os" 7 | 8 | pb "github.com/lreimer/from-rest-to-grpc/grpc-beer-service/proto" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | func main() { 13 | lis, err := net.Listen("tcp", address()) 14 | if err != nil { 15 | log.Fatalf("Failed to listen at %v", err) 16 | } 17 | 18 | s := grpc.NewServer() 19 | pb.RegisterBeerServiceServer(s, &Server{}) 20 | 21 | log.Printf("gRPC Beer service listening at %v", lis.Addr()) 22 | if err := s.Serve(lis); err != nil { 23 | log.Fatalf("Failed to serve %v", err) 24 | } 25 | } 26 | 27 | func address() string { 28 | host := os.Getenv("HOST") 29 | if len(host) == 0 { 30 | host = "localhost" 31 | } 32 | 33 | port := os.Getenv("PORT") 34 | if len(port) == 0 { 35 | port = "9090" 36 | } 37 | 38 | return host + ":" + port 39 | } 40 | -------------------------------------------------------------------------------- /grpc-beer-service/proto/beer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/lreimer/from-rest-to-grpc/grpc-beer-service/proto"; 4 | 5 | import "google/protobuf/empty.proto"; 6 | package beer; 7 | 8 | service BeerService { 9 | // Get the list of all beers 10 | rpc AllBeers (google.protobuf.Empty) returns (GetBeersResponse) {} 11 | // Get a single beer by Asin 12 | rpc GetBeer (GetBeerRequest) returns (GetBeerResponse) {} 13 | // Create a new beer 14 | rpc CreateBeer (CreateBeerRequest) returns (google.protobuf.Empty) {} 15 | // Update an existing beer 16 | rpc UpdateBeer (UpdateBeerRequest) returns (google.protobuf.Empty) {} 17 | // Delete an existing beeer 18 | rpc DeleteBeer (DeleteBeerRequest) returns (google.protobuf.Empty) {} 19 | } 20 | 21 | message Beer { 22 | string asin = 1; 23 | string name = 2; 24 | string brand = 3; 25 | string country = 4; 26 | float alcohol = 5; 27 | enum BeerType{ 28 | IndianPaleAle = 0; 29 | SessionIpa = 1; 30 | Lager = 2; 31 | } 32 | BeerType type = 6; 33 | } 34 | 35 | message GetBeersResponse { 36 | repeated Beer beers = 1; 37 | } 38 | 39 | message GetBeerRequest { 40 | string asin = 1; 41 | } 42 | 43 | message GetBeerResponse { 44 | Beer beer = 1; 45 | } 46 | 47 | message CreateBeerRequest { 48 | Beer beer = 1; 49 | } 50 | 51 | message UpdateBeerRequest { 52 | string asin = 1; 53 | Beer beer = 2; 54 | } 55 | 56 | message DeleteBeerRequest { 57 | string asin = 1; 58 | } -------------------------------------------------------------------------------- /grpc-beer-service/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | pb "github.com/lreimer/from-rest-to-grpc/grpc-beer-service/proto" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | "google.golang.org/protobuf/types/known/emptypb" 11 | ) 12 | 13 | type Server struct { 14 | pb.UnimplementedBeerServiceServer 15 | } 16 | 17 | // Get the list of all beers 18 | func (s *Server) AllBeers(ctx context.Context, r *emptypb.Empty) (*pb.GetBeersResponse, error) { 19 | log.Println("Getting list of all beers") 20 | return &pb.GetBeersResponse{Beers: allBeers()}, nil 21 | } 22 | 23 | // Get a single beer by Asin 24 | func (s *Server) GetBeer(ctx context.Context, r *pb.GetBeerRequest) (*pb.GetBeerResponse, error) { 25 | log.Printf("Getting beer by ASIN %v", r.Asin) 26 | beer, found := getBeer(r.Asin) 27 | if found { 28 | return &pb.GetBeerResponse{Beer: beer}, nil 29 | } else { 30 | return nil, status.Error(codes.NotFound, "Beer not found") 31 | } 32 | } 33 | 34 | // Create a new beer 35 | func (s *Server) CreateBeer(ctx context.Context, r *pb.CreateBeerRequest) (*emptypb.Empty, error) { 36 | log.Printf("Creating beer %v", r.Beer) 37 | _, created := createBeer(r.Beer) 38 | if created { 39 | return &emptypb.Empty{}, nil 40 | } else { 41 | return nil, status.Error(codes.AlreadyExists, "Beer already exists") 42 | } 43 | } 44 | 45 | // Update an existing beer 46 | func (s *Server) UpdateBeer(ctx context.Context, r *pb.UpdateBeerRequest) (*emptypb.Empty, error) { 47 | log.Printf("Updating beer %v", r.Beer) 48 | updated := updateBeer(r.Asin, r.Beer) 49 | if updated { 50 | return &emptypb.Empty{}, nil 51 | } else { 52 | return nil, status.Error(codes.NotFound, "Beer not updated") 53 | } 54 | } 55 | 56 | // Delete an existing beeer 57 | func (s *Server) DeleteBeer(ctx context.Context, r *pb.DeleteBeerRequest) (*emptypb.Empty, error) { 58 | log.Printf("Creating beer by ASIN %v", r.Asin) 59 | deleteBeer(r.Asin) 60 | return &emptypb.Empty{}, nil 61 | } 62 | -------------------------------------------------------------------------------- /grpc-beer-ui/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grpc-beer-ui 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: grpc-beer-ui 10 | template: 11 | metadata: 12 | labels: 13 | app: grpc-beer-ui 14 | spec: 15 | containers: 16 | - name: grpc-ui 17 | image: qaware/grpcox:latest 18 | resources: 19 | requests: 20 | memory: "128Mi" 21 | cpu: "100m" 22 | limits: 23 | memory: "256Mi" 24 | cpu: "250m" 25 | ports: 26 | - name: http 27 | containerPort: 6969 28 | -------------------------------------------------------------------------------- /grpc-beer-ui/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grpc-beer-ui 5 | spec: 6 | selector: 7 | app: grpc-beer-ui 8 | type: ClusterIP 9 | sessionAffinity: None 10 | ports: 11 | - protocol: TCP 12 | port: 6969 13 | targetPort: http 14 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | .gradle 4 | build/ 5 | target/ 6 | out/ 7 | .idea 8 | *.iml 9 | *.ipr 10 | *.iws 11 | .project 12 | .settings 13 | .classpath 14 | .factorypath 15 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/README.md: -------------------------------------------------------------------------------- 1 | ## Micronaut 3.4.2 Documentation 2 | 3 | - [User Guide](https://docs.micronaut.io/3.4.2/guide/index.html) 4 | - [API Reference](https://docs.micronaut.io/3.4.2/api/index.html) 5 | - [Configuration Reference](https://docs.micronaut.io/3.4.2/guide/configurationreference.html) 6 | - [Micronaut Guides](https://guides.micronaut.io/index.html) 7 | --- 8 | 9 | - [Shadow Gradle Plugin](https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow) 10 | - [Protobuf Gradle Plugin](https://plugins.gradle.org/plugin/com.google.protobuf) 11 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.github.johnrengelman.shadow") version "7.1.2" 3 | id("io.micronaut.application") version "3.3.2" 4 | id("com.google.protobuf") version "0.8.15" 5 | id("com.google.cloud.tools.jib") version "3.2.0" 6 | } 7 | 8 | version = "1.0.0" 9 | group = "hands.on.grpc" 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | annotationProcessor("io.micronaut:micronaut-http-validation") 17 | implementation("io.micronaut:micronaut-jackson-databind") 18 | implementation("io.micronaut:micronaut-http-client") 19 | implementation("io.micronaut:micronaut-runtime") 20 | implementation("io.micronaut.grpc:micronaut-grpc-runtime") 21 | implementation("jakarta.annotation:jakarta.annotation-api") 22 | runtimeOnly("org.slf4j:slf4j-simple") 23 | implementation("io.micronaut:micronaut-validation") 24 | implementation("io.micronaut:micronaut-management") 25 | 26 | testImplementation("io.micronaut:micronaut-http-client") 27 | 28 | } 29 | 30 | 31 | application { 32 | mainClass.set("hands.on.grpc.Application") 33 | } 34 | java { 35 | sourceCompatibility = JavaVersion.toVersion("11") 36 | targetCompatibility = JavaVersion.toVersion("11") 37 | } 38 | tasks { 39 | jib { 40 | to { 41 | image = "lreimer/micronaut-beer-grpc" 42 | } 43 | } 44 | } 45 | sourceSets { 46 | main { 47 | java { 48 | srcDirs("build/generated/source/proto/main/grpc") 49 | srcDirs("build/generated/source/proto/main/java") 50 | } 51 | } 52 | } 53 | 54 | protobuf { 55 | protoc { artifact = "com.google.protobuf:protoc:3.17.2" } 56 | plugins { 57 | grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.39.0" } 58 | } 59 | generateProtoTasks { 60 | all()*.plugins { grpc {} } 61 | } 62 | } 63 | graalvmNative.toolchainDetection = false 64 | micronaut { 65 | runtime("netty") 66 | testRuntime("junit5") 67 | processing { 68 | incremental(true) 69 | annotations("hands.on.grpc.*") 70 | } 71 | } 72 | 73 | 74 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/gradle.properties: -------------------------------------------------------------------------------- 1 | micronautVersion=3.4.2 2 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lreimer/from-rest-to-grpc/4bee9f8579ad3f9802e5b618ec35351976c0d3d2/micronaut-beer-grpc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /micronaut-beer-grpc/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grpc-beer-service 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: grpc-beer-service 10 | template: 11 | metadata: 12 | labels: 13 | app: grpc-beer-service 14 | spec: 15 | containers: 16 | - name: grpc-beer-service 17 | image: lreimer/micronaut-beer-grpc 18 | resources: 19 | requests: 20 | memory: "128Mi" 21 | cpu: "0.25" 22 | limits: 23 | memory: "256Mi" 24 | cpu: "2.0" 25 | ports: 26 | - name: http 27 | containerPort: 8080 28 | - name: grpc 29 | containerPort: 9090 30 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grpc-beer-service 5 | spec: 6 | selector: 7 | app: grpc-beer-service 8 | type: ClusterIP 9 | sessionAffinity: ClientIP 10 | ports: 11 | - name: grpc 12 | protocol: TCP 13 | port: 9090 14 | targetPort: grpc 15 | - name: http 16 | protocol: TCP 17 | port: 8080 18 | targetPort: http 19 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/micronaut-cli.yml: -------------------------------------------------------------------------------- 1 | applicationType: grpc 2 | defaultPackage: hands.on.grpc 3 | testFramework: junit 4 | sourceLanguage: java 5 | buildTool: gradle 6 | features: [annotation-api, app-name, gradle, grpc, jackson-databind, java, java-application, junit, micronaut-build, readme, shade, slf4j-simple, yaml] 7 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/settings.gradle: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name="micronaut-beer-grpc" 3 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/src/main/java/hands/on/grpc/Application.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.micronaut.runtime.Micronaut; 4 | 5 | public class Application { 6 | 7 | public static void main(String[] args) { 8 | Micronaut.run(Application.class, args); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /micronaut-beer-grpc/src/main/java/hands/on/grpc/BeerGrpcService.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import com.google.protobuf.Empty; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import io.grpc.Status; 9 | import io.grpc.StatusException; 10 | import io.grpc.stub.StreamObserver; 11 | import jakarta.inject.Singleton; 12 | 13 | import javax.annotation.PostConstruct; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | import static hands.on.grpc.BeerProtos.*; 18 | 19 | @Singleton 20 | public class BeerGrpcService extends BeerServiceGrpc.BeerServiceImplBase { 21 | 22 | private static Logger logger = LoggerFactory.getLogger(BeerGrpcService.class); 23 | 24 | private Map beers = new HashMap<>(); 25 | 26 | @PostConstruct 27 | void initialize() { 28 | beers.put("B079V9ZDNY", Beer.newBuilder().setAsin("B079V9ZDNY").setName("Drunken Sailor").setBrand("CREW Republic").setCountry("Germany").setAlcohol(6.4f).setType(Beer.BeerType.IndianPaleAle).build()); 29 | beers.put("B07B2YW1TW", Beer.newBuilder().setAsin("B07B2YW1TW").setName("Hop Junkie").setBrand("CREW Republic").setCountry("Germany").setAlcohol(3.4f).setType(Beer.BeerType.SessionIpa).build()); 30 | beers.put("B01AU6LWNC", Beer.newBuilder().setAsin("B01AU6LWNC").setName("Edelstoff Exportbier").setBrand("Augustiner Brauerei München").setCountry("Germany").setAlcohol(5.6f).setType(Beer.BeerType.Lager).build()); 31 | } 32 | 33 | @Override 34 | public void allBeers(Empty request, StreamObserver responseObserver) { 35 | logger.info("AllBears gRPC API called"); 36 | GetBeersResponse.Builder builder = GetBeersResponse.newBuilder(); 37 | for (Beer beer : beers.values()) { 38 | builder.addBeers(beer); 39 | } 40 | 41 | responseObserver.onNext(builder.build()); 42 | responseObserver.onCompleted(); 43 | } 44 | 45 | @Override 46 | public void getBeer(GetBeerRequest request, StreamObserver responseObserver) { 47 | logger.info("GetBears gRPC API called"); 48 | Beer beer = beers.get(request.getAsin()); 49 | if (beer == null) { 50 | responseObserver.onError(new StatusException(Status.NOT_FOUND)); 51 | } else { 52 | responseObserver.onNext(GetBeerResponse.newBuilder().setBeer(beer).build()); 53 | } 54 | responseObserver.onCompleted(); 55 | 56 | } 57 | 58 | @Override 59 | public void createBeer(CreateBeerRequest request, StreamObserver responseObserver) { 60 | logger.info("CreateBear gRPC API called"); 61 | String asin = request.getBeer().getAsin(); 62 | if (beers.containsKey(asin)) { 63 | responseObserver.onError(new StatusException(Status.ALREADY_EXISTS)); 64 | } else { 65 | beers.put(asin, request.getBeer()); 66 | responseObserver.onNext(Empty.newBuilder().build()); 67 | } 68 | responseObserver.onCompleted(); 69 | } 70 | 71 | @Override 72 | public void updateBeer(UpdateBeerRequest request, StreamObserver responseObserver) { 73 | logger.info("UpdateBeer gRPC API called"); 74 | if (beers.containsKey(request.getAsin())) { 75 | beers.put(request.getAsin(), request.getBeer()); 76 | responseObserver.onNext(Empty.newBuilder().build()); 77 | } else { 78 | responseObserver.onError(new StatusException(Status.NOT_FOUND)); 79 | } 80 | responseObserver.onCompleted(); 81 | } 82 | 83 | @Override 84 | public void deleteBeer(DeleteBeerRequest request, StreamObserver responseObserver) { 85 | logger.info("DeleteBeer gRPC API called"); 86 | Beer removed = beers.remove(request.getAsin()); 87 | if (removed == null) { 88 | responseObserver.onError(new StatusException(Status.NOT_FOUND)); 89 | } else { 90 | responseObserver.onNext(Empty.newBuilder().build()); 91 | } 92 | responseObserver.onCompleted(); 93 | } 94 | } 95 | // economics of investment in reliability -------------------------------------------------------------------------------- /micronaut-beer-grpc/src/main/proto/beer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | option java_package = "hands.on.grpc"; 5 | option java_outer_classname = "BeerProtos"; 6 | option optimize_for = SPEED; 7 | option objc_class_prefix = "HLW"; 8 | 9 | import "google/protobuf/empty.proto"; 10 | package beer; 11 | 12 | service BeerService { 13 | // Get the list of all beers 14 | rpc AllBeers (google.protobuf.Empty) returns (GetBeersResponse) {} 15 | // Get a single beer by Asin 16 | rpc GetBeer (GetBeerRequest) returns (GetBeerResponse) {} 17 | // Create a new beer 18 | rpc CreateBeer (CreateBeerRequest) returns (google.protobuf.Empty) {} 19 | // Update an existing beer 20 | rpc UpdateBeer (UpdateBeerRequest) returns (google.protobuf.Empty) {} 21 | // Delete an existing beeer 22 | rpc DeleteBeer (DeleteBeerRequest) returns (google.protobuf.Empty) {} 23 | } 24 | 25 | message Beer { 26 | string asin = 1; 27 | string name = 2; 28 | string brand = 3; 29 | string country = 4; 30 | float alcohol = 5; 31 | enum BeerType{ 32 | IndianPaleAle = 0; 33 | SessionIpa = 1; 34 | Lager = 2; 35 | } 36 | BeerType type = 6; 37 | } 38 | 39 | message GetBeersResponse { 40 | repeated Beer beers = 1; 41 | } 42 | 43 | message GetBeerRequest { 44 | string asin = 1; 45 | } 46 | 47 | message GetBeerResponse { 48 | Beer beer = 1; 49 | } 50 | 51 | message CreateBeerRequest { 52 | Beer beer = 1; 53 | } 54 | 55 | message UpdateBeerRequest { 56 | string asin = 1; 57 | Beer beer = 2; 58 | } 59 | 60 | message DeleteBeerRequest { 61 | string asin = 1; 62 | } -------------------------------------------------------------------------------- /micronaut-beer-grpc/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | micronaut: 2 | application: 3 | name: micronautBeerGrpc 4 | 5 | grpc: 6 | server: 7 | port: 9090 8 | keep-alive-time: 3h 9 | health: 10 | enabled: true 11 | 12 | netty: 13 | default: 14 | allocator: 15 | max-order: 3 -------------------------------------------------------------------------------- /micronaut-beer-grpc/src/main/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | org.slf4j.simpleLogger.defaultLogLevel=info 2 | org.slf4j.simpleLogger.log.io.micronaut=info -------------------------------------------------------------------------------- /micronaut-beer-grpc/src/test/java/hands/on/grpc/MicronautBeerGrpcTest.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.micronaut.runtime.EmbeddedApplication; 4 | import io.micronaut.test.extensions.junit5.annotation.MicronautTest; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.Assertions; 7 | 8 | import jakarta.inject.Inject; 9 | 10 | @MicronautTest 11 | class MicronautBeerGrpcTest { 12 | 13 | @Inject 14 | EmbeddedApplication application; 15 | 16 | @Test 17 | void testItWorks() { 18 | Assertions.assertTrue(application.isRunning()); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /micronaut-beer-rest/.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | .gradle 4 | build/ 5 | target/ 6 | out/ 7 | .idea 8 | *.iml 9 | *.ipr 10 | *.iws 11 | .project 12 | .settings 13 | .classpath 14 | .factorypath 15 | -------------------------------------------------------------------------------- /micronaut-beer-rest/README.md: -------------------------------------------------------------------------------- 1 | ## Micronaut 3.4.2 Documentation 2 | 3 | - [User Guide](https://docs.micronaut.io/3.4.2/guide/index.html) 4 | - [API Reference](https://docs.micronaut.io/3.4.2/api/index.html) 5 | - [Configuration Reference](https://docs.micronaut.io/3.4.2/guide/configurationreference.html) 6 | - [Micronaut Guides](https://guides.micronaut.io/index.html) 7 | --- 8 | 9 | - [Shadow Gradle Plugin](https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow) 10 | - [Jib Gradle Plugin](https://plugins.gradle.org/plugin/com.google.cloud.tools.jib) 11 | ## Feature management documentation 12 | 13 | - [Micronaut Management documentation](https://docs.micronaut.io/latest/guide/index.html#management) 14 | 15 | 16 | ## Feature http-client documentation 17 | 18 | - [Micronaut HTTP Client documentation](https://docs.micronaut.io/latest/guide/index.html#httpClient) 19 | 20 | 21 | -------------------------------------------------------------------------------- /micronaut-beer-rest/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.github.johnrengelman.shadow") version "7.1.2" 3 | id("io.micronaut.application") version "3.3.2" 4 | id("com.google.protobuf") version "0.8.15" 5 | id("com.google.cloud.tools.jib") version "3.2.0" 6 | } 7 | 8 | version = "1.0.0" 9 | group = "hands.on.grpc" 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | annotationProcessor("io.micronaut:micronaut-http-validation") 17 | implementation("io.micronaut:micronaut-http-client") 18 | implementation("io.micronaut:micronaut-jackson-databind") 19 | implementation("io.micronaut:micronaut-management") 20 | implementation("io.micronaut:micronaut-runtime") 21 | implementation 'com.google.protobuf:protobuf-java:3.17.2' 22 | implementation("io.micronaut.grpc:micronaut-protobuff-support") 23 | implementation("jakarta.annotation:jakarta.annotation-api") 24 | runtimeOnly("ch.qos.logback:logback-classic") 25 | implementation("io.micronaut:micronaut-validation") 26 | 27 | } 28 | 29 | 30 | application { 31 | mainClass.set("hands.on.grpc.Application") 32 | } 33 | java { 34 | sourceCompatibility = JavaVersion.toVersion("11") 35 | targetCompatibility = JavaVersion.toVersion("11") 36 | } 37 | 38 | tasks { 39 | jib { 40 | to { 41 | image = "lreimer/micronaut-beer-rest" 42 | } 43 | } 44 | } 45 | protobuf { 46 | protoc { artifact = "com.google.protobuf:protoc:3.17.2" } 47 | } 48 | sourceSets.main.java.srcDirs += ['build/generated/source/proto/main/java'] 49 | 50 | graalvmNative.toolchainDetection = false 51 | micronaut { 52 | runtime("netty") 53 | testRuntime("junit5") 54 | processing { 55 | incremental(true) 56 | annotations("hands.on.grpc.*") 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /micronaut-beer-rest/gradle.properties: -------------------------------------------------------------------------------- 1 | micronautVersion=3.4.2 2 | -------------------------------------------------------------------------------- /micronaut-beer-rest/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lreimer/from-rest-to-grpc/4bee9f8579ad3f9802e5b618ec35351976c0d3d2/micronaut-beer-rest/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /micronaut-beer-rest/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /micronaut-beer-rest/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /micronaut-beer-rest/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: rest-beer-service 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: rest-beer-service 10 | template: 11 | metadata: 12 | labels: 13 | app: rest-beer-service 14 | spec: 15 | containers: 16 | - name: rest-beer-service 17 | image: lreimer/micronaut-beer-rest 18 | resources: 19 | requests: 20 | memory: "128Mi" 21 | cpu: "0.25" 22 | limits: 23 | memory: "256Mi" 24 | cpu: "2.0" 25 | ports: 26 | - name: http 27 | containerPort: 8080 28 | -------------------------------------------------------------------------------- /micronaut-beer-rest/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: rest-beer-service 5 | spec: 6 | selector: 7 | app: rest-beer-service 8 | type: ClusterIP 9 | sessionAffinity: None 10 | ports: 11 | - protocol: TCP 12 | port: 8080 13 | targetPort: http 14 | -------------------------------------------------------------------------------- /micronaut-beer-rest/micronaut-cli.yml: -------------------------------------------------------------------------------- 1 | applicationType: default 2 | defaultPackage: hands.on.grpc 3 | testFramework: junit 4 | sourceLanguage: java 5 | buildTool: gradle 6 | features: [annotation-api, app-name, gradle, http-client, jackson-databind, java, java-application, jib, junit, logback, management, micronaut-build, netty-server, readme, shade, yaml] 7 | -------------------------------------------------------------------------------- /micronaut-beer-rest/settings.gradle: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name="micronaut-beer-rest" 3 | -------------------------------------------------------------------------------- /micronaut-beer-rest/src/main/java/hands/on/grpc/Application.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.micronaut.runtime.Micronaut; 4 | 5 | public class Application { 6 | 7 | public static void main(String[] args) { 8 | Micronaut.run(Application.class, args); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /micronaut-beer-rest/src/main/java/hands/on/grpc/Beer.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import java.util.Objects; 4 | 5 | import io.micronaut.core.annotation.Introspected; 6 | 7 | @Introspected 8 | public class Beer { 9 | 10 | public enum Type { 11 | IndianPaleAle, SessionIpa, Lager 12 | } 13 | 14 | private String asin; 15 | private String name; 16 | private String brand; 17 | private String country; 18 | private float alcohol; 19 | private Type type; 20 | 21 | public Beer() { 22 | } 23 | 24 | public Beer(String asin, String name, String brand, String country, float alcohol, Type type) { 25 | this.asin = asin; 26 | this.name = name; 27 | this.brand = brand; 28 | this.country = country; 29 | this.alcohol = alcohol; 30 | this.type = type; 31 | } 32 | 33 | public String getAsin() { 34 | return asin; 35 | } 36 | 37 | public void setAsin(String asin) { 38 | this.asin = asin; 39 | } 40 | 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | public void setName(String name) { 46 | this.name = name; 47 | } 48 | 49 | public String getBrand() { 50 | return brand; 51 | } 52 | 53 | public void setBrand(String brand) { 54 | this.brand = brand; 55 | } 56 | 57 | public String getCountry() { 58 | return country; 59 | } 60 | 61 | public void setCountry(String country) { 62 | this.country = country; 63 | } 64 | 65 | public float getAlcohol() { 66 | return alcohol; 67 | } 68 | 69 | public void setAlcohol(float alcohol) { 70 | this.alcohol = alcohol; 71 | } 72 | 73 | public Type getType() { 74 | return type; 75 | } 76 | 77 | public void setType(Type type) { 78 | this.type = type; 79 | } 80 | 81 | @Override 82 | public boolean equals(Object o) { 83 | if (this == o) return true; 84 | if (o == null || getClass() != o.getClass()) return false; 85 | Beer that = (Beer) o; 86 | return Float.compare(that.alcohol, alcohol) == 0 && Objects.equals(asin, that.asin) && Objects.equals(name, that.name) && Objects.equals(brand, that.brand) && Objects.equals(country, that.country) && type == that.type; 87 | } 88 | 89 | @Override 90 | public int hashCode() { 91 | return Objects.hash(asin, name, brand, country, alcohol, type); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /micronaut-beer-rest/src/main/java/hands/on/grpc/BeerController.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import java.util.Collection; 4 | import java.util.Objects; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import io.micronaut.http.HttpResponse; 10 | import io.micronaut.http.annotation.*; 11 | import jakarta.inject.Inject; 12 | 13 | @Controller("/api") 14 | public class BeerController { 15 | 16 | private static Logger logger = LoggerFactory.getLogger(BeerController.class); 17 | 18 | @Inject 19 | BeerRepository repository; 20 | 21 | @Get(uri = "/beers", produces = "application/json") 22 | public Collection beers() { 23 | logger.info("GetBeers() REST API called"); 24 | return repository.all(); 25 | } 26 | 27 | @Post(uri = "/beers", consumes = "application/json") 28 | public HttpResponse create(@Body Beer beer) { 29 | logger.info("CreateBeer() REST API called"); 30 | String asin = repository.create(beer); 31 | return HttpResponse.created("/api/beers/" + asin); 32 | } 33 | 34 | @Get(uri = "/beers/{asin}", produces = "application/json") 35 | public HttpResponse get(String asin) { 36 | logger.info("GetBeer() REST API called"); 37 | Beer beer = repository.find(asin); 38 | return HttpResponse.ok(beer); 39 | } 40 | 41 | @Put(uri = "/beers/{asin}", produces = "application/json", consumes = "application/json") 42 | public HttpResponse update(String asin, @Body Beer beer) { 43 | logger.info("UpdateBeer() REST API called"); 44 | if (!Objects.equals(asin, beer.getAsin())) { 45 | return HttpResponse.badRequest(); 46 | } 47 | repository.update(asin, beer); 48 | return HttpResponse.ok(); 49 | } 50 | 51 | @Delete(uri = "/beers/{asin}") 52 | public HttpResponse delete(String asin) { 53 | logger.info("DeleteBeer() REST API called"); 54 | repository.delete(asin); 55 | return HttpResponse.ok(); 56 | } 57 | } -------------------------------------------------------------------------------- /micronaut-beer-rest/src/main/java/hands/on/grpc/BeerRepository.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import javax.annotation.PostConstruct; 4 | 5 | import io.micronaut.http.HttpStatus; 6 | import io.micronaut.http.exceptions.HttpStatusException; 7 | import jakarta.inject.Singleton; 8 | 9 | import java.util.Collection; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import static hands.on.grpc.Beer.Type.*; 14 | 15 | @Singleton 16 | public class BeerRepository { 17 | 18 | Map beers = new HashMap<>(); 19 | 20 | public BeerRepository() { 21 | } 22 | 23 | @PostConstruct 24 | public void initialize() { 25 | beers.put("B079V9ZDNY", new Beer("B079V9ZDNY", "Drunken Sailor", "CREW Republic", "Germany", 6.4f, IndianPaleAle)); 26 | beers.put("B07B2YW1TW", new Beer("B07B2YW1TW", "Hop Junkie", "CREW Republic", "Germany", 3.4f, SessionIpa)); 27 | beers.put("B01AU6LWNC", new Beer("B01AU6LWNC", "Edelstoff Exportbier", "Augustiner Brauerei München", "Germany", 5.6f, Lager)); 28 | } 29 | 30 | public Collection all() { 31 | return beers.values(); 32 | } 33 | 34 | public String create(Beer beer) { 35 | String asin = beer.getAsin(); 36 | if (beers.containsKey(asin)) { 37 | throw new HttpStatusException(HttpStatus.CONFLICT, "Beer already exists."); 38 | } 39 | beers.put(asin, beer); 40 | return asin; 41 | } 42 | 43 | 44 | public Beer find(String asin) { 45 | Beer beer = beers.get(asin); 46 | if (beer == null) { 47 | throw new HttpStatusException(HttpStatus.NOT_FOUND, "Beer not found"); 48 | } 49 | return beer; 50 | } 51 | 52 | public void update(String asin, Beer beer) { 53 | beers.put(asin, beer); 54 | } 55 | 56 | 57 | public void delete(String asin) { 58 | Beer beer = beers.remove(asin); 59 | if (beer == null) { 60 | throw new HttpStatusException(HttpStatus.NOT_FOUND, "Beer not found"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /micronaut-beer-rest/src/main/java/hands/on/grpc/ProtoBeerController.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.micronaut.http.HttpResponse; 4 | import io.micronaut.http.annotation.*; 5 | import jakarta.inject.Inject; 6 | 7 | import static hands.on.grpc.BeerProtos.*; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import hands.on.grpc.BeerProtos.GetBeerResponse; 13 | 14 | @Controller("/proto") 15 | public class ProtoBeerController { 16 | 17 | private static Logger logger = LoggerFactory.getLogger(ProtoBeerController.class); 18 | 19 | @Inject 20 | BeerRepository repository; 21 | 22 | @Get(uri = "/beers", produces = "application/x-protobuf") 23 | public HttpResponse beers() { 24 | logger.info("GetProtoBeers() REST API called"); 25 | return HttpResponse.ok(GetBeersResponse.newBuilder().build()) 26 | .contentType("application/x-protobuf"); 27 | } 28 | 29 | @Post(uri = "/beers", produces = "application/x-protobuf", consumes = "application/x-protobuf") 30 | public HttpResponse getBeer(@Body GetBeerRequest request) { 31 | Beer beer = repository.find(request.getAsin()); 32 | GetBeerResponse response = GetBeerResponse.newBuilder() 33 | .setBeer(BeerProtos.Beer.newBuilder() 34 | .setAsin(beer.getAsin()) 35 | .setName(beer.getName()) 36 | .setBrand(beer.getBrand()) 37 | .setCountry(beer.getCountry()) 38 | .setAlcohol(beer.getAlcohol()) 39 | .setType(BeerProtos.Beer.BeerType.valueOf(beer.getType().name())) 40 | ).build(); 41 | return HttpResponse.ok(response).contentType("application/x-protobuf"); 42 | } 43 | } -------------------------------------------------------------------------------- /micronaut-beer-rest/src/main/proto/beer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package beer; 4 | 5 | option java_multiple_files = false; 6 | option java_package = "hands.on.grpc"; 7 | option java_outer_classname = "BeerProtos"; 8 | option optimize_for = SPEED; 9 | // option objc_class_prefix = "HLW"; 10 | 11 | message Beer { 12 | string asin = 1; 13 | string name = 2; 14 | string brand = 3; 15 | string country = 4; 16 | float alcohol = 5; 17 | enum BeerType { 18 | IndianPaleAle = 0; 19 | SessionIpa = 1; 20 | Lager = 2; 21 | } 22 | BeerType type = 6; 23 | } 24 | 25 | message GetBeersRequest { 26 | } 27 | 28 | message GetBeersResponse { 29 | repeated Beer beers = 1; 30 | } 31 | 32 | message GetBeerRequest { 33 | string asin = 1; 34 | } 35 | 36 | message GetBeerResponse { 37 | Beer beer = 1; 38 | } 39 | 40 | message CreateBeerRequest { 41 | Beer beer = 1; 42 | } 43 | 44 | message UpdateBeerRequest { 45 | Beer beer = 1; 46 | } 47 | 48 | message DeleteBeerRequest { 49 | string asin = 1; 50 | } 51 | -------------------------------------------------------------------------------- /micronaut-beer-rest/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | micronaut: 2 | application: 3 | name: micronautBeerRest 4 | netty: 5 | default: 6 | allocator: 7 | max-order: 3 8 | -------------------------------------------------------------------------------- /micronaut-beer-rest/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 7 | 8 | %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /micronaut-beer-rest/src/test/java/hands/on/grpc/BeerControllerTest.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | import io.micronaut.http.HttpStatus; 3 | import io.micronaut.http.client.HttpClient; 4 | import io.micronaut.test.extensions.junit5.annotation.MicronautTest; 5 | import org.junit.jupiter.api.Test; 6 | import io.micronaut.http.client.annotation.*; 7 | import jakarta.inject.Inject; 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | @MicronautTest 11 | public class BeerControllerTest { 12 | 13 | @Inject 14 | @Client("/api") 15 | HttpClient client; 16 | 17 | @Test 18 | public void testBeers() throws Exception { 19 | assertEquals(HttpStatus.OK, client.toBlocking().exchange("/beers").status()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /micronaut-beer-rest/src/test/java/hands/on/grpc/MicronautBeerRestTest.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.micronaut.runtime.EmbeddedApplication; 4 | import io.micronaut.test.extensions.junit5.annotation.MicronautTest; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.Assertions; 7 | 8 | import jakarta.inject.Inject; 9 | 10 | @MicronautTest 11 | class MicronautBeerRestTest { 12 | 13 | @Inject 14 | EmbeddedApplication application; 15 | 16 | @Test 17 | void testItWorks() { 18 | Assertions.assertTrue(application.isRunning()); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /micronaut-beer-rest/src/test/java/hands/on/grpc/ProtoBeerControllerTest.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | import io.micronaut.http.HttpRequest; 3 | import io.micronaut.http.HttpResponse; 4 | import io.micronaut.http.HttpStatus; 5 | import io.micronaut.http.MutableHttpRequest; 6 | import io.micronaut.http.client.HttpClient; 7 | import io.micronaut.test.extensions.junit5.annotation.MicronautTest; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import hands.on.grpc.BeerProtos.GetBeerRequest; 11 | import io.micronaut.http.client.annotation.*; 12 | import jakarta.inject.Inject; 13 | import static org.junit.jupiter.api.Assertions.*; 14 | 15 | import org.junit.jupiter.api.Disabled; 16 | 17 | @MicronautTest 18 | public class ProtoBeerControllerTest { 19 | 20 | @Inject 21 | @Client("/proto") 22 | HttpClient client; 23 | 24 | @Test 25 | public void testProtoBeers() throws Exception { 26 | assertEquals(HttpStatus.OK, client.toBlocking().exchange("/beers").status()); 27 | } 28 | 29 | @Test 30 | @Disabled("Can get the Protobuf marshalling to work") 31 | public void testProtoBeer() throws Exception { 32 | MutableHttpRequest request = HttpRequest.POST("/beers", 33 | BeerProtos.GetBeerRequest.newBuilder().setAsin("B07B2YW1TW").build()) 34 | .accept("application/x-protobuf").contentType("application/x-protobuf"); 35 | 36 | HttpResponse response = client.toBlocking().exchange(request); 37 | assertEquals(HttpStatus.OK, response.status()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /node-beer-graphql/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /node-beer-graphql/beer-1.0.graphql: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | mutation: Mutation 4 | } 5 | 6 | type Query { 7 | # Get the list of all beers 8 | allBeers: [Beer]! 9 | # Get a single beer by ASIN 10 | getBeer(asin: String!): Beer 11 | } 12 | 13 | type Mutation { 14 | # Create a new beer entry 15 | createBeer(asin: String!, name: String!, country: String, abv: Float, brewery: String): Beer 16 | 17 | # Update an existing beer entry 18 | updateBeer(asin: String!, name: String!, country: String, abv: Float, brewery: String): Beer 19 | 20 | # Delete a beer by ASIN 21 | deleteBeer(asin: String!): String 22 | } 23 | 24 | type Beer { 25 | asin: String! 26 | name: String! 27 | country: String 28 | abv: Float # Alcohol by volume 29 | brewery: String 30 | } -------------------------------------------------------------------------------- /node-beer-graphql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "beer-graphql", 3 | "version": "1.0.0", 4 | "main": "src/server.js", 5 | "type": "module", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "MIT", 12 | "description": "", 13 | "dependencies": { 14 | "@apollo/server": "^4.11.0", 15 | "graphql": "^16.9.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /node-beer-graphql/src/server.js: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from "@apollo/server"; 2 | import { startStandaloneServer } from "@apollo/server/standalone"; 3 | 4 | const beers = [ 5 | {asin: "B079V9ZDNY", name: "Drunken Sailor", brewery: "CREW Republic", country: "Germany", abv: 6.4}, 6 | {asin: "B07B2YW1TW", name: "Hop Junkie", brewery: "CREW Republic", country: "Germany", abv: 3.4}, 7 | {asin: "B01AU6LWNC", name: "Edelstoff Exportbier", brewery: "Augustiner Brauerei München", abv: "Germany", Alcohol: 5.6}, 8 | ]; 9 | 10 | const typeDefs = ` 11 | type Query { 12 | allBeers: [Beer]! 13 | getBeer(asin: String!): Beer 14 | } 15 | 16 | type Mutation { 17 | createBeer(asin: String!, name: String!, country: String, abv: Float, brewery: String): Beer 18 | updateBeer(asin: String!, name: String!, country: String, abv: Float, brewery: String): Beer 19 | deleteBeer(asin: String!): String 20 | } 21 | 22 | type Beer { 23 | asin: String! 24 | name: String! 25 | country: String 26 | abv: Float 27 | brewery: String 28 | } 29 | 30 | type Subscription { 31 | beerUpdated: Beer 32 | } 33 | `; 34 | 35 | const resolvers = { 36 | Query: { 37 | allBeers: () => beers, 38 | getBeer: (_, { asin }) => beers.find((beer) => beer.asin === asin), 39 | }, 40 | Mutation: { 41 | updateBeer: (_, { asin, name, country, abv, brewery }) => { 42 | const beerIndex = beers.findIndex((beer) => beer.asin === asin); 43 | if (beerIndex === -1) { 44 | throw new Error("Beer not found"); 45 | } 46 | beers[beerIndex] = { 47 | ...beers[beerIndex], 48 | name, 49 | country, 50 | abv, 51 | brewery, 52 | }; 53 | return beers[beerIndex]; 54 | }, 55 | deleteBeer: (_, { asin }) => { 56 | const beerIndex = beers.findIndex((beer) => beer.asin === asin); 57 | if (beerIndex === -1) { 58 | throw new Error("Beer not found"); 59 | } 60 | beers.splice(beerIndex, 1); 61 | return "Beer deleted successfully"; 62 | }, 63 | createBeer: (_, { asin, name, country, abv, brewery }) => { 64 | const newBeer = { 65 | asin, 66 | name, 67 | country, 68 | abv, 69 | brewery 70 | }; 71 | beers.push(newBeer); 72 | return newBeer; 73 | }, 74 | }, 75 | }; 76 | 77 | const server = new ApolloServer({ typeDefs, resolvers }); 78 | const { url } = await startStandaloneServer(server, { 79 | listen: { port: 4000 }, 80 | }); 81 | console.log(`🚀 Server ready at: ${url}`); 82 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !build/*-runner 3 | !build/*-runner.jar 4 | !build/lib/* 5 | !build/quarkus-app/* -------------------------------------------------------------------------------- /quarkus-beer-grpc/.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle 2 | .gradle/ 3 | build/ 4 | 5 | # Eclipse 6 | .project 7 | .classpath 8 | .settings/ 9 | bin/ 10 | 11 | # IntelliJ 12 | .idea 13 | *.ipr 14 | *.iml 15 | *.iws 16 | 17 | # NetBeans 18 | nb-configuration.xml 19 | 20 | # Visual Studio Code 21 | .vscode 22 | .factorypath 23 | 24 | # OSX 25 | .DS_Store 26 | 27 | # Vim 28 | *.swp 29 | *.swo 30 | 31 | # patch 32 | *.orig 33 | *.rej 34 | 35 | # Local environment 36 | .env 37 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/README.md: -------------------------------------------------------------------------------- 1 | # quarkus-beer-grpc Project 2 | 3 | This project uses Quarkus, the Supersonic Subatomic Java Framework. 4 | 5 | If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . 6 | 7 | ## Running the application in dev mode 8 | 9 | You can run your application in dev mode that enables live coding using: 10 | 11 | ```shell script 12 | ./gradlew quarkusDev 13 | ``` 14 | 15 | > **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/. 16 | 17 | ## Packaging and running the application 18 | 19 | The application can be packaged using: 20 | 21 | ```shell script 22 | ./gradlew build 23 | ``` 24 | 25 | It produces the `quarkus-run.jar` file in the `build/quarkus-app/` directory. Be aware that it’s not an _über-jar_ as 26 | the dependencies are copied into the `build/quarkus-app/lib/` directory. 27 | 28 | The application is now runnable using `java -jar build/quarkus-app/quarkus-run.jar`. 29 | 30 | If you want to build an _über-jar_, execute the following command: 31 | 32 | ```shell script 33 | ./gradlew build -Dquarkus.package.type=uber-jar 34 | ``` 35 | 36 | The application, packaged as an _über-jar_, is now runnable using `java -jar build/*-runner.jar`. 37 | 38 | ## Creating a native executable 39 | 40 | You can create a native executable using: 41 | 42 | ```shell script 43 | ./gradlew build -Dquarkus.package.type=native 44 | ``` 45 | 46 | Or, if you don't have GraalVM installed, you can run the native executable build in a container using: 47 | 48 | ```shell script 49 | ./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true 50 | ``` 51 | 52 | You can then execute your native executable with: `./build/quarkus-beer-grpc-1.0.0-SNAPSHOT-runner` 53 | 54 | If you want to learn more about building native executables, please consult https://quarkus.io/guides/gradle-tooling. 55 | 56 | ## Related Guides 57 | 58 | - RESTEasy JAX-RS ([guide](https://quarkus.io/guides/rest-json)): REST endpoint framework implementing JAX-RS and more 59 | 60 | ## Provided Code 61 | 62 | ### gRPC 63 | 64 | Create your first gRPC service 65 | 66 | [Related guide section...](https://quarkus.io/guides/grpc-getting-started) 67 | 68 | ### RESTEasy JAX-RS 69 | 70 | Easily start your RESTful Web Services 71 | 72 | [Related guide section...](https://quarkus.io/guides/getting-started#the-jax-rs-resources) 73 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'io.quarkus' 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | mavenLocal() 9 | } 10 | 11 | dependencies { 12 | implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") 13 | implementation 'io.quarkus:quarkus-grpc' 14 | implementation 'io.quarkus:quarkus-resteasy' 15 | implementation 'io.quarkus:quarkus-arc' 16 | implementation "io.quarkus:quarkus-smallrye-health" 17 | 18 | testImplementation 'io.quarkus:quarkus-junit5' 19 | testImplementation 'io.rest-assured:rest-assured' 20 | } 21 | 22 | group 'hands.on.grpc' 23 | version '1.0.0-SNAPSHOT' 24 | 25 | java { 26 | sourceCompatibility = JavaVersion.VERSION_11 27 | targetCompatibility = JavaVersion.VERSION_11 28 | } 29 | 30 | compileJava { 31 | options.encoding = 'UTF-8' 32 | options.compilerArgs << '-parameters' 33 | } 34 | 35 | compileTestJava { 36 | options.encoding = 'UTF-8' 37 | } 38 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/gradle.properties: -------------------------------------------------------------------------------- 1 | #Gradle properties 2 | quarkusPluginId=io.quarkus 3 | quarkusPluginVersion=2.7.4.Final 4 | quarkusPlatformGroupId=io.quarkus.platform 5 | quarkusPlatformArtifactId=quarkus-bom 6 | quarkusPlatformVersion=2.7.4.Final -------------------------------------------------------------------------------- /quarkus-beer-grpc/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lreimer/from-rest-to-grpc/4bee9f8579ad3f9802e5b618ec35351976c0d3d2/quarkus-beer-grpc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /quarkus-beer-grpc/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: grpc-beer-service 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: grpc-beer-service 10 | template: 11 | metadata: 12 | labels: 13 | app: grpc-beer-service 14 | spec: 15 | containers: 16 | - name: grpc-beer-service 17 | image: lreimer/quarkus-beer-grpc 18 | resources: 19 | requests: 20 | memory: "128Mi" 21 | cpu: "0.25" 22 | limits: 23 | memory: "256Mi" 24 | cpu: "2.0" 25 | ports: 26 | - name: http 27 | containerPort: 8080 28 | - name: grpc 29 | containerPort: 9090 30 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: grpc-beer-service 5 | spec: 6 | selector: 7 | app: grpc-beer-service 8 | type: ClusterIP 9 | sessionAffinity: ClientIP 10 | ports: 11 | - name: grpc 12 | protocol: TCP 13 | port: 9090 14 | targetPort: grpc 15 | - name: http 16 | protocol: TCP 17 | port: 8080 18 | targetPort: http 19 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | plugins { 8 | id "${quarkusPluginId}" version "${quarkusPluginVersion}" 9 | } 10 | } 11 | rootProject.name = 'quarkus-beer-grpc' 12 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/main/docker/Dockerfile.jvm: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./gradlew build 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/quarkus-beer-grpc-jvm . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-grpc-jvm 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-grpc-jvm 22 | # 23 | # This image uses the `run-java.sh` script to run the application. 24 | # This scripts computes the command line to execute your Java application, and 25 | # includes memory/GC tuning. 26 | # You can configure the behavior using the following environment properties: 27 | # - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") 28 | # - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options 29 | # in JAVA_OPTS (example: "-Dsome.property=foo") 30 | # - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is 31 | # used to calculate a default maximal heap memory based on a containers restriction. 32 | # If used in a container without any memory constraints for the container then this 33 | # option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio 34 | # of the container available memory as set here. The default is `50` which means 50% 35 | # of the available memory is used as an upper boundary. You can skip this mechanism by 36 | # setting this value to `0` in which case no `-Xmx` option is added. 37 | # - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This 38 | # is used to calculate a default initial heap memory based on the maximum heap memory. 39 | # If used in a container without any memory constraints for the container then this 40 | # option has no effect. If there is a memory constraint then `-Xms` is set to a ratio 41 | # of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` 42 | # is used as the initial heap size. You can skip this mechanism by setting this value 43 | # to `0` in which case no `-Xms` option is added (example: "25") 44 | # - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. 45 | # This is used to calculate the maximum value of the initial heap memory. If used in 46 | # a container without any memory constraints for the container then this option has 47 | # no effect. If there is a memory constraint then `-Xms` is limited to the value set 48 | # here. The default is 4096MB which means the calculated value of `-Xms` never will 49 | # be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") 50 | # - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output 51 | # when things are happening. This option, if set to true, will set 52 | # `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). 53 | # - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: 54 | # true"). 55 | # - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). 56 | # - CONTAINER_CORE_LIMIT: A calculated core limit as described in 57 | # https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") 58 | # - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). 59 | # - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. 60 | # (example: "20") 61 | # - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. 62 | # (example: "40") 63 | # - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. 64 | # (example: "4") 65 | # - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus 66 | # previous GC times. (example: "90") 67 | # - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") 68 | # - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") 69 | # - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should 70 | # contain the necessary JRE command-line options to specify the required GC, which 71 | # will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). 72 | # - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") 73 | # - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") 74 | # - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be 75 | # accessed directly. (example: "foo.example.com,bar.example.com") 76 | # 77 | ### 78 | FROM registry.access.redhat.com/ubi8/openjdk-11:1.11 79 | 80 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 81 | 82 | 83 | # We make four distinct layers so if there are application changes the library layers can be re-used 84 | COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/ 85 | COPY --chown=185 build/quarkus-app/*.jar /deployments/ 86 | COPY --chown=185 build/quarkus-app/app/ /deployments/app/ 87 | COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/ 88 | 89 | EXPOSE 8080 9000 9090 90 | USER 185 91 | ENV AB_JOLOKIA_OFF="" 92 | ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 93 | ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" 94 | 95 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/main/docker/Dockerfile.legacy-jar: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./gradlew build -Dquarkus.package.type=legacy-jar 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/quarkus-beer-grpc-legacy-jar . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-grpc-legacy-jar 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-grpc-legacy-jar 22 | # 23 | # This image uses the `run-java.sh` script to run the application. 24 | # This scripts computes the command line to execute your Java application, and 25 | # includes memory/GC tuning. 26 | # You can configure the behavior using the following environment properties: 27 | # - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") 28 | # - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options 29 | # in JAVA_OPTS (example: "-Dsome.property=foo") 30 | # - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is 31 | # used to calculate a default maximal heap memory based on a containers restriction. 32 | # If used in a container without any memory constraints for the container then this 33 | # option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio 34 | # of the container available memory as set here. The default is `50` which means 50% 35 | # of the available memory is used as an upper boundary. You can skip this mechanism by 36 | # setting this value to `0` in which case no `-Xmx` option is added. 37 | # - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This 38 | # is used to calculate a default initial heap memory based on the maximum heap memory. 39 | # If used in a container without any memory constraints for the container then this 40 | # option has no effect. If there is a memory constraint then `-Xms` is set to a ratio 41 | # of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` 42 | # is used as the initial heap size. You can skip this mechanism by setting this value 43 | # to `0` in which case no `-Xms` option is added (example: "25") 44 | # - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. 45 | # This is used to calculate the maximum value of the initial heap memory. If used in 46 | # a container without any memory constraints for the container then this option has 47 | # no effect. If there is a memory constraint then `-Xms` is limited to the value set 48 | # here. The default is 4096MB which means the calculated value of `-Xms` never will 49 | # be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") 50 | # - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output 51 | # when things are happening. This option, if set to true, will set 52 | # `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). 53 | # - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: 54 | # true"). 55 | # - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). 56 | # - CONTAINER_CORE_LIMIT: A calculated core limit as described in 57 | # https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") 58 | # - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). 59 | # - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. 60 | # (example: "20") 61 | # - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. 62 | # (example: "40") 63 | # - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. 64 | # (example: "4") 65 | # - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus 66 | # previous GC times. (example: "90") 67 | # - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") 68 | # - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") 69 | # - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should 70 | # contain the necessary JRE command-line options to specify the required GC, which 71 | # will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). 72 | # - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") 73 | # - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") 74 | # - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be 75 | # accessed directly. (example: "foo.example.com,bar.example.com") 76 | # 77 | ### 78 | FROM registry.access.redhat.com/ubi8/openjdk-11:1.11 79 | 80 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 81 | 82 | 83 | COPY build/lib/* /deployments/lib/ 84 | COPY build/*-runner.jar /deployments/quarkus-run.jar 85 | 86 | EXPOSE 8080 9000 9090 87 | USER 185 88 | ENV AB_JOLOKIA_OFF="" 89 | ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 90 | ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" 91 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/main/docker/Dockerfile.native: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./gradlew build -Dquarkus.package.type=native 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quarkus/quarkus-beer-grpc . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-grpc 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.5 18 | WORKDIR /work/ 19 | RUN chown 1001 /work \ 20 | && chmod "g+rwX" /work \ 21 | && chown 1001:root /work 22 | COPY --chown=1001:root build/*-runner /work/application 23 | 24 | EXPOSE 8080 9000 9090 25 | USER 1001 26 | 27 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] 28 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/main/docker/Dockerfile.native-micro: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # It uses a micro base image, tuned for Quarkus native executables. 4 | # It reduces the size of the resulting container image. 5 | # Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. 6 | # 7 | # Before building the container image run: 8 | # 9 | # ./gradlew build -Dquarkus.package.type=native 10 | # 11 | # Then, build the image with: 12 | # 13 | # docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/quarkus-beer-grpc . 14 | # 15 | # Then run the container using: 16 | # 17 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-grpc 18 | # 19 | ### 20 | FROM quay.io/quarkus/quarkus-micro-image:1.0 21 | WORKDIR /work/ 22 | RUN chown 1001 /work \ 23 | && chmod "g+rwX" /work \ 24 | && chown 1001:root /work 25 | COPY --chown=1001:root build/*-runner /work/application 26 | 27 | EXPOSE 8080 28 | USER 1001 29 | 30 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] 31 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/main/java/hands/on/grpc/BeerGrpcService.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.grpc.Status; 5 | import io.grpc.StatusException; 6 | import io.grpc.stub.StreamObserver; 7 | import io.quarkus.grpc.GrpcService; 8 | import io.quarkus.logging.Log; 9 | 10 | import javax.annotation.PostConstruct; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | import static hands.on.grpc.BeerProtos.*; 15 | 16 | @GrpcService 17 | public class BeerGrpcService extends BeerServiceGrpc.BeerServiceImplBase { 18 | 19 | private Map beers = new HashMap<>(); 20 | 21 | @PostConstruct 22 | void initialize() { 23 | beers.put("B079V9ZDNY", Beer.newBuilder().setAsin("B079V9ZDNY").setName("Drunken Sailor").setBrand("CREW Republic").setCountry("Germany").setAlcohol(6.4f).setType(Beer.BeerType.IndianPaleAle).build()); 24 | beers.put("B07B2YW1TW", Beer.newBuilder().setAsin("B07B2YW1TW").setName("Hop Junkie").setBrand("CREW Republic").setCountry("Germany").setAlcohol(3.4f).setType(Beer.BeerType.SessionIpa).build()); 25 | beers.put("B01AU6LWNC", Beer.newBuilder().setAsin("B01AU6LWNC").setName("Edelstoff Exportbier").setBrand("Augustiner Brauerei München").setCountry("Germany").setAlcohol(5.6f).setType(Beer.BeerType.Lager).build()); 26 | } 27 | 28 | @Override 29 | public void allBeers(Empty request, StreamObserver responseObserver) { 30 | Log.info("AllBeers gRPC API called"); 31 | GetBeersResponse.Builder builder = GetBeersResponse.newBuilder(); 32 | for (Beer beer : beers.values()) { 33 | builder.addBeers(beer); 34 | } 35 | 36 | responseObserver.onNext(builder.build()); 37 | responseObserver.onCompleted(); 38 | } 39 | 40 | @Override 41 | public void getBeer(GetBeerRequest request, StreamObserver responseObserver) { 42 | Log.info("GetBeers gRPC API called"); 43 | Beer beer = beers.get(request.getAsin()); 44 | if (beer == null) { 45 | responseObserver.onError(new StatusException(Status.NOT_FOUND)); 46 | } else { 47 | responseObserver.onNext(GetBeerResponse.newBuilder().setBeer(beer).build()); 48 | } 49 | responseObserver.onCompleted(); 50 | 51 | } 52 | 53 | @Override 54 | public void createBeer(CreateBeerRequest request, StreamObserver responseObserver) { 55 | Log.info("CreateBeer gRPC API called"); 56 | String asin = request.getBeer().getAsin(); 57 | if (beers.containsKey(asin)) { 58 | responseObserver.onError(new StatusException(Status.ALREADY_EXISTS)); 59 | } else { 60 | beers.put(asin, request.getBeer()); 61 | responseObserver.onNext(Empty.newBuilder().build()); 62 | } 63 | responseObserver.onCompleted(); 64 | } 65 | 66 | @Override 67 | public void updateBeer(UpdateBeerRequest request, StreamObserver responseObserver) { 68 | Log.info("UpdateBeer gRPC API called"); 69 | if (beers.containsKey(request.getAsin())) { 70 | beers.put(request.getAsin(), request.getBeer()); 71 | responseObserver.onNext(Empty.newBuilder().build()); 72 | } else { 73 | responseObserver.onError(new StatusException(Status.NOT_FOUND)); 74 | } 75 | responseObserver.onCompleted(); 76 | } 77 | 78 | @Override 79 | public void deleteBeer(DeleteBeerRequest request, StreamObserver responseObserver) { 80 | Log.info("DeleteBeer gRPC API called"); 81 | Beer removed = beers.remove(request.getAsin()); 82 | if (removed == null) { 83 | responseObserver.onError(new StatusException(Status.NOT_FOUND)); 84 | } else { 85 | responseObserver.onNext(Empty.newBuilder().build()); 86 | } 87 | responseObserver.onCompleted(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/main/java/hands/on/grpc/HelloGrpcService.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.quarkus.grpc.GrpcService; 4 | import io.quarkus.logging.Log; 5 | import io.smallrye.mutiny.Uni; 6 | 7 | @GrpcService 8 | public class HelloGrpcService implements HelloGrpc { 9 | 10 | @Override 11 | public Uni sayHello(HelloRequest request) { 12 | Log.info("Hello gRPC API called"); 13 | return Uni.createFrom().item("Hello " + request.getName() + "!") 14 | .map(msg -> HelloReply.newBuilder().setMessage(msg).build()); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/main/java/hands/on/grpc/HelloResource.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.quarkus.grpc.GrpcClient; 4 | import io.quarkus.logging.Log; 5 | 6 | import javax.ws.rs.GET; 7 | import javax.ws.rs.Path; 8 | import javax.ws.rs.Produces; 9 | import javax.ws.rs.core.MediaType; 10 | 11 | @Path("/hello") 12 | public class HelloResource { 13 | 14 | @GrpcClient("greeter") 15 | HelloGrpcGrpc.HelloGrpcBlockingStub stub; 16 | 17 | @GET 18 | @Produces(MediaType.TEXT_PLAIN) 19 | public String hello() { 20 | Log.info("Hello REST API called"); 21 | return stub.sayHello(HelloRequest.newBuilder().setName("JavaLand 2022").build()).getMessage(); 22 | } 23 | } -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/main/proto/beer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_package = "hands.on.grpc"; 4 | option java_outer_classname = "BeerProtos"; 5 | option optimize_for = SPEED; 6 | 7 | import "google/protobuf/empty.proto"; 8 | package beer; 9 | 10 | service BeerService { 11 | // Get the list of all beers 12 | rpc AllBeers (google.protobuf.Empty) returns (GetBeersResponse) {} 13 | // Get a single beer by Asin 14 | rpc GetBeer (GetBeerRequest) returns (GetBeerResponse) {} 15 | // Create a new beer 16 | rpc CreateBeer (CreateBeerRequest) returns (google.protobuf.Empty) {} 17 | // Update an existing beer 18 | rpc UpdateBeer (UpdateBeerRequest) returns (google.protobuf.Empty) {} 19 | // Delete an existing beeer 20 | rpc DeleteBeer (DeleteBeerRequest) returns (google.protobuf.Empty) {} 21 | } 22 | 23 | message Beer { 24 | string asin = 1; 25 | string name = 2; 26 | string brand = 3; 27 | string country = 4; 28 | float alcohol = 5; 29 | enum BeerType{ 30 | IndianPaleAle = 0; 31 | SessionIpa = 1; 32 | Lager = 2; 33 | } 34 | BeerType type = 6; 35 | } 36 | 37 | message GetBeersResponse { 38 | repeated Beer beers = 1; 39 | } 40 | 41 | message GetBeerRequest { 42 | string asin = 1; 43 | } 44 | 45 | message GetBeerResponse { 46 | Beer beer = 1; 47 | } 48 | 49 | message CreateBeerRequest { 50 | Beer beer = 1; 51 | } 52 | 53 | message UpdateBeerRequest { 54 | string asin = 1; 55 | Beer beer = 2; 56 | } 57 | 58 | message DeleteBeerRequest { 59 | string asin = 1; 60 | } -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/main/proto/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | option java_package = "hands.on.grpc"; 5 | option java_outer_classname = "HelloGrpcProto"; 6 | 7 | package hello; 8 | 9 | service HelloGrpc { 10 | rpc SayHello (HelloRequest) returns (HelloReply) {} 11 | } 12 | 13 | message HelloRequest { 14 | string name = 1; 15 | } 16 | 17 | message HelloReply { 18 | string message = 1; 19 | } 20 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.grpc.server.port=9090 2 | 3 | quarkus.grpc.clients.greeter.port=9090 4 | quarkus.grpc.clients.greeter.host=localhost 5 | quarkus.grpc.clients.beer.port=9090 6 | quarkus.grpc.clients.beer.host=localhost 7 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/native-test/java/hands/on/grpc/NativeHelloResourceIT.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.quarkus.test.junit.NativeImageTest; 4 | 5 | @NativeImageTest 6 | public class NativeHelloResourceIT extends HelloResourceTest { 7 | 8 | // Execute the same tests but in native mode. 9 | } -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/test/java/hands/on/grpc/BeerGrpcServiceTest.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import com.google.protobuf.Empty; 4 | import io.quarkus.grpc.GrpcClient; 5 | import io.quarkus.test.junit.QuarkusTest; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | @QuarkusTest 11 | public class BeerGrpcServiceTest { 12 | 13 | @GrpcClient("beer") 14 | BeerServiceGrpc.BeerServiceBlockingStub client; 15 | 16 | @Test 17 | public void testAllBeers() { 18 | BeerProtos.GetBeersResponse allBeers = client.allBeers(Empty.newBuilder().build()); 19 | assertEquals(3, allBeers.getBeersCount()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/test/java/hands/on/grpc/HelloGrpcServiceTest.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.quarkus.grpc.GrpcClient; 4 | import io.quarkus.test.junit.QuarkusTest; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.time.Duration; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | @QuarkusTest 12 | public class HelloGrpcServiceTest { 13 | 14 | @GrpcClient 15 | HelloGrpc helloGrpc; 16 | 17 | @Test 18 | public void testHello() { 19 | HelloReply reply = helloGrpc 20 | .sayHello(HelloRequest.newBuilder().setName("Neo").build()).await().atMost(Duration.ofSeconds(5)); 21 | assertEquals("Hello Neo!", reply.getMessage()); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /quarkus-beer-grpc/src/test/java/hands/on/grpc/HelloResourceTest.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.quarkus.test.junit.QuarkusTest; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static io.restassured.RestAssured.given; 7 | import static org.hamcrest.CoreMatchers.is; 8 | 9 | @QuarkusTest 10 | public class HelloResourceTest { 11 | 12 | @Test 13 | public void testHelloEndpoint() { 14 | given() 15 | .when().get("/hello") 16 | .then() 17 | .statusCode(200) 18 | .body(is("Hello JavaLand 2022!")); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /quarkus-beer-rest/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !build/*-runner 3 | !build/*-runner.jar 4 | !build/lib/* 5 | !build/quarkus-app/* -------------------------------------------------------------------------------- /quarkus-beer-rest/.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle 2 | .gradle/ 3 | build/ 4 | 5 | # Eclipse 6 | .project 7 | .classpath 8 | .settings/ 9 | bin/ 10 | 11 | # IntelliJ 12 | .idea 13 | *.ipr 14 | *.iml 15 | *.iws 16 | 17 | # NetBeans 18 | nb-configuration.xml 19 | 20 | # Visual Studio Code 21 | .vscode 22 | .factorypath 23 | 24 | # OSX 25 | .DS_Store 26 | 27 | # Vim 28 | *.swp 29 | *.swo 30 | 31 | # patch 32 | *.orig 33 | *.rej 34 | 35 | # Local environment 36 | .env 37 | -------------------------------------------------------------------------------- /quarkus-beer-rest/README.md: -------------------------------------------------------------------------------- 1 | # quarkus-beer-rest Project 2 | 3 | This project uses Quarkus, the Supersonic Subatomic Java Framework. 4 | 5 | If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . 6 | 7 | ## Running the application in dev mode 8 | 9 | You can run your application in dev mode that enables live coding using: 10 | 11 | ```shell script 12 | ./gradlew quarkusDev 13 | ``` 14 | 15 | > **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/. 16 | 17 | ## Packaging and running the application 18 | 19 | The application can be packaged using: 20 | 21 | ```shell script 22 | ./gradlew build 23 | ``` 24 | 25 | It produces the `quarkus-run.jar` file in the `build/quarkus-app/` directory. Be aware that it’s not an _über-jar_ as 26 | the dependencies are copied into the `build/quarkus-app/lib/` directory. 27 | 28 | The application is now runnable using `java -jar build/quarkus-app/quarkus-run.jar`. 29 | 30 | If you want to build an _über-jar_, execute the following command: 31 | 32 | ```shell script 33 | ./gradlew build -Dquarkus.package.type=uber-jar 34 | ``` 35 | 36 | The application, packaged as an _über-jar_, is now runnable using `java -jar build/*-runner.jar`. 37 | 38 | ## Creating a native executable 39 | 40 | You can create a native executable using: 41 | 42 | ```shell script 43 | ./gradlew build -Dquarkus.package.type=native 44 | ``` 45 | 46 | Or, if you don't have GraalVM installed, you can run the native executable build in a container using: 47 | 48 | ```shell script 49 | ./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true 50 | ``` 51 | 52 | You can then execute your native executable with: `./build/quarkus-beer-rest-1.0.0-SNAPSHOT-runner` 53 | 54 | If you want to learn more about building native executables, please consult https://quarkus.io/guides/gradle-tooling. 55 | 56 | ## Related Guides 57 | 58 | - RESTEasy JAX-RS ([guide](https://quarkus.io/guides/rest-json)): REST endpoint framework implementing JAX-RS and more 59 | 60 | ## Provided Code 61 | 62 | ### RESTEasy JAX-RS 63 | 64 | Easily start your RESTful Web Services 65 | 66 | [Related guide section...](https://quarkus.io/guides/getting-started#the-jax-rs-resources) 67 | -------------------------------------------------------------------------------- /quarkus-beer-rest/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'io.quarkus' 4 | id 'idea' 5 | id "com.google.protobuf" version "0.8.18" 6 | } 7 | 8 | repositories { 9 | mavenCentral() 10 | mavenLocal() 11 | } 12 | 13 | dependencies { 14 | implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") 15 | implementation 'io.quarkus:quarkus-resteasy-jsonb' 16 | implementation 'io.quarkus:quarkus-resteasy' 17 | implementation "io.quarkus:quarkus-smallrye-health" 18 | implementation 'io.quarkus:quarkus-arc' 19 | 20 | implementation 'com.google.protobuf:protobuf-java:3.19.4' 21 | 22 | implementation "org.mapstruct:mapstruct:1.4.2.Final" 23 | // annotationProcessor "org.mapstruct:mapstruct-processor:1.4.2.Final" 24 | 25 | testImplementation 'io.quarkus:quarkus-junit5' 26 | testImplementation 'io.rest-assured:rest-assured' 27 | } 28 | 29 | group 'hands.on.grpc' 30 | version '1.0.0-SNAPSHOT' 31 | 32 | java { 33 | sourceCompatibility = JavaVersion.VERSION_11 34 | targetCompatibility = JavaVersion.VERSION_11 35 | } 36 | 37 | compileJava { 38 | options.encoding = 'UTF-8' 39 | options.compilerArgs << '-parameters' 40 | } 41 | 42 | compileTestJava { 43 | options.encoding = 'UTF-8' 44 | } 45 | 46 | protobuf { 47 | // Configure the protoc executable 48 | protoc { 49 | // Download from repositories 50 | artifact = 'com.google.protobuf:protoc:3.19.4' 51 | } 52 | } 53 | 54 | sourceSets.main.java.srcDirs += ['build/generated/source/proto/main/java'] 55 | -------------------------------------------------------------------------------- /quarkus-beer-rest/gradle.properties: -------------------------------------------------------------------------------- 1 | #Gradle properties 2 | quarkusPluginId=io.quarkus 3 | quarkusPluginVersion=2.7.4.Final 4 | quarkusPlatformGroupId=io.quarkus.platform 5 | quarkusPlatformArtifactId=quarkus-bom 6 | quarkusPlatformVersion=2.7.4.Final -------------------------------------------------------------------------------- /quarkus-beer-rest/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lreimer/from-rest-to-grpc/4bee9f8579ad3f9802e5b618ec35351976c0d3d2/quarkus-beer-rest/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /quarkus-beer-rest/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /quarkus-beer-rest/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /quarkus-beer-rest/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: rest-beer-service 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: rest-beer-service 10 | template: 11 | metadata: 12 | labels: 13 | app: rest-beer-service 14 | spec: 15 | containers: 16 | - name: rest-beer-service 17 | image: lreimer/quarkus-beer-rest 18 | resources: 19 | requests: 20 | memory: "128Mi" 21 | cpu: "0.25" 22 | limits: 23 | memory: "256Mi" 24 | cpu: "2.0" 25 | ports: 26 | - name: http 27 | containerPort: 8080 28 | -------------------------------------------------------------------------------- /quarkus-beer-rest/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: rest-beer-service 5 | spec: 6 | selector: 7 | app: rest-beer-service 8 | type: ClusterIP 9 | sessionAffinity: None 10 | ports: 11 | - protocol: TCP 12 | port: 8080 13 | targetPort: http 14 | -------------------------------------------------------------------------------- /quarkus-beer-rest/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | plugins { 8 | id "${quarkusPluginId}" version "${quarkusPluginVersion}" 9 | } 10 | } 11 | rootProject.name = 'quarkus-beer-rest' 12 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/docker/Dockerfile.jvm: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./gradlew build 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/quarkus-beer-rest-jvm . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-rest-jvm 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-rest-jvm 22 | # 23 | # This image uses the `run-java.sh` script to run the application. 24 | # This scripts computes the command line to execute your Java application, and 25 | # includes memory/GC tuning. 26 | # You can configure the behavior using the following environment properties: 27 | # - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") 28 | # - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options 29 | # in JAVA_OPTS (example: "-Dsome.property=foo") 30 | # - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is 31 | # used to calculate a default maximal heap memory based on a containers restriction. 32 | # If used in a container without any memory constraints for the container then this 33 | # option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio 34 | # of the container available memory as set here. The default is `50` which means 50% 35 | # of the available memory is used as an upper boundary. You can skip this mechanism by 36 | # setting this value to `0` in which case no `-Xmx` option is added. 37 | # - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This 38 | # is used to calculate a default initial heap memory based on the maximum heap memory. 39 | # If used in a container without any memory constraints for the container then this 40 | # option has no effect. If there is a memory constraint then `-Xms` is set to a ratio 41 | # of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` 42 | # is used as the initial heap size. You can skip this mechanism by setting this value 43 | # to `0` in which case no `-Xms` option is added (example: "25") 44 | # - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. 45 | # This is used to calculate the maximum value of the initial heap memory. If used in 46 | # a container without any memory constraints for the container then this option has 47 | # no effect. If there is a memory constraint then `-Xms` is limited to the value set 48 | # here. The default is 4096MB which means the calculated value of `-Xms` never will 49 | # be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") 50 | # - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output 51 | # when things are happening. This option, if set to true, will set 52 | # `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). 53 | # - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: 54 | # true"). 55 | # - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). 56 | # - CONTAINER_CORE_LIMIT: A calculated core limit as described in 57 | # https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") 58 | # - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). 59 | # - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. 60 | # (example: "20") 61 | # - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. 62 | # (example: "40") 63 | # - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. 64 | # (example: "4") 65 | # - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus 66 | # previous GC times. (example: "90") 67 | # - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") 68 | # - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") 69 | # - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should 70 | # contain the necessary JRE command-line options to specify the required GC, which 71 | # will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). 72 | # - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") 73 | # - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") 74 | # - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be 75 | # accessed directly. (example: "foo.example.com,bar.example.com") 76 | # 77 | ### 78 | FROM registry.access.redhat.com/ubi8/openjdk-11:1.11 79 | 80 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 81 | 82 | 83 | # We make four distinct layers so if there are application changes the library layers can be re-used 84 | COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/ 85 | COPY --chown=185 build/quarkus-app/*.jar /deployments/ 86 | COPY --chown=185 build/quarkus-app/app/ /deployments/app/ 87 | COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/ 88 | 89 | EXPOSE 8080 90 | USER 185 91 | ENV AB_JOLOKIA_OFF="" 92 | ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 93 | ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" 94 | 95 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/docker/Dockerfile.legacy-jar: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./gradlew build -Dquarkus.package.type=legacy-jar 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/quarkus-beer-rest-legacy-jar . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-rest-legacy-jar 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-rest-legacy-jar 22 | # 23 | # This image uses the `run-java.sh` script to run the application. 24 | # This scripts computes the command line to execute your Java application, and 25 | # includes memory/GC tuning. 26 | # You can configure the behavior using the following environment properties: 27 | # - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") 28 | # - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options 29 | # in JAVA_OPTS (example: "-Dsome.property=foo") 30 | # - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is 31 | # used to calculate a default maximal heap memory based on a containers restriction. 32 | # If used in a container without any memory constraints for the container then this 33 | # option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio 34 | # of the container available memory as set here. The default is `50` which means 50% 35 | # of the available memory is used as an upper boundary. You can skip this mechanism by 36 | # setting this value to `0` in which case no `-Xmx` option is added. 37 | # - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This 38 | # is used to calculate a default initial heap memory based on the maximum heap memory. 39 | # If used in a container without any memory constraints for the container then this 40 | # option has no effect. If there is a memory constraint then `-Xms` is set to a ratio 41 | # of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` 42 | # is used as the initial heap size. You can skip this mechanism by setting this value 43 | # to `0` in which case no `-Xms` option is added (example: "25") 44 | # - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. 45 | # This is used to calculate the maximum value of the initial heap memory. If used in 46 | # a container without any memory constraints for the container then this option has 47 | # no effect. If there is a memory constraint then `-Xms` is limited to the value set 48 | # here. The default is 4096MB which means the calculated value of `-Xms` never will 49 | # be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") 50 | # - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output 51 | # when things are happening. This option, if set to true, will set 52 | # `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). 53 | # - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: 54 | # true"). 55 | # - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). 56 | # - CONTAINER_CORE_LIMIT: A calculated core limit as described in 57 | # https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") 58 | # - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). 59 | # - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. 60 | # (example: "20") 61 | # - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. 62 | # (example: "40") 63 | # - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. 64 | # (example: "4") 65 | # - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus 66 | # previous GC times. (example: "90") 67 | # - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") 68 | # - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") 69 | # - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should 70 | # contain the necessary JRE command-line options to specify the required GC, which 71 | # will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). 72 | # - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") 73 | # - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") 74 | # - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be 75 | # accessed directly. (example: "foo.example.com,bar.example.com") 76 | # 77 | ### 78 | FROM registry.access.redhat.com/ubi8/openjdk-11:1.11 79 | 80 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 81 | 82 | 83 | COPY build/lib/* /deployments/lib/ 84 | COPY build/*-runner.jar /deployments/quarkus-run.jar 85 | 86 | EXPOSE 8080 87 | USER 185 88 | ENV AB_JOLOKIA_OFF="" 89 | ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 90 | ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" 91 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/docker/Dockerfile.native: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./gradlew build -Dquarkus.package.type=native 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quarkus/quarkus-beer-rest . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-rest 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.5 18 | WORKDIR /work/ 19 | RUN chown 1001 /work \ 20 | && chmod "g+rwX" /work \ 21 | && chown 1001:root /work 22 | COPY --chown=1001:root build/*-runner /work/application 23 | 24 | EXPOSE 8080 25 | USER 1001 26 | 27 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] 28 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/docker/Dockerfile.native-micro: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # It uses a micro base image, tuned for Quarkus native executables. 4 | # It reduces the size of the resulting container image. 5 | # Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. 6 | # 7 | # Before building the container image run: 8 | # 9 | # ./gradlew build -Dquarkus.package.type=native 10 | # 11 | # Then, build the image with: 12 | # 13 | # docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/quarkus-beer-rest . 14 | # 15 | # Then run the container using: 16 | # 17 | # docker run -i --rm -p 8080:8080 quarkus/quarkus-beer-rest 18 | # 19 | ### 20 | FROM quay.io/quarkus/quarkus-micro-image:1.0 21 | WORKDIR /work/ 22 | RUN chown 1001 /work \ 23 | && chmod "g+rwX" /work \ 24 | && chown 1001:root /work 25 | COPY --chown=1001:root build/*-runner /work/application 26 | 27 | EXPOSE 8080 28 | USER 1001 29 | 30 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] 31 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/java/hands/on/grpc/Beer.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import java.util.Objects; 4 | 5 | public class Beer { 6 | 7 | public enum Type { 8 | IndianPaleAle, SessionIpa, Lager 9 | } 10 | 11 | private String asin; 12 | private String name; 13 | private String brand; 14 | private String country; 15 | private float alcohol; 16 | private Type type; 17 | 18 | public Beer() { 19 | } 20 | 21 | public Beer(String asin, String name, String brand, String country, float alcohol, Type type) { 22 | this.asin = asin; 23 | this.name = name; 24 | this.brand = brand; 25 | this.country = country; 26 | this.alcohol = alcohol; 27 | this.type = type; 28 | } 29 | 30 | public String getAsin() { 31 | return asin; 32 | } 33 | 34 | public void setAsin(String asin) { 35 | this.asin = asin; 36 | } 37 | 38 | public String getName() { 39 | return name; 40 | } 41 | 42 | public void setName(String name) { 43 | this.name = name; 44 | } 45 | 46 | public String getBrand() { 47 | return brand; 48 | } 49 | 50 | public void setBrand(String brand) { 51 | this.brand = brand; 52 | } 53 | 54 | public String getCountry() { 55 | return country; 56 | } 57 | 58 | public void setCountry(String country) { 59 | this.country = country; 60 | } 61 | 62 | public float getAlcohol() { 63 | return alcohol; 64 | } 65 | 66 | public void setAlcohol(float alcohol) { 67 | this.alcohol = alcohol; 68 | } 69 | 70 | public Type getType() { 71 | return type; 72 | } 73 | 74 | public void setType(Type type) { 75 | this.type = type; 76 | } 77 | 78 | @Override 79 | public boolean equals(Object o) { 80 | if (this == o) return true; 81 | if (o == null || getClass() != o.getClass()) return false; 82 | Beer that = (Beer) o; 83 | return Float.compare(that.alcohol, alcohol) == 0 && Objects.equals(asin, that.asin) && Objects.equals(name, that.name) && Objects.equals(brand, that.brand) && Objects.equals(country, that.country) && type == that.type; 84 | } 85 | 86 | @Override 87 | public int hashCode() { 88 | return Objects.hash(asin, name, brand, country, alcohol, type); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/java/hands/on/grpc/BeerApplication.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import javax.ws.rs.ApplicationPath; 4 | import javax.ws.rs.core.Application; 5 | 6 | @ApplicationPath("api") 7 | public class BeerApplication extends Application { 8 | } 9 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/java/hands/on/grpc/BeerRepository.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import javax.annotation.PostConstruct; 4 | import javax.enterprise.context.ApplicationScoped; 5 | import javax.ws.rs.NotFoundException; 6 | import javax.ws.rs.WebApplicationException; 7 | import javax.ws.rs.core.Response; 8 | import java.util.Collection; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import static hands.on.grpc.Beer.Type.*; 13 | 14 | @ApplicationScoped 15 | public class BeerRepository { 16 | 17 | Map beers = new HashMap<>(); 18 | 19 | public BeerRepository() { 20 | } 21 | 22 | @PostConstruct 23 | public void initialize() { 24 | beers.put("B079V9ZDNY", new Beer("B079V9ZDNY", "Drunken Sailor", "CREW Republic", "Germany", 6.4f, IndianPaleAle)); 25 | beers.put("B07B2YW1TW", new Beer("B07B2YW1TW", "Hop Junkie", "CREW Republic", "Germany", 3.4f, SessionIpa)); 26 | beers.put("B01AU6LWNC", new Beer("B01AU6LWNC", "Edelstoff Exportbier", "Augustiner Brauerei München", "Germany", 5.6f, Lager)); 27 | } 28 | 29 | public Collection all() { 30 | return beers.values(); 31 | } 32 | 33 | public String create(Beer beer) { 34 | String asin = beer.getAsin(); 35 | if (beers.containsKey(asin)) { 36 | throw new WebApplicationException("Beer already exists.", Response.Status.CONFLICT); 37 | } 38 | beers.put(asin, beer); 39 | return asin; 40 | } 41 | 42 | 43 | public Beer find(String asin) { 44 | Beer beer = beers.get(asin); 45 | if (beer == null) { 46 | throw new NotFoundException("Beer not found"); 47 | } 48 | return beer; 49 | } 50 | 51 | public void update(String asin, Beer beer) { 52 | beers.put(asin, beer); 53 | } 54 | 55 | 56 | public void delete(String asin) { 57 | Beer beer = beers.remove(asin); 58 | if (beer == null) { 59 | throw new NotFoundException("Beer not found"); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/java/hands/on/grpc/BeerResource.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import javax.inject.Inject; 4 | import javax.ws.rs.*; 5 | import javax.ws.rs.core.MediaType; 6 | import javax.ws.rs.core.Response; 7 | import javax.ws.rs.core.UriBuilder; 8 | 9 | import io.quarkus.logging.Log; 10 | 11 | import java.net.URI; 12 | import java.util.Objects; 13 | 14 | @Path("/beers") 15 | public class BeerResource { 16 | 17 | @Inject 18 | BeerRepository repository; 19 | 20 | @GET 21 | @Produces(MediaType.APPLICATION_JSON) 22 | public Response all() { 23 | Log.info("Getting all JSON beers"); 24 | return Response.ok(repository.all()).build(); 25 | } 26 | 27 | @POST 28 | @Consumes(MediaType.APPLICATION_JSON) 29 | public Response create(Beer beer) { 30 | String asin = repository.create(beer); 31 | URI location = UriBuilder.fromResource(BeerResource.class) 32 | .path("/{asin}") 33 | .resolveTemplate("asin", asin) 34 | .build(); 35 | return Response.created(location).build(); 36 | } 37 | 38 | @GET 39 | @Path("/{asin}") 40 | @Produces(MediaType.APPLICATION_JSON) 41 | public Response get(@PathParam("asin") String asin) { 42 | Beer beer = repository.find(asin); 43 | return Response.ok(beer).build(); 44 | } 45 | 46 | @PUT 47 | @Path("/{asin}") 48 | @Produces(MediaType.APPLICATION_JSON) 49 | public Response update(@PathParam("asin") String asin, Beer beer) { 50 | if (!Objects.equals(asin, beer.getAsin())) { 51 | // return Response.status(Response.Status.BAD_REQUEST).build(); 52 | // throw new WebApplicationException("ASIN must match path parameter.", Response.Status.BAD_REQUEST); 53 | throw new BadRequestException("ASIN must match path parameter."); 54 | } 55 | repository.update(asin, beer); 56 | return Response.ok().build(); 57 | } 58 | 59 | @DELETE 60 | @Path("/{asin}") 61 | public Response delete(@PathParam("asin") String asin) { 62 | repository.delete(asin); 63 | return Response.ok().build(); 64 | } 65 | } -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/java/hands/on/grpc/ProtoMapper.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import org.mapstruct.Mapper; 4 | import org.mapstruct.factory.Mappers; 5 | 6 | @Mapper 7 | public interface ProtoMapper { 8 | ProtoMapper INSTANCE = Mappers.getMapper(ProtoMapper.class); 9 | 10 | Beer protoBeerToBeer(BeerProtos.Beer beer); 11 | 12 | default Beer.Type protoBeerTypeToBeerType(BeerProtos.Beer.BeerType beerType) { 13 | return Beer.Type.valueOf(beerType.name()); 14 | } 15 | 16 | BeerProtos.Beer beerToProtoBeer(Beer beer); 17 | 18 | default BeerProtos.Beer.BeerType beerTypeToProtoBeerType(Beer.Type beerType) { 19 | return BeerProtos.Beer.BeerType.valueOf(beerType.name()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/java/hands/on/grpc/ProtoResource.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import hands.on.grpc.protobuf.ProtocolBufferMediaType; 4 | import io.quarkus.logging.Log; 5 | 6 | import javax.inject.Inject; 7 | import javax.ws.rs.Consumes; 8 | import javax.ws.rs.POST; 9 | import javax.ws.rs.Path; 10 | import javax.ws.rs.Produces; 11 | import javax.ws.rs.core.Response; 12 | 13 | import static hands.on.grpc.BeerProtos.*; 14 | 15 | @Path("/proto") 16 | @Consumes(ProtocolBufferMediaType.APPLICATION_PROTOBUF) 17 | @Produces(ProtocolBufferMediaType.APPLICATION_PROTOBUF) 18 | public class ProtoResource { 19 | @Inject 20 | BeerRepository repository; 21 | 22 | @POST 23 | public GetBeersResponse getBeers(GetBeersRequest request) { 24 | // TODO add individual beers to response 25 | Log.info("Getting all Protobuf beers"); 26 | return GetBeersResponse.newBuilder().build(); 27 | } 28 | 29 | @POST 30 | public GetBeerResponse getBeer(GetBeerRequest request) { 31 | Beer beer = repository.find(request.getAsin()); 32 | return GetBeerResponse.newBuilder() 33 | .setBeer(BeerProtos.Beer.newBuilder() 34 | .setAsin(beer.getAsin()) 35 | .setName(beer.getName()) 36 | .setBrand(beer.getBrand()) 37 | .setCountry(beer.getCountry()) 38 | .setAlcohol(beer.getAlcohol()) 39 | .setType(BeerProtos.Beer.BeerType.valueOf(beer.getType().name())) 40 | ).build(); 41 | } 42 | 43 | @POST 44 | public Response createBeer(CreateBeerRequest request) { 45 | // maybe implement mapping using MapStruct 46 | repository.create(new Beer( 47 | request.getBeer().getAsin(), 48 | request.getBeer().getName(), 49 | request.getBeer().getBrand(), 50 | request.getBeer().getCountry(), 51 | request.getBeer().getAlcohol(), 52 | Beer.Type.valueOf(request.getBeer().getType().name()))); 53 | return Response.ok().build(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/java/hands/on/grpc/protobuf/InvalidProtocolBufferExceptionMapper.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc.protobuf; 2 | 3 | import com.google.protobuf.InvalidProtocolBufferException; 4 | 5 | import javax.ws.rs.core.Response; 6 | import javax.ws.rs.ext.ExceptionMapper; 7 | import javax.ws.rs.ext.Provider; 8 | 9 | @Provider 10 | public class InvalidProtocolBufferExceptionMapper implements ExceptionMapper { 11 | @Override 12 | public Response toResponse(InvalidProtocolBufferException e) { 13 | return Response.status(Response.Status.BAD_REQUEST).build(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/java/hands/on/grpc/protobuf/ProtocolBufferMediaType.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc.protobuf; 2 | 3 | import javax.ws.rs.core.MediaType; 4 | 5 | /** 6 | * Custom MediaType subclass for the Protobuf media types. 7 | */ 8 | public class ProtocolBufferMediaType extends MediaType { 9 | 10 | /** 11 | * "application/x-protobuf" 12 | */ 13 | public static final String APPLICATION_PROTOBUF = "application/x-protobuf"; 14 | 15 | /** 16 | * "application/x-protobuf" 17 | */ 18 | public static final MediaType APPLICATION_PROTOBUF_TYPE = new MediaType("application", "x-protobuf"); 19 | 20 | /** 21 | * "application/x-protobuf-text-format" 22 | */ 23 | public static final String APPLICATION_PROTOBUF_TEXT = "application/x-protobuf-text-format"; 24 | 25 | /** 26 | * "application/x-protobuf-text-format" 27 | */ 28 | public static final MediaType APPLICATION_PROTOBUF_TEXT_TYPE = new MediaType("application", "x-protobuf-text-format"); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/java/hands/on/grpc/protobuf/ProtocolBufferMessageBodyReader.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc.protobuf; 2 | 3 | import com.google.protobuf.Message; 4 | import com.google.protobuf.TextFormat; 5 | 6 | import javax.ws.rs.Consumes; 7 | import javax.ws.rs.WebApplicationException; 8 | import javax.ws.rs.core.MediaType; 9 | import javax.ws.rs.core.MultivaluedMap; 10 | import javax.ws.rs.ext.MessageBodyReader; 11 | import javax.ws.rs.ext.Provider; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.InputStreamReader; 15 | import java.lang.annotation.Annotation; 16 | import java.lang.reflect.Method; 17 | import java.lang.reflect.Type; 18 | import java.nio.charset.StandardCharsets; 19 | import java.util.Map; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | 22 | /** 23 | * A JAX-RS provider to parse Protocol Buffers request entities into objects. 24 | */ 25 | @Provider 26 | @Consumes({ 27 | ProtocolBufferMediaType.APPLICATION_PROTOBUF, 28 | ProtocolBufferMediaType.APPLICATION_PROTOBUF_TEXT 29 | }) 30 | public class ProtocolBufferMessageBodyReader implements MessageBodyReader { 31 | private final Map, Method> methodCache = new ConcurrentHashMap<>(); 32 | 33 | @Override 34 | public boolean isReadable(final Class type, final Type genericType, 35 | final Annotation[] annotations, final MediaType mediaType) { 36 | return Message.class.isAssignableFrom(type); 37 | } 38 | 39 | @Override 40 | public Message readFrom(final Class type, final Type genericType, final Annotation[] annotations, 41 | final MediaType mediaType, final MultivaluedMap httpHeaders, 42 | final InputStream entityStream) throws IOException { 43 | 44 | final Method newBuilder = 45 | methodCache.computeIfAbsent( 46 | type, 47 | t -> { 48 | try { 49 | return t.getMethod("newBuilder"); 50 | } catch (Exception e) { 51 | return null; 52 | } 53 | }); 54 | 55 | final Message.Builder builder; 56 | try { 57 | builder = (Message.Builder) newBuilder.invoke(type); 58 | } catch (Exception e) { 59 | throw new WebApplicationException(e); 60 | } 61 | if (mediaType.getSubtype().contains("text-format")) { 62 | TextFormat.merge(new InputStreamReader(entityStream, StandardCharsets.UTF_8), builder); 63 | return builder.build(); 64 | } else { 65 | return builder.mergeFrom(entityStream).build(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/java/hands/on/grpc/protobuf/ProtocolBufferMessageBodyWriter.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc.protobuf; 2 | 3 | import com.google.protobuf.Message; 4 | import com.google.protobuf.TextFormat; 5 | 6 | import javax.ws.rs.Produces; 7 | import javax.ws.rs.core.MediaType; 8 | import javax.ws.rs.core.MultivaluedMap; 9 | import javax.ws.rs.ext.MessageBodyWriter; 10 | import javax.ws.rs.ext.Provider; 11 | import java.io.IOException; 12 | import java.io.OutputStream; 13 | import java.lang.annotation.Annotation; 14 | import java.lang.reflect.Type; 15 | import java.nio.charset.StandardCharsets; 16 | 17 | /** 18 | * A JAX-RS provider to produce and write Protocol Buffers response entities. 19 | */ 20 | @Provider 21 | @Produces({ 22 | ProtocolBufferMediaType.APPLICATION_PROTOBUF, 23 | ProtocolBufferMediaType.APPLICATION_PROTOBUF_TEXT 24 | }) 25 | public class ProtocolBufferMessageBodyWriter implements MessageBodyWriter { 26 | 27 | @Override 28 | public boolean isWriteable(final Class type, final Type genericType, 29 | final Annotation[] annotations, final MediaType mediaType) { 30 | return Message.class.isAssignableFrom(type); 31 | } 32 | 33 | @Override 34 | public void writeTo(final Message m, final Class type, final Type genericType, final Annotation[] annotations, 35 | final MediaType mediaType, final MultivaluedMap httpHeaders, 36 | final OutputStream entityStream) 37 | throws IOException { 38 | 39 | if (mediaType.getSubtype().contains("text-format")) { 40 | entityStream.write(m.toString().getBytes(StandardCharsets.UTF_8)); 41 | } else { 42 | m.writeTo(entityStream); 43 | } 44 | } 45 | 46 | @Override 47 | public long getSize(final Message m, final Class type, final Type genericType, 48 | final Annotation[] annotations, final MediaType mediaType) { 49 | 50 | if (mediaType.getSubtype().contains("text-format")) { 51 | final String formatted = TextFormat.printer().escapingNonAscii(false).printToString(m); 52 | return formatted.getBytes(StandardCharsets.UTF_8).length; 53 | } 54 | 55 | return m.getSerializedSize(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/proto/beer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package beer; 4 | 5 | option java_package = "hands.on.grpc"; 6 | option java_outer_classname = "BeerProtos"; 7 | option optimize_for = SPEED; 8 | 9 | message Beer { 10 | string asin = 1; 11 | string name = 2; 12 | string brand = 3; 13 | string country = 4; 14 | float alcohol = 5; 15 | enum BeerType { 16 | IndianPaleAle = 0; 17 | SessionIpa = 1; 18 | Lager = 2; 19 | } 20 | BeerType type = 6; 21 | } 22 | 23 | message GetBeersRequest { 24 | } 25 | 26 | message GetBeersResponse { 27 | repeated Beer beers = 1; 28 | } 29 | 30 | message GetBeerRequest { 31 | string asin = 1; 32 | } 33 | 34 | message GetBeerResponse { 35 | Beer beer = 1; 36 | } 37 | 38 | message CreateBeerRequest { 39 | Beer beer = 1; 40 | } 41 | 42 | message UpdateBeerRequest { 43 | Beer beer = 1; 44 | } 45 | 46 | message DeleteBeerRequest { 47 | string asin = 1; 48 | } 49 | -------------------------------------------------------------------------------- /quarkus-beer-rest/src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lreimer/from-rest-to-grpc/4bee9f8579ad3f9802e5b618ec35351976c0d3d2/quarkus-beer-rest/src/main/resources/application.properties -------------------------------------------------------------------------------- /quarkus-beer-rest/src/native-test/java/hands/on/grpc/NativeBeerResourceIT.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.quarkus.test.junit.NativeImageTest; 4 | 5 | @NativeImageTest 6 | public class NativeBeerResourceIT extends BeerResourceTest { 7 | 8 | // Execute the same tests but in native mode. 9 | } -------------------------------------------------------------------------------- /quarkus-beer-rest/src/test/java/hands/on/grpc/BeerResourceTest.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import io.quarkus.test.junit.QuarkusTest; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static io.restassured.RestAssured.given; 7 | 8 | @QuarkusTest 9 | public class BeerResourceTest { 10 | 11 | @Test 12 | public void testBeerEndpoint() { 13 | given() 14 | .when().get("/api/beers") 15 | .then() 16 | .statusCode(200); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /quarkus-beer-rest/src/test/java/hands/on/grpc/ProtoMapperTest.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static hands.on.grpc.Beer.Type.IndianPaleAle; 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | class ProtoMapperTest { 10 | 11 | @Test 12 | @Disabled("Problem with MapStruct annotation processor") 13 | void beerToProtoBeer() { 14 | ProtoMapper mapper = ProtoMapper.INSTANCE; 15 | BeerProtos.Beer protoBeer = mapper.beerToProtoBeer(new Beer("B079V9ZDNY", "Drunken Sailor", "CREW Republic", "Germany", 6.4f, IndianPaleAle)); 16 | assertEquals("B079V9ZDNY", protoBeer.getAsin()); 17 | } 18 | 19 | @Test 20 | @Disabled("Problem with MapStruct annotation processor") 21 | void protoBeerToBeer() { 22 | ProtoMapper mapper = ProtoMapper.INSTANCE; 23 | BeerProtos.Beer protoBeer = BeerProtos.Beer.newBuilder().setAsin("B079V9ZDNY").build(); 24 | Beer beer = mapper.protoBeerToBeer(protoBeer); 25 | assertEquals("B079V9ZDNY", beer.getAsin()); 26 | } 27 | } -------------------------------------------------------------------------------- /quarkus-beer-rest/src/test/java/hands/on/grpc/ProtoResourceTest.java: -------------------------------------------------------------------------------- 1 | package hands.on.grpc; 2 | 3 | import hands.on.grpc.protobuf.ProtocolBufferMediaType; 4 | import io.quarkus.test.junit.QuarkusTest; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static io.restassured.RestAssured.given; 8 | 9 | @QuarkusTest 10 | public class ProtoResourceTest { 11 | 12 | @Test 13 | public void testBeersEndpoint() { 14 | given() 15 | .contentType(ProtocolBufferMediaType.APPLICATION_PROTOBUF) 16 | .accept(ProtocolBufferMediaType.APPLICATION_PROTOBUF) 17 | .body(BeerProtos.GetBeersRequest.newBuilder().build().toByteArray()) 18 | .when().post("/api/proto") 19 | .then() 20 | .statusCode(200); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /rest-beer-client/Beer-Service.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "1f34be6e-f124-407f-ae88-b9fedcb8c2ad", 4 | "name": "Beer Service", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Get list of beers", 10 | "request": { 11 | "method": "GET", 12 | "header": [], 13 | "url": { 14 | "raw": "http://localhost:18080/api/beers", 15 | "protocol": "http", 16 | "host": [ 17 | "localhost" 18 | ], 19 | "port": "18080", 20 | "path": [ 21 | "api", 22 | "beers" 23 | ] 24 | } 25 | }, 26 | "response": [] 27 | }, 28 | { 29 | "name": "Create new beer", 30 | "request": { 31 | "method": "POST", 32 | "header": [], 33 | "body": { 34 | "mode": "raw", 35 | "raw": "{\n \"asin\": \"B01CJPU3XW\",\n \"name\": \"Blauer August\",\n \"brand\": \"Augustiner Brauerei München\",\n \"country\": \"Germany\",\n \"alcohol\": 5.2,\n \"type\": \"Lager\"\n}", 36 | "options": { 37 | "raw": { 38 | "language": "json" 39 | } 40 | } 41 | }, 42 | "url": { 43 | "raw": "http://localhost:18080/api/beers", 44 | "protocol": "http", 45 | "host": [ 46 | "localhost" 47 | ], 48 | "port": "18080", 49 | "path": [ 50 | "api", 51 | "beers" 52 | ] 53 | } 54 | }, 55 | "response": [] 56 | }, 57 | { 58 | "name": "Get Blauer August beer", 59 | "request": { 60 | "method": "GET", 61 | "header": [], 62 | "url": { 63 | "raw": "http://localhost:18080/api/beers/B01CJPU3XW", 64 | "protocol": "http", 65 | "host": [ 66 | "localhost" 67 | ], 68 | "port": "18080", 69 | "path": [ 70 | "api", 71 | "beers", 72 | "B01CJPU3XW" 73 | ] 74 | } 75 | }, 76 | "response": [] 77 | }, 78 | { 79 | "name": "Delete Blauer August", 80 | "request": { 81 | "method": "DELETE", 82 | "header": [], 83 | "url": { 84 | "raw": "http://localhost:18080/api/beers/B01CJPU3XW", 85 | "protocol": "http", 86 | "host": [ 87 | "localhost" 88 | ], 89 | "port": "18080", 90 | "path": [ 91 | "api", 92 | "beers", 93 | "B01CJPU3XW" 94 | ] 95 | } 96 | }, 97 | "response": [] 98 | } 99 | ] 100 | } -------------------------------------------------------------------------------- /rest-beer-client/README.md: -------------------------------------------------------------------------------- 1 | # Beer REST Client 2 | 3 | Basically you can use any REST client to interact with the REST service, such as Postman. 4 | 5 | ```bash 6 | $ http get localhost:18080/api/beers 7 | $ http post localhost:18080/api/beers < august.json 8 | 9 | $ http get localhost:18080/api/beers/B01CJPU3XW 10 | $ http put localhost:18080/api/beers/B01CJPU3XW < august.json 11 | $ http delete localhost:18080/api/beers/B01CJPU3XW 12 | ``` -------------------------------------------------------------------------------- /rest-beer-client/august.json: -------------------------------------------------------------------------------- 1 | { 2 | "asin": "B01CJPU3XW", 3 | "name": "Blauer August", 4 | "brand": "Augustiner Brauerei München", 5 | "country": "Germany", 6 | "alcohol": 5.2, 7 | "type": "Lager" 8 | } -------------------------------------------------------------------------------- /rest-beer-golang/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17-bullseye as build 2 | 3 | WORKDIR /go/src/app 4 | ADD . /go/src/app 5 | 6 | RUN go get -d -v ./... 7 | RUN go build -o /go/bin/rest-beer-golang 8 | 9 | FROM gcr.io/distroless/base-debian11 10 | ENV PORT=8081 11 | COPY --from=build /go/bin/rest-beer-golang / 12 | CMD ["/rest-beer-golang"] 13 | -------------------------------------------------------------------------------- /rest-beer-golang/Makefile: -------------------------------------------------------------------------------- 1 | NAME = rest-beer-golang 2 | 3 | default: build 4 | 5 | image: 6 | @docker build -t lreimer/$(NAME) . 7 | 8 | build: 9 | @go build 10 | 11 | clean: 12 | @rm -f $(NAME) -------------------------------------------------------------------------------- /rest-beer-golang/README.md: -------------------------------------------------------------------------------- 1 | # Beer REST Service in Golang 2 | 3 | Simple REST service implemented in plain Golang. 4 | -------------------------------------------------------------------------------- /rest-beer-golang/beer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Beer type as string enum 4 | type BeerType string 5 | 6 | const ( 7 | IndianPaleAle BeerType = "IndianPaleAle" 8 | SessionIpa BeerType = "SessionIpa" 9 | Lager BeerType = "Lager" 10 | ) 11 | 12 | // BeerList structure for array of beers 13 | type BeerList struct { 14 | Beers []Beer `json:"beers"` 15 | } 16 | 17 | // Beer structure with ASIN, Name, Country and Alcohol 18 | type Beer struct { 19 | ASIN string `json:"asin"` 20 | Name string `json:"name"` 21 | Brand string `json:"brand"` 22 | Country string `json:"country"` 23 | Alcohol float32 `json:"alcohol"` 24 | Type BeerType `json:"type"` 25 | } 26 | 27 | var beers = map[string]Beer{ 28 | "B079V9ZDNY": {ASIN: "B079V9ZDNY", Name: "Drunken Sailor", Brand: "CREW Republic", Country: "Germany", Alcohol: 6.4, Type: IndianPaleAle}, 29 | "B07B2YW1TW": {ASIN: "B07B2YW1TW", Name: "Hop Junkie", Brand: "CREW Republic", Country: "Germany", Alcohol: 3.4, Type: SessionIpa}, 30 | "B01AU6LWNC": {ASIN: "B01AU6LWNC", Name: "Edelstoff Exportbier", Brand: "Augustiner Brauerei München", Country: "Germany", Alcohol: 5.6, Type: Lager}, 31 | } 32 | 33 | // AllBeers returns a slice of all Beers 34 | func AllBeers() []Beer { 35 | values := make([]Beer, len(beers)) 36 | idx := 0 37 | for _, beer := range beers { 38 | values[idx] = beer 39 | idx++ 40 | } 41 | return values 42 | } 43 | 44 | // GetBeer returns the beer for a given ASIN 45 | func GetBeer(asin string) (Beer, bool) { 46 | beer, found := beers[asin] 47 | return beer, found 48 | } 49 | 50 | // CreateBeer creates a new Beer if it does not exist 51 | func CreateBeer(beer Beer) (string, bool) { 52 | _, exists := beers[beer.ASIN] 53 | if exists { 54 | return "", false 55 | } 56 | beers[beer.ASIN] = beer 57 | return beer.ASIN, true 58 | } 59 | 60 | // UpdateBeer updates an existing beer 61 | func UpdateBeer(asin string, beer Beer) bool { 62 | _, exists := beers[asin] 63 | if exists { 64 | beers[asin] = beer 65 | } 66 | return exists 67 | } 68 | 69 | // DeleteBeer removes a beer from the map by ASIN key 70 | func DeleteBeer(asin string) { 71 | delete(beers, asin) 72 | } 73 | -------------------------------------------------------------------------------- /rest-beer-golang/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lreimer/from-rest-to-grpc/rest-beer-golang 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /rest-beer-golang/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: rest-beer-golang 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: rest-beer-golang 10 | template: 11 | metadata: 12 | labels: 13 | app: rest-beer-golang 14 | spec: 15 | containers: 16 | - name: rest-beer-golang 17 | image: lreimer/rest-beer-golang 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "100m" 22 | limits: 23 | memory: "128Mi" 24 | cpu: "250m" 25 | env: 26 | - name: PORT 27 | value: "8081" 28 | ports: 29 | - name: http 30 | containerPort: 8081 31 | -------------------------------------------------------------------------------- /rest-beer-golang/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: rest-beer-golang 5 | spec: 6 | selector: 7 | app: rest-beer-golang 8 | type: ClusterIP 9 | sessionAffinity: None 10 | ports: 11 | - protocol: TCP 12 | port: 8081 13 | targetPort: http 14 | -------------------------------------------------------------------------------- /rest-beer-golang/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | http.HandleFunc("/", indexHandleFunc) 13 | 14 | http.HandleFunc("/api/beers", beersHandleFunc) 15 | http.HandleFunc("/api/beers/", beerHandleFunc) 16 | 17 | http.ListenAndServe(port(), nil) 18 | } 19 | 20 | func port() string { 21 | port := os.Getenv("PORT") 22 | if len(port) == 0 { 23 | port = "8080" 24 | } 25 | return ":" + port 26 | } 27 | 28 | func indexHandleFunc(w http.ResponseWriter, r *http.Request) { 29 | w.WriteHeader(http.StatusOK) 30 | fmt.Fprintf(w, "Beer REST Service") 31 | } 32 | 33 | func beersHandleFunc(w http.ResponseWriter, r *http.Request) { 34 | switch method := r.Method; method { 35 | case http.MethodGet: 36 | beers := AllBeers() 37 | writeJSON(w, BeerList{Beers: beers}) 38 | case http.MethodPost: 39 | body, err := ioutil.ReadAll(r.Body) 40 | if err != nil { 41 | w.WriteHeader(http.StatusInternalServerError) 42 | } 43 | beer := readJSON(body) 44 | asin, created := CreateBeer(beer) 45 | if created { 46 | w.Header().Add("Location", "/api/beers/"+asin) 47 | w.WriteHeader(http.StatusCreated) 48 | } else { 49 | w.WriteHeader(http.StatusConflict) 50 | } 51 | default: 52 | w.WriteHeader(http.StatusBadRequest) 53 | w.Write([]byte("Unsupported request method.")) 54 | } 55 | } 56 | 57 | func beerHandleFunc(w http.ResponseWriter, r *http.Request) { 58 | asin := r.URL.Path[len("/api/beers/"):] 59 | 60 | switch method := r.Method; method { 61 | case http.MethodGet: 62 | beer, found := GetBeer(asin) 63 | if found { 64 | writeJSON(w, beer) 65 | } else { 66 | w.WriteHeader(http.StatusNotFound) 67 | } 68 | case http.MethodPut: 69 | body, err := ioutil.ReadAll(r.Body) 70 | if err != nil { 71 | w.WriteHeader(http.StatusInternalServerError) 72 | } 73 | beer := readJSON(body) 74 | exists := UpdateBeer(asin, beer) 75 | if exists { 76 | w.WriteHeader(http.StatusOK) 77 | } else { 78 | w.WriteHeader(http.StatusNotFound) 79 | } 80 | case http.MethodDelete: 81 | DeleteBeer(asin) 82 | w.WriteHeader(http.StatusOK) 83 | default: 84 | w.WriteHeader(http.StatusBadRequest) 85 | w.Write([]byte("Unsupported request method.")) 86 | } 87 | } 88 | 89 | func readJSON(data []byte) Beer { 90 | beer := Beer{} 91 | err := json.Unmarshal(data, &beer) 92 | if err != nil { 93 | panic(err) 94 | } 95 | return beer 96 | } 97 | 98 | func writeJSON(w http.ResponseWriter, i interface{}) { 99 | b, err := json.Marshal(i) 100 | if err != nil { 101 | panic(err) 102 | } 103 | w.Header().Add("Content-Type", "application/json; charset=utf-8") 104 | w.Write(b) 105 | } 106 | -------------------------------------------------------------------------------- /rest-beer-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17-bullseye as build 2 | 3 | WORKDIR /go/src/app 4 | ADD . /go/src/app 5 | 6 | RUN go get -d -v ./... 7 | RUN go build -o /go/bin/rest-beer-service 8 | 9 | FROM gcr.io/distroless/base-debian11 10 | 11 | ENV GIN_MODE=release 12 | ENV PORT=8080 13 | 14 | COPY --from=build /go/src/app/templates /templates 15 | COPY --from=build /go/src/app/favicon.ico / 16 | COPY --from=build /go/bin/rest-beer-service / 17 | 18 | CMD ["/rest-beer-service"] 19 | -------------------------------------------------------------------------------- /rest-beer-service/Makefile: -------------------------------------------------------------------------------- 1 | NAME = rest-beer-service 2 | 3 | default: build 4 | 5 | image: 6 | @docker build -t lreimer/$(NAME) . 7 | 8 | build: 9 | @buf generate 10 | @go build 11 | 12 | clean: 13 | @rm -f $(NAME) -------------------------------------------------------------------------------- /rest-beer-service/README.md: -------------------------------------------------------------------------------- 1 | # Beer REST Service with Gin Framework 2 | 3 | More sophisticated REST service implemented in Golang using the Gin Framework. 4 | 5 | ```bash 6 | # for Protocol Buffers we need to install the latest generator 7 | $ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 8 | 9 | $ export SRC_DIR=$PWD/proto 10 | $ export DST_DIR=$PWD/proto 11 | 12 | # call the protoc compiler to generate the Go files 13 | $ protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/beer.proto 14 | 15 | # alternatively, we use the Buf utility to do the same 16 | # see https://buf.build 17 | ``` -------------------------------------------------------------------------------- /rest-beer-service/beer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Beer type as string enum 4 | type BeerType string 5 | 6 | const ( 7 | IndianPaleAle BeerType = "IndianPaleAle" 8 | SessionIpa BeerType = "SessionIpa" 9 | Lager BeerType = "Lager" 10 | ) 11 | 12 | // Beer structure with ASIN, Name, Country and Alcohol 13 | type Beer struct { 14 | ASIN string `json:"asin"` 15 | Name string `json:"name"` 16 | Brand string `json:"brand"` 17 | Country string `json:"country"` 18 | Alcohol float32 `json:"alcohol"` 19 | Type BeerType `json:"type"` 20 | } 21 | 22 | var beers = map[string]Beer{ 23 | "B079V9ZDNY": {ASIN: "B079V9ZDNY", Name: "Drunken Sailor", Brand: "CREW Republic", Country: "Germany", Alcohol: 6.4, Type: IndianPaleAle}, 24 | "B07B2YW1TW": {ASIN: "B07B2YW1TW", Name: "Hop Junkie", Brand: "CREW Republic", Country: "Germany", Alcohol: 3.4, Type: SessionIpa}, 25 | "B01AU6LWNC": {ASIN: "B01AU6LWNC", Name: "Edelstoff Exportbier", Brand: "Augustiner Brauerei München", Country: "Germany", Alcohol: 5.6, Type: Lager}, 26 | } 27 | 28 | // AllBeers returns a slice of all Beers 29 | func AllBeers() []Beer { 30 | values := make([]Beer, len(beers)) 31 | idx := 0 32 | for _, beer := range beers { 33 | values[idx] = beer 34 | idx++ 35 | } 36 | return values 37 | } 38 | 39 | // GetBeer returns the beer for a given ASIN 40 | func GetBeer(asin string) (Beer, bool) { 41 | beer, found := beers[asin] 42 | return beer, found 43 | } 44 | 45 | // CreateBeer creates a new Beer if it does not exist 46 | func CreateBeer(beer Beer) (string, bool) { 47 | _, exists := beers[beer.ASIN] 48 | if exists { 49 | return "", false 50 | } 51 | beers[beer.ASIN] = beer 52 | return beer.ASIN, true 53 | } 54 | 55 | // UpdateBeer updates an existing beer 56 | func UpdateBeer(asin string, beer Beer) bool { 57 | _, exists := beers[asin] 58 | if exists { 59 | beers[asin] = beer 60 | } 61 | return exists 62 | } 63 | 64 | // DeleteBeer removes a beer from the map by ASIN key 65 | func DeleteBeer(asin string) { 66 | delete(beers, asin) 67 | } 68 | -------------------------------------------------------------------------------- /rest-beer-service/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | plugins: 3 | - name: go 4 | out: proto 5 | opt: paths=source_relative 6 | -------------------------------------------------------------------------------- /rest-beer-service/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1beta1 2 | name: github.com/lreimer/from-rest-to-grpc 3 | deps: 4 | - buf.build/beta/googleapis 5 | build: 6 | roots: 7 | - proto 8 | -------------------------------------------------------------------------------- /rest-beer-service/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lreimer/from-rest-to-grpc/4bee9f8579ad3f9802e5b618ec35351976c0d3d2/rest-beer-service/favicon.ico -------------------------------------------------------------------------------- /rest-beer-service/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lreimer/from-rest-to-grpc/rest-beer-service 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.4 7 | google.golang.org/protobuf v1.27.1 8 | ) 9 | 10 | require ( 11 | github.com/gin-contrib/sse v0.1.0 // indirect 12 | github.com/go-playground/locales v0.13.0 // indirect 13 | github.com/go-playground/universal-translator v0.17.0 // indirect 14 | github.com/go-playground/validator/v10 v10.4.1 // indirect 15 | github.com/golang/protobuf v1.5.0 // indirect 16 | github.com/json-iterator/go v1.1.9 // indirect 17 | github.com/leodido/go-urn v1.2.0 // indirect 18 | github.com/mattn/go-isatty v0.0.12 // indirect 19 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 20 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect 21 | github.com/ugorji/go/codec v1.1.7 // indirect 22 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect 23 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 // indirect 24 | gopkg.in/yaml.v2 v2.2.8 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /rest-beer-service/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: rest-beer-service 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: rest-beer-service 10 | template: 11 | metadata: 12 | labels: 13 | app: rest-beer-service 14 | spec: 15 | containers: 16 | - name: rest-beer-service 17 | image: lreimer/rest-beer-service 18 | resources: 19 | requests: 20 | memory: "64Mi" 21 | cpu: "100m" 22 | limits: 23 | memory: "128Mi" 24 | cpu: "250m" 25 | env: 26 | - name: PORT 27 | value: "8080" 28 | ports: 29 | - name: http 30 | containerPort: 8080 31 | -------------------------------------------------------------------------------- /rest-beer-service/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: rest-beer-service 5 | spec: 6 | selector: 7 | app: rest-beer-service 8 | type: ClusterIP 9 | sessionAffinity: None 10 | ports: 11 | - protocol: TCP 12 | port: 8080 13 | targetPort: http 14 | -------------------------------------------------------------------------------- /rest-beer-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/lreimer/from-rest-to-grpc/rest-beer-service/proto" 9 | ) 10 | 11 | func main() { 12 | engine := gin.Default() 13 | 14 | // configuration for static files and templates 15 | engine.LoadHTMLFiles("templates/index.html") 16 | engine.StaticFile("/favicon.ico", "favicon.ico") 17 | 18 | engine.GET("/", func(c *gin.Context) { 19 | c.HTML(http.StatusOK, "index.html", gin.H{ 20 | "title": "Beer REST Service", 21 | }) 22 | }) 23 | 24 | engine.GET("/api/beers", allBeers) // get list of beers 25 | engine.POST("/api/beers", createBeer) // create new beer 26 | engine.GET("/api/beers/:asin", getBeer) // get beer by ASIN 27 | engine.PUT("/api/beers/:asin", updateBeer) // update existing beer 28 | engine.DELETE("/api/beers/:asin", deleteBeer) // delete beer 29 | 30 | engine.Run(port()) 31 | } 32 | 33 | func port() string { 34 | port := os.Getenv("PORT") 35 | if len(port) == 0 { 36 | port = "8080" 37 | } 38 | return ":" + port 39 | } 40 | 41 | func allBeers(c *gin.Context) { 42 | beers := AllBeers() 43 | c.JSON(http.StatusOK, gin.H{"beers": beers}) 44 | } 45 | 46 | func createBeer(c *gin.Context) { 47 | var beer Beer 48 | if c.BindJSON(&beer) == nil { 49 | asin, created := CreateBeer(beer) 50 | if created { 51 | c.Header("Location", "/api/beers/"+asin) 52 | c.Status(http.StatusCreated) 53 | } else { 54 | c.Status(http.StatusConflict) 55 | } 56 | } 57 | } 58 | 59 | func getBeer(c *gin.Context) { 60 | asin := c.Params.ByName("asin") 61 | beer, found := GetBeer(asin) 62 | if found { 63 | if c.Request.Header.Get("Accept") == "application/x-protobuf" { 64 | pbeer := &proto.Beer{ 65 | Asin: beer.ASIN, 66 | Name: beer.Name, 67 | Brand: beer.Brand, 68 | Country: beer.Country, 69 | Alcohol: beer.Alcohol, 70 | Type: proto.Beer_BeerType(proto.Beer_BeerType_value[string(beer.Type)]), 71 | } 72 | c.ProtoBuf(http.StatusOK, &proto.GetBeerResponse{Beer: pbeer}) 73 | } else { 74 | c.JSON(http.StatusOK, beer) 75 | } 76 | 77 | } else { 78 | c.AbortWithStatus(http.StatusNotFound) 79 | } 80 | } 81 | 82 | func updateBeer(c *gin.Context) { 83 | asin := c.Params.ByName("asin") 84 | 85 | var beer Beer 86 | if c.BindJSON(&beer) == nil { 87 | exists := UpdateBeer(asin, beer) 88 | if exists { 89 | c.Status(http.StatusOK) 90 | } else { 91 | c.Status(http.StatusNotFound) 92 | } 93 | } 94 | } 95 | 96 | func deleteBeer(c *gin.Context) { 97 | asin := c.Params.ByName("asin") 98 | DeleteBeer(asin) 99 | c.Status(http.StatusOK) 100 | } 101 | -------------------------------------------------------------------------------- /rest-beer-service/proto/beer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "github.com/lreimer/from-rest-to-grpc/rest-beer-service/proto"; 4 | package beer; 5 | 6 | message Beer { 7 | string asin = 1; 8 | string name = 2; 9 | string brand = 3; 10 | string country = 4; 11 | float alcohol = 5; 12 | enum BeerType{ 13 | IndianPaleAle = 0; 14 | SessionIpa = 1; 15 | Lager = 2; 16 | } 17 | BeerType type = 6; 18 | 19 | } 20 | 21 | message GetBeersResponse { 22 | repeated Beer beers = 1; 23 | } 24 | 25 | message GetBeerRequest { 26 | string asin = 1; 27 | } 28 | 29 | message GetBeerResponse { 30 | Beer beer = 1; 31 | } 32 | 33 | message CreateBeerRequest { 34 | Beer beer = 1; 35 | } 36 | 37 | message UpdateBeerRequest { 38 | Beer beer = 1; 39 | } 40 | 41 | message DeleteBeerRequest { 42 | string asin = 1; 43 | } -------------------------------------------------------------------------------- /rest-beer-service/templates/index.html: -------------------------------------------------------------------------------- 1 | {{ define "index.html" }} 2 | 3 | 4 | 5 | {{ .title }} 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 22 | 23 | 24 | 25 | {{ end }} -------------------------------------------------------------------------------- /skaffold_golang.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: skaffold/v2beta24 2 | kind: Config 3 | metadata: 4 | name: from-rest-to-grpc 5 | 6 | build: 7 | tagPolicy: 8 | gitCommit: {} 9 | artifacts: 10 | # for the Golang demo 11 | - image: lreimer/grpc-beer-client 12 | context: grpc-beer-client/ 13 | - image: lreimer/rest-beer-golang 14 | context: rest-beer-golang/ 15 | - image: lreimer/rest-beer-service 16 | context: rest-beer-service/ 17 | - image: lreimer/grpc-beer-service 18 | context: grpc-beer-service/ 19 | 20 | # for gRPC ecosystem demo 21 | - image: lreimer/grpc-beer-gateway 22 | context: grpc-beer-gateway/ 23 | - image: lreimer/grpc-beer-nginx 24 | context: grpc-beer-nginx/ 25 | - image: lreimer/grpc-beer-envoy 26 | context: grpc-beer-envoy/ 27 | 28 | local: 29 | # push: false 30 | useBuildkit: true 31 | useDockerCLI: false 32 | 33 | deploy: 34 | kubectl: 35 | manifests: 36 | # for the Golang demo 37 | - rest-beer-golang/k8s/*.yaml 38 | - rest-beer-service/k8s/*.yaml 39 | - grpc-beer-service/k8s/*.yaml 40 | # - grpc-beer-client/k8s/*.yaml 41 | 42 | - grpc-beer-envoy/k8s/*.yaml 43 | - grpc-beer-gateway/k8s/*.yaml 44 | - grpc-beer-nginx/k8s/*.yaml 45 | - grpc-beer-ui/k8s/*.yaml 46 | 47 | portForward: 48 | # for the Golang demo 49 | - resourceName: rest-beer-service 50 | resourceType: service 51 | namespace: default 52 | port: 8080 53 | localPort: 18080 54 | - resourceName: grpc-beer-service 55 | resourceType: service 56 | namespace: default 57 | port: 9090 58 | localPort: 19090 59 | 60 | - resourceName: grpc-beer-gateway 61 | resourceType: service 62 | namespace: default 63 | port: 8090 64 | localPort: 18090 65 | - resourceName: grpc-beer-envoy 66 | resourceType: service 67 | namespace: default 68 | port: 8091 69 | localPort: 18091 70 | - resourceName: grpc-beer-nginx 71 | resourceType: service 72 | namespace: default 73 | port: 8888 74 | localPort: 18888 75 | - resourceName: grpc-beer-ui 76 | resourceType: service 77 | namespace: default 78 | port: 6969 79 | localPort: 16969 -------------------------------------------------------------------------------- /skaffold_micronaut.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: skaffold/v2beta24 2 | kind: Config 3 | metadata: 4 | name: from-rest-to-grpc 5 | 6 | build: 7 | tagPolicy: 8 | gitCommit: {} 9 | artifacts: 10 | # for the Micronaut demo 11 | - image: lreimer/micronaut-beer-rest 12 | context: micronaut-beer-rest/ 13 | jib: 14 | type: gradle 15 | - image: lreimer/micronaut-beer-grpc 16 | context: micronaut-beer-grpc/ 17 | jib: 18 | type: gradle 19 | 20 | # for gRPC ecosystem demo 21 | - image: lreimer/grpc-beer-gateway 22 | context: grpc-beer-gateway/ 23 | - image: lreimer/grpc-beer-nginx 24 | context: grpc-beer-nginx/ 25 | - image: lreimer/grpc-beer-envoy 26 | context: grpc-beer-envoy/ 27 | 28 | local: 29 | # push: false 30 | useBuildkit: true 31 | useDockerCLI: false 32 | 33 | deploy: 34 | kubectl: 35 | manifests: 36 | # for the Micronaut demo 37 | - micronaut-beer-rest/k8s/*.yaml 38 | - micronaut-beer-grpc/k8s/*.yaml 39 | 40 | - grpc-beer-envoy/k8s/*.yaml 41 | - grpc-beer-gateway/k8s/*.yaml 42 | - grpc-beer-nginx/k8s/*.yaml 43 | - grpc-beer-ui/k8s/*.yaml 44 | 45 | portForward: 46 | # for the Micronaut demo 47 | - resourceName: rest-beer-service 48 | resourceType: service 49 | namespace: default 50 | port: 8080 51 | localPort: 18080 52 | - resourceName: grpc-beer-service 53 | resourceType: service 54 | namespace: default 55 | port: 9090 56 | localPort: 19090 57 | 58 | - resourceName: grpc-beer-gateway 59 | resourceType: service 60 | namespace: default 61 | port: 8090 62 | localPort: 18090 63 | - resourceName: grpc-beer-envoy 64 | resourceType: service 65 | namespace: default 66 | port: 8091 67 | localPort: 18091 68 | - resourceName: grpc-beer-nginx 69 | resourceType: service 70 | namespace: default 71 | port: 8888 72 | localPort: 18888 73 | - resourceName: grpc-beer-ui 74 | resourceType: service 75 | namespace: default 76 | port: 6969 77 | localPort: 16969 -------------------------------------------------------------------------------- /skaffold_quarkus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: skaffold/v2beta24 2 | kind: Config 3 | metadata: 4 | name: from-rest-to-grpc 5 | 6 | build: 7 | tagPolicy: 8 | gitCommit: {} 9 | artifacts: 10 | # for the Quarkus demo 11 | - image: lreimer/quarkus-beer-rest 12 | context: quarkus-beer-rest/ 13 | docker: 14 | dockerfile: quarkus-beer-rest/src/main/docker/Dockerfile.jvm 15 | - image: lreimer/quarkus-beer-grpc 16 | context: quarkus-beer-grpc/ 17 | docker: 18 | dockerfile: quarkus-beer-grpc/src/main/docker/Dockerfile.jvm 19 | 20 | # for gRPC ecosystem demo 21 | - image: lreimer/grpc-beer-gateway 22 | context: grpc-beer-gateway/ 23 | - image: lreimer/grpc-beer-nginx 24 | context: grpc-beer-nginx/ 25 | - image: lreimer/grpc-beer-envoy 26 | context: grpc-beer-envoy/ 27 | 28 | local: 29 | # push: false 30 | useBuildkit: true 31 | useDockerCLI: false 32 | 33 | deploy: 34 | kubectl: 35 | manifests: 36 | # for the Quarkus demo 37 | - quarkus-beer-rest/k8s/*.yaml 38 | - quarkus-beer-grpc/k8s/*.yaml 39 | 40 | - grpc-beer-envoy/k8s/*.yaml 41 | - grpc-beer-gateway/k8s/*.yaml 42 | - grpc-beer-nginx/k8s/*.yaml 43 | - grpc-beer-ui/k8s/*.yaml 44 | 45 | portForward: 46 | # for the Quarkus demo 47 | - resourceName: rest-beer-service 48 | resourceType: service 49 | namespace: default 50 | port: 8080 51 | localPort: 18080 52 | - resourceName: grpc-beer-service 53 | resourceType: service 54 | namespace: default 55 | port: 9090 56 | localPort: 19090 57 | 58 | - resourceName: grpc-beer-gateway 59 | resourceType: service 60 | namespace: default 61 | port: 8090 62 | localPort: 18090 63 | - resourceName: grpc-beer-envoy 64 | resourceType: service 65 | namespace: default 66 | port: 8091 67 | localPort: 18091 68 | - resourceName: grpc-beer-nginx 69 | resourceType: service 70 | namespace: default 71 | port: 8888 72 | localPort: 18888 73 | - resourceName: grpc-beer-ui 74 | resourceType: service 75 | namespace: default 76 | port: 6969 77 | localPort: 16969 --------------------------------------------------------------------------------