diff --git a/.scripts/setup-env-variables-azure-template.sh b/.scripts/setup-env-variables-azure-template.sh index fc42d4a7b1..55ccc6b7b2 100755 --- a/.scripts/setup-env-variables-azure-template.sh +++ b/.scripts/setup-env-variables-azure-template.sh @@ -3,27 +3,51 @@ # ==== Resource Group ==== export SUBSCRIPTION=subscription-id # customize this export RESOURCE_GROUP=resource-group-name # customize this -export REGION=westus2 - -# ==== Service and App Instances ==== -export SPRING_CLOUD_SERVICE=azure-spring-cloud-name # customize this -export API_GATEWAY=api-gateway -export ADMIN_SERVER=admin-server -export CUSTOMERS_SERVICE=customers-service -export VETS_SERVICE=vets-service -export VISITS_SERVICE=visits-service - -# ==== JARS ==== -export API_GATEWAY_JAR=spring-petclinic-api-gateway/target/spring-petclinic-api-gateway-2.2.1.jar -export ADMIN_SERVER_JAR=spring-petclinic-admin-server/target/spring-petclinic-admin-server-2.2.1.jar -export CUSTOMERS_SERVICE_JAR=spring-petclinic-customers-service/target/spring-petclinic-customers-service-2.2.1.jar -export VETS_SERVICE_JAR=spring-petclinic-vets-service/target/spring-petclinic-vets-service-2.2.1.jar -export VISITS_SERVICE_JAR=spring-petclinic-visits-service/target/spring-petclinic-visits-service-2.2.1.jar - -# ==== MYSQL INFO ==== -export MYSQL_SERVER_NAME=mysql-servername # customize this -export MYSQL_SERVER_FULL_NAME=${MYSQL_SERVER_NAME}.mysql.database.azure.com -export MYSQL_SERVER_ADMIN_NAME=admin-name # customize this -export MYSQL_SERVER_ADMIN_LOGIN_NAME=${MYSQL_SERVER_ADMIN_NAME}\@${MYSQL_SERVER_NAME} -export MYSQL_SERVER_ADMIN_PASSWORD=SuperS3cr3t # customize this -export MYSQL_DATABASE_NAME=petclinic +export LOCATION=SouthCentralUS #customize this +export COSMOSDB_NAME=mycosmosdbaccname # customize this +export REDIS_NAME=myredisname #customize this +export KEYVAULT_NAME=myend2endkv #customize this + +# ==== Create CosmosDB Account ==== +az cosmosdb create --name $COSMOSDB_NAME --resource-group $RESOURCE_GROUP +COSMOS_KEYS=$(az cosmosdb keys list --name $COSMOSDB_NAME --resource-group $RESOURCE_GROUP --type keys) +COSMOS_PRIMARY_KEY=$(echo $COSMOS_KEYS | jq -r .primaryMasterKey) +COSMOS_SECONDARY_KEY=$(echo $COSMOS_KEYS | jq -r .secondaryMasterKey) +COSMOSDB_URI=$(az cosmosdb show --name $COSMOSDB_NAME --resource-group $RESOURCE_GROUP | jq -r .documentEndpoint) + +# ==== Create Redis Cache Account ==== +az redis create --name $REDIS_NAME --resource-group $RESOURCE_GROUP --sku Basic --vm-size c0 --location $LOCATION +REDIS_HOSTNAME=$(az redis show --name $REDIS_NAME --resource-group $RESOURCE_GROUP | jq -r .hostName) +REDIS_PASSWORD=$(az redis list-keys --name $REDIS_NAME --resource-group $RESOURCE_GROUP | jq -r .primaryKey) + +# ==== Create a KeyVault Account ==== +az keyvault create --location $LOCATION --name $KEYVAULT_NAME --resource-group $RESOURCE_GROUP + +SERVICE_PRINCIPAL=$(az ad sp create-for-rbac -n "endtoendsp") +az keyvault set-policy -n MyVault --key-permissions get list --spn $AZURE_KEYVAULT_CLIENTID + +AZURE_KEYVAULT_URI=$(az keyvault show --name $KEYVAULT_NAME | jq -r .properties | jq -r .vaultUri) +AZURE_KEYVAULT_CLIENTID=$(echo $SERVICE_PRINCIPAL | jq -r .appId) +AZURE_KEYVAULT_TENANTID=$(echo $SERVICE_PRINCIPAL | jq -r .tenant) +AZURE_KEYVAULT_CLIENTKEY=$(echo $SERVICE_PRINCIPAL | jq -r .password) + + +# ==== add keys to keyvault ==== +az keyvault secret set --name cosmosdburi --value $COSMOSDB_URI +az keyvault secret set --name cosmosdbkey --value $COSMOS_PRIMARY_KEY +az keyvault secret set --name cosmosdbsecondarykey --value $COSMOS_SECONDARY_KEY +az keyvault secret set --name redisuri--value $REDIS_HOSTNAME +az keyvault secret set --name redispassword --value $REDIS_PASSWORD + +# ==== Create Kevvault environment file for Docker containers ==== +cat > keyvault.env << EOF +AZURE_KEYVAULT_URI=$AZURE_KEYVAULT_URI +AZURE_KEYVAULT_CLIENTID=$AZURE_KEYVAULT_CLIENTID +AZURE_KEYVAULT_TENANTID=$AZURE_KEYVAULT_TENANTID +AZURE_KEYVAULT_CLIENTKEY=$AZURE_KEYVAULT_CLIENTKEY +EOF + + + + + diff --git a/README-petclinic.md b/README-petclinic.md index 03cbf9ee96..db5016f2bb 100644 --- a/README-petclinic.md +++ b/README-petclinic.md @@ -47,47 +47,14 @@ You can then access petclinic here: http://localhost:8080/ ![Spring Petclinic Microservices screenshot](docs/application-screenshot.png) +**Architecture diagram of the Spring Petclinic Microservices with CosmosDB** -**Architecture diagram of the Spring Petclinic Microservices** - -![Spring Petclinic Microservices architecture](docs/microservices-architecture-diagram.jpg) - +![Spring Petclinic Microservices architecture](docs/microservices-architecture-diagram-cosmosdb.jpg) ## In case you find a bug/suggested improvement for Spring Petclinic Microservices Our issue tracker is available here: https://github.com/spring-petclinic/spring-petclinic-microservices/issues -## Database configuration - -In its default configuration, Petclinic uses an in-memory database (HSQLDB) which gets populated at startup with data. -A similar setup is provided for MySql in case a persistent database configuration is needed. -Dependency for Connector/J, the MySQL JDBC driver is already included in the `pom.xml` files. - -### Start a MySql database - -You may start a MySql database with docker: - -``` -docker run -e MYSQL_ROOT_PASSWORD=petclinic -e MYSQL_DATABASE=petclinic -p 3306:3306 mysql:5.7.8 -``` -or download and install the MySQL database (e.g., MySQL Community Server 5.7 GA), which can be found here: https://dev.mysql.com/downloads/ - -### Use the Spring 'mysql' profile - -To use a MySQL database, you have to start 3 microservices (`visits-service`, `customers-service` and `vets-services`) -with the `mysql` Spring profile. Add the `--spring.profiles.active=mysql` as programm argument. - -By default, at startup, database schema will be created and data will be populated. -You may also manually create the PetClinic database and data by executing the `"db/mysql/{schema,data}.sql"` scripts of each 3 microservices. -In the `application.yml` of the [Configuration repository], set the `initialization-mode` to `never`. - -If you are running the microservices with Docker, you have to add the `mysql` profile into the (Dockerfile)[docker/Dockerfile]: -``` -ENV SPRING_PROFILES_ACTIVE docker,mysql -``` -In the `mysql section` of the `application.yml` from the [Configuration repository], you have to change -the host and port of your MySQL JDBC connection string. - ## Custom metrics monitoring Grafana and Prometheus are included in the `docker-compose.yml` configuration, and the public facing applications diff --git a/README.md b/README.md index 323477e367..a22db80109 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ page_type: sample languages: - java products: -- Azure Spring Cloud -description: "Deploy Spring microservices using Azure Spring Cloud and MySQL" +- Azure Spring Integration Starters +description: "Deploy Spring microservices using Spring Integration starters for Azure" urlFragment: "spring-petclinic-microservices" --- -# Deploy Spring Microservices using Azure Spring Cloud and MySQL +# Deploy Spring Microservices using Azure Spring starters for Cosmos DB, Redis, KeyVault Azure Spring Cloud enables you to easily run a Spring Boot based microservices application on Azure. @@ -16,9 +16,8 @@ This quickstart shows you how to deploy an existing Java Spring Cloud applicatio ## What will you experience You will: - Build existing Spring microservices applications -- Provision an Azure Spring Cloud service instance -- Deploy applications to Azure -- Bind applications to Azure Database for MySQL +- Provision azure resources required for the application +- Run the application locally with CosmosDB backend, Azure Redis Cache and using KeyVault for storing secrets - Open the application ## What you will need @@ -37,16 +36,9 @@ In addition, you will need the following: | [Maven](https://maven.apache.org/download.cgi) | [MySQL CLI](https://dev.mysql.com/downloads/shell/) | [Git](https://git-scm.com/) +| [Jq](https://stedolan.github.io/jq/) | -## Install the Azure CLI extension - -Install the Azure Spring Cloud extension for the Azure CLI using the following command - -```bash - az extension add --name spring-cloud -``` - ## Clone and build the repo ### Create a new folder and clone the sample app repository to your Azure Cloud account @@ -60,40 +52,12 @@ Install the Azure Spring Cloud extension for the Azure CLI using the following c ```bash cd spring-petclinic-microservices - mvn clean package -DskipTests -Denv=cloud + mvn clean package -DskipTests ``` This will take a few minutes. -## Provision Azure Spring Cloud service instance using Azure CLI - -### Prepare your environment for deployments - -Create a bash script with environment variables by making a copy of the supplied template: -```bash - cp .scripts/setup-env-variables-azure-template.sh .scripts/setup-env-variables-azure.sh -``` - -Open `.scripts/setup-env-variables-azure.sh` and enter the following information: - -```bash - - export SUBSCRIPTION=subscription-id # customize this - export RESOURCE_GROUP=resource-group-name # customize this - ... - export SPRING_CLOUD_SERVICE=azure-spring-cloud-name # customize this - ... - export MYSQL_SERVER_NAME=mysql-servername # customize this - ... - export MYSQL_SERVER_ADMIN_NAME=admin-name # customize this - ... - export MYSQL_SERVER_ADMIN_PASSWORD=SuperS3cr3t # customize this - ... -``` +## Provision Azure resources using Azure CLI -Then, set the environment: -```bash - source .scripts/setup-env-variables-azure.sh -``` ### Login to Azure Login to the Azure CLI and choose your active subscription. Be sure to choose the active subscription that is whitelisted for Azure Spring Cloud @@ -104,205 +68,75 @@ Login to the Azure CLI and choose your active subscription. Be sure to choose th az account set --subscription ${SUBSCRIPTION} ``` -### Create Azure Spring Cloud service instance -Prepare a name for your Azure Spring Cloud service. The name must be between 4 and 32 characters long and can contain only lowercase letters, numbers, and hyphens. The first character of the service name must be a letter and the last character must be either a letter or a number. - -Create a resource group to contain your Azure Spring Cloud service. +### Prepare your environment for deployments +Create a bash script with environment variables by making a copy of the supplied template: ```bash - az group create --name ${RESOURCE_GROUP} \ - --location ${REGION} + cp .scripts/setup-env-variables-azure-template.sh .scripts/setup-env-variables-azure.sh ``` -Create an instance of Azure Spring Cloud. +Open `.scripts/setup-env-variables-azure.sh` and enter the following information: ```bash - az spring-cloud create --name ${SPRING_CLOUD_SERVICE} \ - --resource-group ${RESOURCE_GROUP} \ - --location ${REGION} -``` -The service instance will take around five minutes to deploy. - -Set your default resource group name and cluster name using the following commands: - -```bash - az configure --defaults \ - group=${RESOURCE_GROUP} \ - location=${REGION} \ - spring-cloud=${SPRING_CLOUD_SERVICE} + export SUBSCRIPTION=subscription-id # customize this +... + export RESOURCE_GROUP=resource-group-name # customize this +... + export RESOURCE_GROUP=resource-group-name # customize this +... + export LOCATION=SouthCentralUS #customize this +... + export COSMOSDB_NAME=mycosmosdbaccname # customize this +... + export REDIS_NAME=myredisname #customize this +... + export KEYVAULT_NAME=myend2endkv #customize this +... + ``` -### Load Spring Cloud Config Server - -Use the `application.yml` in the root of this project to load configuration into the Config Server in Azure Spring Cloud. - +Then, set the environment: ```bash - az spring-cloud config-server set \ - --config-file application.yml \ - --name ${SPRING_CLOUD_SERVICE} + source .scripts/setup-env-variables-azure.sh ``` +make sure keyvault.env file is created at the root of the repo. -## Create microservice applications - -Create 5 microservice apps. - -```bash - az spring-cloud app create --name ${API_GATEWAY} --instance-count 1 --is-public true \ - --memory 2 \ - --jvm-options='-Xms2048m -Xmx2048m' - - az spring-cloud app create --name ${ADMIN_SERVER} --instance-count 1 --is-public true \ - --memory 2 \ - --jvm-options='-Xms2048m -Xmx2048m' - - az spring-cloud app create --name ${CUSTOMERS_SERVICE} --instance-count 1 \ - --memory 2 \ - --jvm-options='-Xms2048m -Xmx2048m' - - az spring-cloud app create --name ${VETS_SERVICE} --instance-count 1 \ - --memory 2 \ - --jvm-options='-Xms2048m -Xmx2048m' - - az spring-cloud app create --name ${VISITS_SERVICE} --instance-count 1 \ - --memory 2 \ - --jvm-options='-Xms2048m -Xmx2048m' -``` +## Starting services locally with docker-compose +In order to start entire infrastructure using Docker, you have to build images by executing `./mvnw clean install -P buildDocker` +from a project root. Once images are ready, you can start them with a single command +`docker-compose up`. Containers startup order is coordinated with [`dockerize` script](https://github.com/jwilder/dockerize). +After starting services it takes a while for API Gateway to be in sync with service registry, +so don't be scared of initial Spring Cloud Gateway timeouts. You can track services availability using Eureka dashboard +available by default at http://localhost:8761. -## Create MySQL Database +## Understanding the Spring Petclinic application +[See the presentation of the Spring Petclinic Framework version](http://fr.slideshare.net/AntoineRey/spring-framework-petclinic-sample-application) -Create a MySQL database in Azure Database for MySQL. +[A blog bost introducing the Spring Petclinic Microsevices](http://javaetmoi.com/2018/10/architecture-microservices-avec-spring-cloud/) (french language) -```bash - // create mysql server - az mysql server create --resource-group ${RESOURCE_GROUP} \ - --name ${MYSQL_SERVER_NAME} --location ${REGION} \ - --admin-user ${MYSQL_SERVER_ADMIN_NAME} \ - --admin-password ${MYSQL_SERVER_ADMIN_PASSWORD} \ - --sku-name GP_Gen5_2 \ - --ssl-enforcement Disabled \ - --version 5.7 - - // allow access from Azure resources - az mysql server firewall-rule create --name allAzureIPs \ - --server ${MYSQL_SERVER_NAME} \ - --resource-group ${RESOURCE_GROUP} \ - --start-ip-address 0.0.0.0 --end-ip-address 0.0.0.0 - - // allow access from your dev machine for testing - az mysql server firewall-rule create --name devMachine \ - --server ${MYSQL_SERVER_NAME} \ - --resource-group ${RESOURCE_GROUP} \ - --start-ip-address \ - --end-ip-address - - // increase connection timeout - az mysql server configuration set --name wait_timeout \ - --resource-group ${RESOURCE_GROUP} \ - --server ${MYSQL_SERVER_NAME} --value 2147483 - - // SUBSTITUTE values - mysql -u ${MYSQL_SERVER_ADMIN_LOGIN_NAME} \ - -h ${MYSQL_SERVER_FULL_NAME} -P 3306 -p - - Enter password: - Welcome to the MySQL monitor. Commands end with ; or \g. - Your MySQL connection id is 64379 - Server version: 5.6.39.0 MySQL Community Server (GPL) - - Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. - - Oracle is a registered trademark of Oracle Corporation and/or its - affiliates. Other names may be trademarks of their respective - owners. - - Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. - - mysql> CREATE DATABASE petclinic; - Query OK, 1 row affected (0.10 sec) - - mysql> CREATE USER 'root' IDENTIFIED BY 'petclinic'; - Query OK, 0 rows affected (0.11 sec) - - mysql> GRANT ALL PRIVILEGES ON petclinic.* TO 'root'; - Query OK, 0 rows affected (1.29 sec) - - mysql> CALL mysql.az_load_timezone(); - Query OK, 3179 rows affected, 1 warning (6.34 sec) - - mysql> SELECT name FROM mysql.time_zone_name; - ... - - mysql> quit - Bye - - - az mysql server configuration set --name time_zone \ - --resource-group ${RESOURCE_GROUP} \ - --server ${MYSQL_SERVER_NAME} --value "US/Pacific" -``` +You can then access petclinic here: http://localhost:8080/ -## Deploy applications and set environment variables +![Spring Petclinic Microservices screenshot](docs/application-screenshot.png) -Deploy microservice applications to Azure. +**Architecture diagram of the Spring Petclinic Microservices with CosmosDB** -```bash - az spring-cloud app deploy --name ${API_GATEWAY} \ - --jar-path ${API_GATEWAY_JAR} \ - --jvm-options='-Xms2048m -Xmx2048m -Dspring.profiles.active=mysql' - - - az spring-cloud app deploy --name ${ADMIN_SERVER} \ - --jar-path ${ADMIN_SERVER_JAR} \ - --jvm-options='-Xms2048m -Xmx2048m -Dspring.profiles.active=mysql' - - - az spring-cloud app deploy --name ${CUSTOMERS_SERVICE} \ - --jar-path ${CUSTOMERS_SERVICE_JAR} \ - --jvm-options='-Xms2048m -Xmx2048m -Dspring.profiles.active=mysql' \ - --env MYSQL_SERVER_FULL_NAME=${MYSQL_SERVER_FULL_NAME} \ - MYSQL_DATABASE_NAME=${MYSQL_DATABASE_NAME} \ - MYSQL_SERVER_ADMIN_LOGIN_NAME=${MYSQL_SERVER_ADMIN_LOGIN_NAME} \ - MYSQL_SERVER_ADMIN_PASSWORD=${MYSQL_SERVER_ADMIN_PASSWORD} - - - az spring-cloud app deploy --name ${VETS_SERVICE} \ - --jar-path ${VETS_SERVICE_JAR} \ - --jvm-options='-Xms2048m -Xmx2048m -Dspring.profiles.active=mysql' \ - --env MYSQL_SERVER_FULL_NAME=${MYSQL_SERVER_FULL_NAME} \ - MYSQL_DATABASE_NAME=${MYSQL_DATABASE_NAME} \ - MYSQL_SERVER_ADMIN_LOGIN_NAME=${MYSQL_SERVER_ADMIN_LOGIN_NAME} \ - MYSQL_SERVER_ADMIN_PASSWORD=${MYSQL_SERVER_ADMIN_PASSWORD} - - - az spring-cloud app deploy --name ${VISITS_SERVICE} \ - --jar-path ${VISITS_SERVICE_JAR} \ - --jvm-options='-Xms2048m -Xmx2048m -Dspring.profiles.active=mysql' \ - --env MYSQL_SERVER_FULL_NAME=${MYSQL_SERVER_FULL_NAME} \ - MYSQL_DATABASE_NAME=${MYSQL_DATABASE_NAME} \ - MYSQL_SERVER_ADMIN_LOGIN_NAME=${MYSQL_SERVER_ADMIN_LOGIN_NAME} \ - MYSQL_SERVER_ADMIN_PASSWORD=${MYSQL_SERVER_ADMIN_PASSWORD} -``` +![Spring Petclinic Microservices architecture](docs/microservices-architecture-diagram-cosmosdb.jpg) -```bash - az spring-cloud app show --name ${API_GATEWAY} | grep url -``` -Navigate to the URL provided by the previous command to open the Pet Clinic microservice application. - +## Navigate to the application +The application could be reached at http://localhost:8080 ![](./media/petclinic.jpg) +## Enabling Spring boot starter for Azure Active directory (Optional) +To secure the Java applications in this sample please follow the [Spring Security Azure Active Directory tutorial](https://docs.microsoft.com/en-us/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-active-directory). +After setting your Active directory you can enable security on Customers service by uncommenting the relevant AAD sample code. + ## Next Steps -In this quickstart, you've deployed an existing Spring microservices app using Azure CLI. To learn more about Azure Spring Cloud, go to: - -- [Azure Spring Cloud](https://azure.microsoft.com/en-us/services/spring-cloud/) -- [Azure Spring Cloud docs](https://docs.microsoft.com/en-us/azure/java/) -- [Deploy Spring microservices from scratch](https://github.com/microsoft/azure-spring-cloud-training) -- [Deploy existing Spring microservices](https://github.com/Azure-Samples/azure-spring-cloud) +In this quickstart, you've deployed an existing Spring microservices app using Azure CLI. To learn more about Spring on Azure, go to: +- [Spring on Azure](https://docs.microsoft.com/en-us/azure/developer/java/spring-framework/) - [Azure for Java Cloud Developers](https://docs.microsoft.com/en-us/azure/java/) -- [Spring Cloud Azure](https://cloud.spring.io/spring-cloud-azure/) -- [Spring Cloud](https://spring.io/projects/spring-cloud) ## Credits diff --git a/application.yml b/application.yml deleted file mode 100644 index 498a33622c..0000000000 --- a/application.yml +++ /dev/null @@ -1,10 +0,0 @@ -spring: - cloud: - config: - server: - git: - uri: https://github.com/selvasingh/spring-petclinic-microservices-config - native: - search-locations: classpath:. - profiles: - active: native diff --git a/docker-compose.yml b/docker-compose.yml index 93c0a42d34..30ebe30804 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: mem_limit: 512M depends_on: - config-server - entrypoint: ["./dockerize","-wait=tcp://config-server:8888","-timeout=60s","--","java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] + entrypoint: ["./dockerize","-wait=tcp://config-server:8888","-timeout=60s","--","java", "org.springframework.boot.loader.JarLauncher"] ports: - 8761:8761 @@ -25,9 +25,11 @@ services: depends_on: - config-server - discovery-server - entrypoint: ["./dockerize","-wait=tcp://discovery-server:8761","-timeout=60s","--","java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] + entrypoint: ["./dockerize","-wait=tcp://discovery-server:8761","-timeout=60s","--","java", "org.springframework.boot.loader.JarLauncher"] ports: - 8081:8081 + env_file: + - keyvault.env visits-service: image: springcommunity/spring-petclinic-visits-service @@ -36,9 +38,12 @@ services: depends_on: - config-server - discovery-server - entrypoint: ["./dockerize","-wait=tcp://discovery-server:8761","-timeout=60s","--","java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] + entrypoint: ["./dockerize","-wait=tcp://discovery-server:8761","-timeout=60s","--","java", "org.springframework.boot.loader.JarLauncher"] ports: - 8082:8082 + env_file: + - keyvault.env + vets-service: image: springcommunity/spring-petclinic-vets-service @@ -47,9 +52,11 @@ services: depends_on: - config-server - discovery-server - entrypoint: ["./dockerize","-wait=tcp://discovery-server:8761","-timeout=60s","--","java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] + entrypoint: ["./dockerize","-wait=tcp://discovery-server:8761","-timeout=60s","--","java", "org.springframework.boot.loader.JarLauncher"] ports: - 8083:8083 + env_file: + - keyvault.env api-gateway: image: springcommunity/spring-petclinic-api-gateway @@ -58,7 +65,7 @@ services: depends_on: - config-server - discovery-server - entrypoint: ["./dockerize","-wait=tcp://discovery-server:8761","-timeout=60s","--","java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] + entrypoint: ["./dockerize","-wait=tcp://discovery-server:8761","-timeout=60s","--","java", "org.springframework.boot.loader.JarLauncher"] ports: - 8080:8080 @@ -78,7 +85,7 @@ services: depends_on: - config-server - discovery-server - entrypoint: ["./dockerize","-wait=tcp://discovery-server:8761","-timeout=60s","--","java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] + entrypoint: ["./dockerize","-wait=tcp://discovery-server:8761","-timeout=60s","--","java", "org.springframework.boot.loader.JarLauncher"] ports: - 9090:9090 diff --git a/docker/Dockerfile b/docker/Dockerfile index 52aef406eb..84d25c6baa 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,19 +1,31 @@ -FROM openjdk:8-jre-alpine -VOLUME /tmp - -# Download dockerize and cache that layer -ARG DOCKERIZE_VERSION -RUN wget -O dockerize.tar.gz https://github.com/jwilder/dockerize/releases/download/${DOCKERIZE_VERSION}/dockerize-alpine-linux-amd64-${DOCKERIZE_VERSION}.tar.gz -RUN tar xzf dockerize.tar.gz -RUN chmod +x dockerize - -# This is the first layer that won't be cached -ARG ARTIFACT_NAME -ADD ${ARTIFACT_NAME}.jar /app.jar - -ARG EXPOSED_PORT -EXPOSE ${EXPOSED_PORT} - -ENV SPRING_PROFILES_ACTIVE docker - -ENTRYPOINT ["java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] +FROM openjdk:11-jre as builder +WORKDIR application +ARG ARTIFACT_NAME +COPY ${ARTIFACT_NAME}.jar application.jar +RUN java -Djarmode=layertools -jar application.jar extract + +# Download dockerize and cache that layer +ARG DOCKERIZE_VERSION +RUN wget -O dockerize.tar.gz https://github.com/jwilder/dockerize/releases/download/${DOCKERIZE_VERSION}/dockerize-alpine-linux-amd64-${DOCKERIZE_VERSION}.tar.gz +RUN tar xzf dockerize.tar.gz +RUN chmod +x dockerize + + +# wget is not installed on adoptopenjdk:11-jre-hotspot +FROM adoptopenjdk:11-jre-hotspot + +WORKDIR application + +# Dockerize +COPY --from=builder application/dockerize ./ + +ARG EXPOSED_PORT +EXPOSE ${EXPOSED_PORT} + +ENV SPRING_PROFILES_ACTIVE docker + +COPY --from=builder application/dependencies/ ./ +COPY --from=builder application/spring-boot-loader/ ./ +COPY --from=builder application/snapshot-dependencies/ ./ +COPY --from=builder application/application/ ./ +ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"] diff --git a/docker/grafana/Dockerfile b/docker/grafana/Dockerfile index c11b3d911e..4f8de4f90d 100644 --- a/docker/grafana/Dockerfile +++ b/docker/grafana/Dockerfile @@ -1,4 +1,4 @@ -FROM grafana/grafana:5.2.4 -ADD ./provisioning /etc/grafana/provisioning -ADD ./grafana.ini /etc/grafana/grafana.ini -ADD ./dashboards /var/lib/grafana/dashboards +FROM grafana/grafana:5.2.4 +ADD ./provisioning /etc/grafana/provisioning +ADD ./grafana.ini /etc/grafana/grafana.ini +ADD ./dashboards /var/lib/grafana/dashboards diff --git a/docker/grafana/dashboards/grafana-petclinic-dashboard.json b/docker/grafana/dashboards/grafana-petclinic-dashboard.json index ccd51d1bfa..9c12fae5e5 100644 --- a/docker/grafana/dashboards/grafana-petclinic-dashboard.json +++ b/docker/grafana/dashboards/grafana-petclinic-dashboard.json @@ -1,772 +1,772 @@ -{ - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "5.0.0" - }, - { - "type": "panel", - "id": "graph", - "name": "Graph", - "version": "5.0.0" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "5.0.0" - }, - { - "type": "panel", - "id": "singlestat", - "name": "Singlestat", - "version": "5.0.0" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": null, - "iteration": 1539967676482, - "links": [], - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 0 - }, - "id": 11, - "legend": { - "avg": false, - "current": true, - "max": false, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(http_server_requests_seconds_sum{status!~\"5..\"}[1m]))/sum(rate(http_server_requests_seconds_count{ status!~\"5..\"}[1m]))", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "HTTP - AVG", - "refId": "A" - }, - { - "expr": "max(http_server_requests_seconds_max{status!~\"5..\"})", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "HTTP - MAX", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "HTTP Request Latency", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "decimals": null, - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 0 - }, - "id": 9, - "legend": { - "avg": false, - "current": true, - "max": true, - "min": false, - "show": true, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 0.5, - "points": true, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(rate(http_server_requests_seconds_count[1m]))", - "format": "time_series", - "interval": "", - "intervalFactor": 1, - "legendFormat": "request - ok", - "refId": "A" - }, - { - "expr": "sum(rate(http_server_requests_seconds_count{status=~\"5..\"}[1m]))", - "format": "time_series", - "interval": "", - "intervalFactor": 1, - "legendFormat": "request - err", - "refId": "B" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "HTTP Request Activity", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "ops", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "Prometheus", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 9 - }, - "id": 2, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(petclinic_owner_seconds_count{method=\"PUT\", status=\"204\"})", - "format": "time_series", - "instant": true, - "intervalFactor": 1, - "refId": "A" - } - ], - "thresholds": "", - "title": "Owners Updated", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "Prometheus", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 9 - }, - "id": 3, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(petclinic_owner_seconds_count{method=\"POST\", status=\"201\"})", - "format": "time_series", - "instant": true, - "intervalFactor": 1, - "legendFormat": "", - "refId": "A" - } - ], - "thresholds": "", - "title": "Owners Created", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "Prometheus", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 12, - "y": 9 - }, - "id": 4, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(petclinic_pet_seconds_count{method=\"POST\", status=\"201\"})", - "format": "time_series", - "instant": true, - "intervalFactor": 1, - "legendFormat": "", - "refId": "A" - } - ], - "thresholds": "", - "title": "Pets Created", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "Prometheus", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 6, - "x": 18, - "y": 9 - }, - "id": 5, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "Value", - "targets": [ - { - "expr": "sum(petclinic_visit_seconds_count{method=\"POST\", status=\"201\"})", - "format": "time_series", - "instant": true, - "intervalFactor": 1, - "legendFormat": "", - "refId": "A" - } - ], - "thresholds": "", - "title": "Visit Created", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "aliasColors": {}, - "bars": true, - "dashLength": 10, - "dashes": false, - "datasource": "Prometheus", - "fill": 1, - "gridPos": { - "h": 8, - "w": 24, - "x": 0, - "y": 14 - }, - "id": 7, - "legend": { - "alignAsTable": false, - "avg": false, - "current": false, - "hideEmpty": false, - "hideZero": true, - "max": true, - "min": false, - "rightSide": false, - "show": true, - "total": false, - "values": true - }, - "lines": false, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "sum(petclinic_owner_seconds_count{method=\"POST\", status=\"201\"})", - "format": "time_series", - "instant": false, - "intervalFactor": 1, - "legendFormat": "owner create", - "refId": "A" - }, - { - "expr": "sum(petclinic_pet_seconds_count{method=\"POST\", status=\"201\"})", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "pet create", - "refId": "B" - }, - { - "expr": "sum(petclinic_visit_seconds_count{method=\"POST\", status=\"201\"})", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "visit create", - "refId": "C" - }, - { - "expr": "sum(petclinic_owner_seconds_count{method=\"PUT\", status=\"204\"})", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "owner update", - "refId": "D" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "SPC Business Histogram", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "refresh": "30s", - "schemaVersion": 16, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "auto": true, - "auto_count": 1, - "auto_min": "10s", - "current": { - "text": "auto", - "value": "$__auto_interval_timeRange" - }, - "hide": 0, - "label": null, - "name": "timeRange", - "options": [ - { - "selected": true, - "text": "auto", - "value": "$__auto_interval_timeRange" - }, - { - "selected": false, - "text": "1m", - "value": "1m" - }, - { - "selected": false, - "text": "10m", - "value": "10m" - }, - { - "selected": false, - "text": "30m", - "value": "30m" - }, - { - "selected": false, - "text": "1h", - "value": "1h" - }, - { - "selected": false, - "text": "6h", - "value": "6h" - }, - { - "selected": false, - "text": "12h", - "value": "12h" - }, - { - "selected": false, - "text": "1d", - "value": "1d" - }, - { - "selected": false, - "text": "7d", - "value": "7d" - }, - { - "selected": false, - "text": "14d", - "value": "14d" - }, - { - "selected": false, - "text": "30d", - "value": "30d" - } - ], - "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d", - "refresh": 2, - "type": "interval" - } - ] - }, - "time": { - "from": "now-1h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "", - "title": "Spring Petclinic Metrics", - "uid": "69JXeR0iw", - "version": 1 -} +{ + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "5.0.0" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "5.0.0" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "5.0.0" + }, + { + "type": "panel", + "id": "singlestat", + "name": "Singlestat", + "version": "5.0.0" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1539967676482, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 11, + "legend": { + "avg": false, + "current": true, + "max": false, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(http_server_requests_seconds_sum{status!~\"5..\"}[1m]))/sum(rate(http_server_requests_seconds_count{ status!~\"5..\"}[1m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "HTTP - AVG", + "refId": "A" + }, + { + "expr": "max(http_server_requests_seconds_max{status!~\"5..\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "HTTP - MAX", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "HTTP Request Latency", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 9, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 0.5, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(http_server_requests_seconds_count[1m]))", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "request - ok", + "refId": "A" + }, + { + "expr": "sum(rate(http_server_requests_seconds_count{status=~\"5..\"}[1m]))", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "request - err", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "HTTP Request Activity", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 9 + }, + "id": 2, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(petclinic_owner_seconds_count{method=\"PUT\", status=\"204\"})", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "refId": "A" + } + ], + "thresholds": "", + "title": "Owners Updated", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 9 + }, + "id": 3, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(petclinic_owner_seconds_count{method=\"POST\", status=\"201\"})", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": "", + "title": "Owners Created", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 9 + }, + "id": 4, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(petclinic_pet_seconds_count{method=\"POST\", status=\"201\"})", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": "", + "title": "Pets Created", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "Prometheus", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 9 + }, + "id": 5, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "Value", + "targets": [ + { + "expr": "sum(petclinic_visit_seconds_count{method=\"POST\", status=\"201\"})", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": "", + "title": "Visit Created", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 7, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "hideZero": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum(petclinic_owner_seconds_count{method=\"POST\", status=\"201\"})", + "format": "time_series", + "instant": false, + "intervalFactor": 1, + "legendFormat": "owner create", + "refId": "A" + }, + { + "expr": "sum(petclinic_pet_seconds_count{method=\"POST\", status=\"201\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "pet create", + "refId": "B" + }, + { + "expr": "sum(petclinic_visit_seconds_count{method=\"POST\", status=\"201\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "visit create", + "refId": "C" + }, + { + "expr": "sum(petclinic_owner_seconds_count{method=\"PUT\", status=\"204\"})", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "owner update", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "SPC Business Histogram", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + } + ], + "refresh": "30s", + "schemaVersion": 16, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "auto": true, + "auto_count": 1, + "auto_min": "10s", + "current": { + "text": "auto", + "value": "$__auto_interval_timeRange" + }, + "hide": 0, + "label": null, + "name": "timeRange", + "options": [ + { + "selected": true, + "text": "auto", + "value": "$__auto_interval_timeRange" + }, + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": false, + "text": "10m", + "value": "10m" + }, + { + "selected": false, + "text": "30m", + "value": "30m" + }, + { + "selected": false, + "text": "1h", + "value": "1h" + }, + { + "selected": false, + "text": "6h", + "value": "6h" + }, + { + "selected": false, + "text": "12h", + "value": "12h" + }, + { + "selected": false, + "text": "1d", + "value": "1d" + }, + { + "selected": false, + "text": "7d", + "value": "7d" + }, + { + "selected": false, + "text": "14d", + "value": "14d" + }, + { + "selected": false, + "text": "30d", + "value": "30d" + } + ], + "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d", + "refresh": 2, + "type": "interval" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Spring Petclinic Metrics", + "uid": "69JXeR0iw", + "version": 1 +} diff --git a/docker/grafana/grafana.ini b/docker/grafana/grafana.ini index 2919aa4635..6d5a20edd3 100644 --- a/docker/grafana/grafana.ini +++ b/docker/grafana/grafana.ini @@ -1,27 +1,27 @@ -##################### Spring Petclinic Microservices Grafana Configuration ##################### - -# possible values : production, development -app_mode = development - -#################################### Paths #################################### -[paths] -# folder that contains provisioning config files that grafana will apply on startup and while running. -provisioning = /etc/grafana/provisioning - -#################################### Server #################################### -[server] -# enable gzip -enable_gzip = true - -#################################### Anonymous Auth ########################## -# Anonymous authentication has been enabled in the Petclinic sample with Admin role -# Do not do that in Production environment -[auth.anonymous] -# enable anonymous access -enabled = true - -# specify organization name that should be used for unauthenticated users -org_name = Main Org. - -# specify role for unauthenticated users -org_role = Admin +##################### Spring Petclinic Microservices Grafana Configuration ##################### + +# possible values : production, development +app_mode = development + +#################################### Paths #################################### +[paths] +# folder that contains provisioning config files that grafana will apply on startup and while running. +provisioning = /etc/grafana/provisioning + +#################################### Server #################################### +[server] +# enable gzip +enable_gzip = true + +#################################### Anonymous Auth ########################## +# Anonymous authentication has been enabled in the Petclinic sample with Admin role +# Do not do that in Production environment +[auth.anonymous] +# enable anonymous access +enabled = true + +# specify organization name that should be used for unauthenticated users +org_name = Main Org. + +# specify role for unauthenticated users +org_role = Admin diff --git a/docker/grafana/provisioning/dashboards/all.yml b/docker/grafana/provisioning/dashboards/all.yml index 3b978e6254..5dcedd9a87 100644 --- a/docker/grafana/provisioning/dashboards/all.yml +++ b/docker/grafana/provisioning/dashboards/all.yml @@ -1,11 +1,11 @@ -apiVersion: 1 - -providers: -- name: 'default' - orgId: 1 - folder: '' - type: file - disableDeletion: false - updateIntervalSeconds: 10 #how often Grafana will scan for changed dashboards - options: - path: /var/lib/grafana/dashboards +apiVersion: 1 + +providers: +- name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: false + updateIntervalSeconds: 10 #how often Grafana will scan for changed dashboards + options: + path: /var/lib/grafana/dashboards diff --git a/docker/grafana/provisioning/datasources/all.yml b/docker/grafana/provisioning/datasources/all.yml index 9c88fce5ac..967fd8e3ca 100644 --- a/docker/grafana/provisioning/datasources/all.yml +++ b/docker/grafana/provisioning/datasources/all.yml @@ -1,13 +1,13 @@ -# config file version -apiVersion: 1 - -# list of datasources to insert/update depending what's available in the database -datasources: -- name: Prometheus - type: prometheus - access: proxy - org_id: 1 - url: http://prometheus-server:9090 - is_default: true - version: 1 - editable: true +# config file version +apiVersion: 1 + +# list of datasources to insert/update depending what's available in the database +datasources: +- name: Prometheus + type: prometheus + access: proxy + org_id: 1 + url: http://prometheus-server:9090 + is_default: true + version: 1 + editable: true diff --git a/docker/prometheus/Dockerfile b/docker/prometheus/Dockerfile index 58626f638c..befdae3649 100644 --- a/docker/prometheus/Dockerfile +++ b/docker/prometheus/Dockerfile @@ -1,2 +1,2 @@ -FROM prom/prometheus:v2.4.2 -ADD prometheus.yml /etc/prometheus/ +FROM prom/prometheus:v2.4.2 +ADD prometheus.yml /etc/prometheus/ diff --git a/docker/prometheus/prometheus.yml b/docker/prometheus/prometheus.yml index 30f089b72b..9798cc9a00 100644 --- a/docker/prometheus/prometheus.yml +++ b/docker/prometheus/prometheus.yml @@ -1,32 +1,32 @@ -# my global config -global: - scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. - evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. - # scrape_timeout is set to the global default (10s). - -# A scrape configuration containing exactly one endpoint to scrape: -# Here it's Prometheus itself. -scrape_configs: -- job_name: prometheus - static_configs: - - targets: ['localhost:9090'] - -- job_name: api-gateway - metrics_path: /actuator/prometheus - static_configs: - - targets: ['api-gateway:8080'] - -- job_name: customers-service - metrics_path: /actuator/prometheus - static_configs: - - targets: ['customers-service:8081'] - -- job_name: visits-service - metrics_path: /actuator/prometheus - static_configs: - - targets: ['visits-service:8082'] - -- job_name: vets-service - metrics_path: /actuator/prometheus - static_configs: - - targets: ['vets-service:8083'] +# my global config +global: + scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. + evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. + # scrape_timeout is set to the global default (10s). + +# A scrape configuration containing exactly one endpoint to scrape: +# Here it's Prometheus itself. +scrape_configs: +- job_name: prometheus + static_configs: + - targets: ['localhost:9090'] + +- job_name: api-gateway + metrics_path: /actuator/prometheus + static_configs: + - targets: ['api-gateway:8080'] + +- job_name: customers-service + metrics_path: /actuator/prometheus + static_configs: + - targets: ['customers-service:8081'] + +- job_name: visits-service + metrics_path: /actuator/prometheus + static_configs: + - targets: ['visits-service:8082'] + +- job_name: vets-service + metrics_path: /actuator/prometheus + static_configs: + - targets: ['vets-service:8083'] diff --git a/docs/microservices-architecture-diagram-cosmosdb.jpg b/docs/microservices-architecture-diagram-cosmosdb.jpg new file mode 100644 index 0000000000..585e405122 Binary files /dev/null and b/docs/microservices-architecture-diagram-cosmosdb.jpg differ diff --git a/pom.xml b/pom.xml index dde16d3342..e564b9da87 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.1.RELEASE + 2.3.6.RELEASE org.springframework.samples @@ -29,8 +29,8 @@ 1.8 3.11.1 - 2.2.1.RELEASE - Hoxton.RELEASE + 2.3.4.RELEASE + Hoxton.SR8 2.22.0 diff --git a/spring-petclinic-admin-server/pom.xml b/spring-petclinic-admin-server/pom.xml index 5554d0d53b..e7f6174aac 100644 --- a/spring-petclinic-admin-server/pom.xml +++ b/spring-petclinic-admin-server/pom.xml @@ -1,74 +1,74 @@ - - - 4.0.0 - - org.springframework.samples.petclinic.admin - spring-petclinic-admin-server - jar - Spring Boot Admin server - - - org.springframework.samples - spring-petclinic-microservices - 2.2.1 - - - - 2.2.2 - 9090 - ${basedir}/../docker - - - - - - org.springframework.boot - spring-boot-starter - - - org.springframework.cloud - spring-cloud-starter-config - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - - de.codecentric - spring-boot-admin-starter-server - ${spring-boot-admin.version} - - - de.codecentric - spring-boot-admin-server-ui - ${spring-boot-admin.version} - - - - - org.jolokia - jolokia-core - - - - - - - buildDocker - - - - com.spotify - docker-maven-plugin - ${docker.plugin.version} - - - - - - - + + + 4.0.0 + + org.springframework.samples.petclinic.admin + spring-petclinic-admin-server + jar + Spring Boot Admin server + + + org.springframework.samples + spring-petclinic-microservices + 2.3.6 + + + + 2.2.2 + 9090 + ${basedir}/../docker + + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.cloud + spring-cloud-starter-config + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + + + de.codecentric + spring-boot-admin-starter-server + ${spring-boot-admin.version} + + + de.codecentric + spring-boot-admin-server-ui + ${spring-boot-admin.version} + + + + + org.jolokia + jolokia-core + + + + + + + buildDocker + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + + + + + + diff --git a/spring-petclinic-admin-server/src/main/resources/bootstrap.yml b/spring-petclinic-admin-server/src/main/resources/bootstrap.yml index acd7449356..690d11c0d6 100644 --- a/spring-petclinic-admin-server/src/main/resources/bootstrap.yml +++ b/spring-petclinic-admin-server/src/main/resources/bootstrap.yml @@ -10,3 +10,16 @@ spring: cloud: config: uri: http://config-server:8888 +--- +spring: + profiles: local + cloud: + config: + uri: http://localhost:8888 +eureka: + client: + serviceUrl: + defaultZone: http://localhost:8761/eureka/ +server: + port: 9090 + diff --git a/spring-petclinic-api-gateway/pom.xml b/spring-petclinic-api-gateway/pom.xml index 98fc2fbb66..cde857fd1b 100644 --- a/spring-petclinic-api-gateway/pom.xml +++ b/spring-petclinic-api-gateway/pom.xml @@ -1,211 +1,211 @@ - - - 4.0.0 - - org.springframework.samples.petclinic.api - spring-petclinic-api-gateway - jar - Spring PetClinic API Gateway - - - org.springframework.samples - spring-petclinic-microservices - 2.2.1 - - - - 3.3.7-1 - 1.11.4 - 3.1.1-1 - 1.6.4 - 1.0.3 - 1.8.0 - 8081 - ${basedir}/../docker - - - - - - - org.springframework.boot - spring-boot-devtools - true - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-test - test - - - - - org.springframework.cloud - spring-cloud-sleuth-zipkin - - - org.springframework.cloud - spring-cloud-starter-circuitbreaker-reactor-resilience4j - - - org.springframework.cloud - spring-cloud-starter-config - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - org.springframework.cloud - spring-cloud-starter-netflix-ribbon - - - org.springframework.cloud - spring-cloud-netflix-ribbon - - - com.netflix.ribbon - ribbon-eureka - - - org.springframework.cloud - spring-cloud-netflix-hystrix - - - - - org.springframework.cloud - spring-cloud-starter-sleuth - - - org.springframework.cloud - spring-cloud-starter-gateway - - - - - org.jolokia - jolokia-core - - - org.projectlombok - lombok - - - io.micrometer - micrometer-registry-prometheus - - - io.github.resilience4j - resilience4j-micrometer - - - - - org.webjars - angularjs - ${webjars-angular.version} - - - org.webjars - jquery - ${webjars-jquery.version} - - - org.webjars - bootstrap - ${webjars-bootstrap.version} - - - org.webjars - angular-ui-router - ${webjars-angular-ui-router.version} - - - org.webjars - webjars-locator-core - - - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - com.squareup.okhttp3 - mockwebserver - test - - - - - - - ro.isdc.wro4j - wro4j-maven-plugin - ${wro4j.version} - - - generate-resources - - run - - - - - ro.isdc.wro.maven.plugin.manager.factory.ConfigurableWroManagerFactory - ${project.build.directory}/classes/static/css - ${basedir}/src/main/wro/wro.xml - ${basedir}/src/main/wro/wro.properties - ${basedir}/src/main/less - - - - org.webjars - bootstrap - ${webjars-bootstrap.version} - - - - - org.mockito - mockito-core - ${mockito.version} - - - - - - - - - buildDocker - - - - com.spotify - docker-maven-plugin - ${docker.plugin.version} - - - - - - - + + + 4.0.0 + + org.springframework.samples.petclinic.api + spring-petclinic-api-gateway + jar + Spring PetClinic API Gateway + + + org.springframework.samples + spring-petclinic-microservices + 2.3.6 + + + + 3.3.7-1 + 1.11.4 + 3.1.1-1 + 1.6.4 + 1.0.3 + 1.8.0 + 8081 + ${basedir}/../docker + + + + + + + org.springframework.boot + spring-boot-devtools + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.cloud + spring-cloud-sleuth-zipkin + + + org.springframework.cloud + spring-cloud-starter-circuitbreaker-reactor-resilience4j + + + org.springframework.cloud + spring-cloud-starter-config + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + + + org.springframework.cloud + spring-cloud-netflix-ribbon + + + com.netflix.ribbon + ribbon-eureka + + + org.springframework.cloud + spring-cloud-netflix-hystrix + + + + + org.springframework.cloud + spring-cloud-starter-sleuth + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + + org.jolokia + jolokia-core + + + org.projectlombok + lombok + + + io.micrometer + micrometer-registry-prometheus + + + io.github.resilience4j + resilience4j-micrometer + + + + + org.webjars + angularjs + ${webjars-angular.version} + + + org.webjars + jquery + ${webjars-jquery.version} + + + org.webjars + bootstrap + ${webjars-bootstrap.version} + + + org.webjars + angular-ui-router + ${webjars-angular-ui-router.version} + + + org.webjars + webjars-locator-core + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + com.squareup.okhttp3 + mockwebserver + test + + + + + + + ro.isdc.wro4j + wro4j-maven-plugin + ${wro4j.version} + + + generate-resources + + run + + + + + ro.isdc.wro.maven.plugin.manager.factory.ConfigurableWroManagerFactory + ${project.build.directory}/classes/static/css + ${basedir}/src/main/wro/wro.xml + ${basedir}/src/main/wro/wro.properties + ${basedir}/src/main/less + + + + org.webjars + bootstrap + ${webjars-bootstrap.version} + + + + + org.mockito + mockito-core + ${mockito.version} + + + + + + + + + buildDocker + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + + + + + + diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java index 0cb31c9b12..f3efd61050 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/CustomersServiceClient.java @@ -16,6 +16,7 @@ package org.springframework.samples.petclinic.api.application; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.samples.petclinic.api.dto.OwnerDetails; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @@ -31,7 +32,7 @@ public class CustomersServiceClient { private final WebClient.Builder webClientBuilder; - public Mono getOwner(final int ownerId) { + public Mono getOwner(final String ownerId) { return webClientBuilder.build().get() .uri("http://customers-service/owners/{ownerId}", ownerId) .retrieve() diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java index ffb3d848af..38a2db5085 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/application/VisitsServiceClient.java @@ -16,6 +16,7 @@ package org.springframework.samples.petclinic.api.application; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.samples.petclinic.api.dto.Visits; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; @@ -32,12 +33,11 @@ @RequiredArgsConstructor public class VisitsServiceClient { - // Could be changed for testing purpose private String hostname = "http://visits-service/"; private final WebClient.Builder webClientBuilder; - public Mono getVisitsForPets(final List petIds) { + public Mono getVisitsForPets(final List petIds) { return webClientBuilder.build() .get() .uri(hostname + "pets/visits?petId={petId}", joinIds(petIds)) @@ -45,7 +45,7 @@ public Mono getVisitsForPets(final List petIds) { .bodyToMono(Visits.class); } - private String joinIds(List petIds) { + private String joinIds(List petIds) { return petIds.stream().map(Object::toString).collect(joining(",")); } diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java index 3d7163e9cb..adde8d33f4 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayController.java @@ -46,7 +46,7 @@ public class ApiGatewayController { private final ReactiveCircuitBreakerFactory cbFactory; @GetMapping(value = "owners/{ownerId}") - public Mono getOwnerDetails(final @PathVariable int ownerId) { + public Mono getOwnerDetails(final @PathVariable String ownerId) { return customersServiceClient.getOwner(ownerId) .flatMap(owner -> visitsServiceClient.getVisitsForPets(owner.getPetIds()) @@ -64,7 +64,7 @@ private Function addVisitsToOwner(OwnerDetails owner) { owner.getPets() .forEach(pet -> pet.getVisits() .addAll(visits.getItems().stream() - .filter(v -> v.getPetId() == pet.getId()) + .filter(v -> v.getPetId().equals(pet.getId())) .collect(Collectors.toList())) ); return owner; diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/OwnerDetails.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/OwnerDetails.java index 5cea00a88f..8843ed01e1 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/OwnerDetails.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/OwnerDetails.java @@ -29,7 +29,7 @@ @Data public class OwnerDetails { - private int id; + private String id; private String firstName; @@ -44,7 +44,7 @@ public class OwnerDetails { private final List pets = new ArrayList<>(); @JsonIgnore - public List getPetIds() { + public List getPetIds() { return pets.stream() .map(PetDetails::getId) .collect(toList()); diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetDetails.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetDetails.java index e9630d07da..9f5b9342cd 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetDetails.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/PetDetails.java @@ -26,13 +26,13 @@ @Data public class PetDetails { - private int id; + private String id; private String name; private String birthDate; - private PetType type; + private String type; private final List visits = new ArrayList<>(); diff --git a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/VisitDetails.java b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/VisitDetails.java index de3a405439..a3e1d65ba5 100644 --- a/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/VisitDetails.java +++ b/spring-petclinic-api-gateway/src/main/java/org/springframework/samples/petclinic/api/dto/VisitDetails.java @@ -25,9 +25,9 @@ @NoArgsConstructor public class VisitDetails { - private Integer id = null; + private String id = null; - private Integer petId = null; + private String petId = null; private String date = null; diff --git a/spring-petclinic-api-gateway/src/main/resources/application.yml b/spring-petclinic-api-gateway/src/main/resources/application.yml index f3bc0ee829..c0f79c4630 100644 --- a/spring-petclinic-api-gateway/src/main/resources/application.yml +++ b/spring-petclinic-api-gateway/src/main/resources/application.yml @@ -6,20 +6,76 @@ spring: gateway: routes: - id: vets-service - uri: http://vets-service + uri: lb://vets-service predicates: - Path=/api/vet/** filters: - StripPrefix=2 - id: visits-service - uri: http://visits-service + uri: lb://visits-service predicates: - Path=/api/visit/** filters: - StripPrefix=2 - id: customers-service - uri: http://customers-service + uri: lb://customers-service predicates: - Path=/api/customer/** filters: - StripPrefix=2 +--- +spring: + profiles: docker + cloud: + loadbalancer: + ribbon: + enabled: false + gateway: + routes: + - id: vets-service + uri: lb://vets-service + predicates: + - Path=/api/vet/** + filters: + - StripPrefix=2 + - id: visits-service + uri: lb://visits-service + predicates: + - Path=/api/visit/** + filters: + - StripPrefix=2 + - id: customers-service + uri: lb://customers-service + predicates: + - Path=/api/customer/** + filters: + - StripPrefix=2 + +--- +spring: + profiles: local + cloud: + loadbalancer: + ribbon: + enabled: false + gateway: + routes: + - id: vets-service + uri: http://localhost:8083 + predicates: + - Path=/api/vet/** + filters: + - StripPrefix=2 + - id: visits-service + uri: http://localhost:8082 + predicates: + - Path=/api/visit/** + filters: + - StripPrefix=2 + - id: customers-service + uri: http://localhost:8081 + predicates: + - Path=/api/customer/** + filters: + - StripPrefix=2 + diff --git a/spring-petclinic-api-gateway/src/main/resources/bootstrap.yml b/spring-petclinic-api-gateway/src/main/resources/bootstrap.yml index f4cc782f9e..3b646f220a 100644 --- a/spring-petclinic-api-gateway/src/main/resources/bootstrap.yml +++ b/spring-petclinic-api-gateway/src/main/resources/bootstrap.yml @@ -1,12 +1,28 @@ spring: - cloud: - config: - uri: http://localhost:8888 - application: - name: api-gateway + cloud: + config: + uri: http://localhost:8888 + application: + name: api-gateway --- spring: profiles: docker cloud: config: uri: http://config-server:8888 +server: + port: 8080 +--- +spring: + profiles: local + cloud: + config: + uri: http://localhost:8888 +eureka: + client: + serviceUrl: + defaultZone: http://localhost:8761/eureka/ +server: + port: 8080 + + diff --git a/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-details/owner-details.template.html b/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-details/owner-details.template.html index ed2810f9ca..1edb3556d5 100644 --- a/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-details/owner-details.template.html +++ b/spring-petclinic-api-gateway/src/main/resources/static/scripts/owner-details/owner-details.template.html @@ -38,7 +38,7 @@

Pets and Visits

Birth Date
{{pet.birthDate | date:'yyyy MMM dd'}}
Type
-
{{pet.type.name}}
+
{{pet.type}}
diff --git a/spring-petclinic-api-gateway/src/main/resources/static/scripts/pet-form/pet-form.template.html b/spring-petclinic-api-gateway/src/main/resources/static/scripts/pet-form/pet-form.template.html index 76317e5575..80ae3db889 100644 --- a/spring-petclinic-api-gateway/src/main/resources/static/scripts/pet-form/pet-form.template.html +++ b/spring-petclinic-api-gateway/src/main/resources/static/scripts/pet-form/pet-form.template.html @@ -27,8 +27,8 @@

Pet

- +
diff --git a/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/application/VisitsServiceClientIntegrationTest.java b/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/application/VisitsServiceClientIntegrationTest.java index 62e74c4907..5e8744be42 100644 --- a/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/application/VisitsServiceClientIntegrationTest.java +++ b/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/application/VisitsServiceClientIntegrationTest.java @@ -18,7 +18,7 @@ class VisitsServiceClientIntegrationTest { - private static final Integer PET_ID = 1; + private static final String PET_ID = "1"; private VisitsServiceClient visitsServiceClient; @@ -42,13 +42,13 @@ void getVisitsForPets_withAvailableVisitsService() { .setHeader("Content-Type", "application/json") .setBody("{\"items\":[{\"id\":5,\"date\":\"2018-11-15\",\"description\":\"test visit\",\"petId\":1}]}")); - Mono visits = visitsServiceClient.getVisitsForPets(Collections.singletonList(1)); + Mono visits = visitsServiceClient.getVisitsForPets(Collections.singletonList("1")); assertVisitDescriptionEquals(visits.block(), PET_ID,"test visit"); } - private void assertVisitDescriptionEquals(Visits visits, int petId, String description) { + private void assertVisitDescriptionEquals(Visits visits, String petId, String description) { assertEquals(1, visits.getItems().size()); assertNotNull(visits.getItems().get(0)); assertEquals(petId, visits.getItems().get(0).getPetId()); diff --git a/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayControllerTest.java b/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayControllerTest.java index 9f5707e681..9ae998463d 100644 --- a/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayControllerTest.java +++ b/spring-petclinic-api-gateway/src/test/java/org/springframework/samples/petclinic/api/boundary/web/ApiGatewayControllerTest.java @@ -40,16 +40,16 @@ class ApiGatewayControllerTest { void getOwnerDetails_withAvailableVisitsService() { OwnerDetails owner = new OwnerDetails(); PetDetails cat = new PetDetails(); - cat.setId(20); + cat.setId("20"); cat.setName("Garfield"); owner.getPets().add(cat); Mockito - .when(customersServiceClient.getOwner(1)) + .when(customersServiceClient.getOwner("1")) .thenReturn(Mono.just(owner)); Visits visits = new Visits(); VisitDetails visit = new VisitDetails(); - visit.setId(300); + visit.setId("300"); visit.setDescription("First visit"); visit.setPetId(cat.getId()); visits.getItems().add(visit); @@ -76,11 +76,11 @@ void getOwnerDetails_withAvailableVisitsService() { void getOwnerDetails_withServiceError() { OwnerDetails owner = new OwnerDetails(); PetDetails cat = new PetDetails(); - cat.setId(20); + cat.setId("20"); cat.setName("Garfield"); owner.getPets().add(cat); Mockito - .when(customersServiceClient.getOwner(1)) + .when(customersServiceClient.getOwner("1")) .thenReturn(Mono.just(owner)); Mockito diff --git a/spring-petclinic-config-server/pom.xml b/spring-petclinic-config-server/pom.xml index b8c5ab8cd8..4a963f59cd 100644 --- a/spring-petclinic-config-server/pom.xml +++ b/spring-petclinic-config-server/pom.xml @@ -1,69 +1,69 @@ - - - 4.0.0 - - org.springframework.samples.petclinic.config - spring-petclinic-config-server - jar - Spring PetClinic Config Server - - - org.springframework.samples - spring-petclinic-microservices - 2.2.1 - - - - 8888 - ${basedir}/../docker - - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - org.springframework.cloud - spring-cloud-config-server - - - - - org.jolokia - jolokia-core - - - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - - - - buildDocker - - - - com.spotify - docker-maven-plugin - ${docker.plugin.version} - - - - - - + + + 4.0.0 + + org.springframework.samples.petclinic.config + spring-petclinic-config-server + jar + Spring PetClinic Config Server + + + org.springframework.samples + spring-petclinic-microservices + 2.3.6 + + + + 8888 + ${basedir}/../docker + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.cloud + spring-cloud-config-server + + + + + org.jolokia + jolokia-core + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + buildDocker + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + + + + + diff --git a/spring-petclinic-customers-service/pom.xml b/spring-petclinic-customers-service/pom.xml index b10c75e531..93292feb58 100644 --- a/spring-petclinic-customers-service/pom.xml +++ b/spring-petclinic-customers-service/pom.xml @@ -11,7 +11,7 @@ org.springframework.samples spring-petclinic-microservices - 2.2.1 + 2.3.6 @@ -26,10 +26,6 @@ org.springframework.boot spring-boot-starter-actuator - - org.springframework.boot - spring-boot-starter-data-jpa - org.springframework.boot spring-boot-starter-test @@ -39,6 +35,24 @@ org.springframework.boot spring-boot-starter-web + + com.azure + azure-spring-data-cosmos + 3.2.0 + + + com.azure.spring + azure-spring-boot-starter-keyvault-secrets + 3.1.0 + + + org.springframework.data + spring-data-rest-core + + + org.springframework.boot + spring-boot-starter-data-redis + @@ -60,14 +74,9 @@ - mysql - mysql-connector-java - runtime - - - org.hsqldb - hsqldb - runtime + org.springframework.boot + spring-boot-configuration-processor + true org.jolokia @@ -81,6 +90,17 @@ io.micrometer micrometer-registry-prometheus + + javax.validation + validation-api + + + commons-jxpath + commons-jxpath + 1.3 + compile + + @@ -98,7 +118,28 @@ assertj-core test - + + junit + junit + test + + + jakarta.persistence + jakarta.persistence-api + + + + diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/CustomersServiceApplication.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/CustomersServiceApplication.java index 3c757c9d47..20ded28fc9 100644 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/CustomersServiceApplication.java +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/CustomersServiceApplication.java @@ -15,18 +15,31 @@ */ package org.springframework.samples.petclinic.customers; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.samples.petclinic.customers.model.OwnerRepository; +import org.springframework.samples.petclinic.customers.model.PetRepository; +// For AAD Sample +//import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; /** * @author Maciej Szarlinski */ + + +// For AAD Sample +//@EnableWebSecurity +//@EnableGlobalMethodSecurity(prePostEnabled = true) +@EnableCaching @EnableDiscoveryClient @SpringBootApplication public class CustomersServiceApplication { - public static void main(String[] args) { - SpringApplication.run(CustomersServiceApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(CustomersServiceApplication.class, args); + } } diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/PopulateSeedData.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/PopulateSeedData.java new file mode 100644 index 0000000000..ebd8e7d1fa --- /dev/null +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/PopulateSeedData.java @@ -0,0 +1,98 @@ +package org.springframework.samples.petclinic.customers; + +import com.azure.cosmos.implementation.guava25.collect.Lists; +import lombok.SneakyThrows; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.customers.model.Owner; +import org.springframework.samples.petclinic.customers.model.OwnerRepository; +import org.springframework.samples.petclinic.customers.model.Pet; +import org.springframework.samples.petclinic.customers.model.PetRepository; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; + +import javax.annotation.PostConstruct; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + + +@Component +public class PopulateSeedData { + + private static final Logger logger = LoggerFactory.getLogger(PopulateSeedData.class); + + @Autowired + private OwnerRepository ownerRepository; + + @Autowired + private PetRepository petRepository; + + + @PostConstruct + public void populateSeedData() { + + final Pet pet1 = new Pet("1", "Leo", parseDate("2000-09-07"), "cat"); + final Pet pet2 = new Pet("2", "Basil", parseDate("2002-08-06"), "hamster"); + final Pet pet3 = new Pet("3", "Rosy", parseDate("2001-04-17"), "dog"); + final Pet pet4 = new Pet("4", "Jewel", parseDate("2000-03-07"), "dog"); + final Pet pet5 = new Pet("5", "Iggy", parseDate("2000-11-30"), "lizard"); + final Pet pet6 = new Pet("6", "George", parseDate("2000-01-20"), "snake"); + final Pet pet7 = new Pet("7", "Samantha", parseDate("1995-09-04"), "cat"); + final Pet pet8 = new Pet("8", "Max", parseDate("1995-09-04"), "cat"); + final Pet pet9 = new Pet("9", "Lucky", parseDate("1999-08-06"), "bird"); + final Pet pet10 = new Pet("10", "Mulligan", parseDate("1997-02-24"), "dog"); + final Pet pet11 = new Pet("11", "Freddy", parseDate("2000-03-09"), "bird"); + final Pet pet12 = new Pet("12", "Lucky", parseDate("2000-06-24"), "dog"); + final Pet pet13 = new Pet("13", "Sly", parseDate("2002-06-08"), "cat"); + + final Owner owner1 = new Owner("1", "George", "Franklin", "110 W. Liberty St.", "Madison", "6085551023",new HashSet() {{ + add(pet1); + }}); + final Owner owner2 = new Owner("2", "Betty", "Davis", "638 Cardinal Ave.", "Sun Prairie", "6085551749", new HashSet() {{ + add(pet2); + }}); + final Owner owner3 = new Owner("3", "Eduardo", "Rodriquez", "2693 Commerce St.", "McFarland", "6085558763", new HashSet() {{ + add(pet3); + add(pet4); + }}); + final Owner owner4 = new Owner("4", "Harold", "Davis", "563 Friendly St.", "Windsor", "6085553198", new HashSet() {{ + add(pet5); + }}); + final Owner owner5 = new Owner("5", "Peter", "McTavish", "2387 S. Fair Way", "Madison", "6085552765", new HashSet() {{ + add(pet6); + }}); + final Owner owner6 = new Owner("6", "Jean", "Coleman", "105 N. Lake St.", "Monona", "6085552654", new HashSet() {{ + add(pet7); + add(pet8); + }}); + final Owner owner7 = new Owner("7", "Jeff", "Black", "1450 Oak Blvd.", "Monona", "6085555387", new HashSet() {{ + add(pet9); + }}); + final Owner owner8 = new Owner("8", "Maria", "Escobito", "345 Maple St.", "Madison", "6085557683", new HashSet() {{ + add(pet10); + }}); + final Owner owner9 = new Owner("9", "David", "Schroeder", "2749 Blackhawk Trail", "Madison", "6085559435", new HashSet() {{ + add(pet11); + }}); + final Owner owner10 = new Owner("10", "Carlos", "Estaban", "2335 Independence La.", "Waunakee", "6085555487", new HashSet() {{ + add(pet12); + add(pet13); + }}); + + + this.ownerRepository.saveAll(Lists.newArrayList(owner1, owner2, owner3, owner4, owner5, owner6, owner7, owner8, owner9, owner10)); + this.petRepository.saveAll(Lists.newArrayList(pet1, pet2, pet3, pet4, pet5, pet6, pet7, pet8, pet9, pet10, pet11, pet12, pet13)); + } + + public static Date parseDate(String date) { + try { + return new SimpleDateFormat("yyyy-MM-dd").parse(date); + } catch (ParseException e) { + return null; + } + } +} diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/CosmosProperties.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/CosmosProperties.java new file mode 100644 index 0000000000..1e4c95777b --- /dev/null +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/CosmosProperties.java @@ -0,0 +1,22 @@ +package org.springframework.samples.petclinic.customers.model; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + + +@Getter +@Setter +@ConfigurationProperties(prefix = "azure.cosmosdb") +public class CosmosProperties { + + private String uri; + + private String key; + + private String secondaryKey; + + private String database; + + private boolean populateQueryMetrics; +} diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/CustomersAppConfiguration.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/CustomersAppConfiguration.java new file mode 100644 index 0000000000..9c6f128c61 --- /dev/null +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/CustomersAppConfiguration.java @@ -0,0 +1,79 @@ +package org.springframework.samples.petclinic.customers.model; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +/** + * WARNING: MODIFYING THIS FILE WILL REQUIRE CORRESPONDING UPDATES TO README.md FILE. LINE NUMBERS + * ARE USED TO EXTRACT APPROPRIATE CODE SEGMENTS FROM THIS FILE. ADD NEW CODE AT THE BOTTOM TO AVOID CHANGING + * LINE NUMBERS OF EXISTING CODE SAMPLES. + */ + +import com.azure.core.credential.AzureKeyCredential; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.DirectConnectionConfig; +import com.azure.cosmos.GatewayConnectionConfig; +import com.azure.spring.data.cosmos.config.AbstractCosmosConfiguration; +import com.azure.spring.data.cosmos.config.CosmosConfig; +import com.azure.spring.data.cosmos.core.ResponseDiagnostics; +import com.azure.spring.data.cosmos.core.ResponseDiagnosticsProcessor; +import com.azure.spring.data.cosmos.repository.config.EnableCosmosRepositories; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.lang.Nullable; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +@Configuration +@EnableConfigurationProperties(CosmosProperties.class) +@EnableCosmosRepositories +@PropertySource("classpath:application.properties") +public class CustomersAppConfiguration extends AbstractCosmosConfiguration { + + private static final Logger logger = LoggerFactory.getLogger(CustomersAppConfiguration.class); + + @Autowired + private CosmosProperties properties; + + private AzureKeyCredential azureKeyCredential; + + @Bean + public CosmosClientBuilder cosmosClientBuilder() { + this.azureKeyCredential = new AzureKeyCredential(properties.getKey()); + DirectConnectionConfig directConnectionConfig = new DirectConnectionConfig(); + GatewayConnectionConfig gatewayConnectionConfig = new GatewayConnectionConfig(); + return new CosmosClientBuilder() + .endpoint(properties.getUri()) + .credential(azureKeyCredential) + .directMode(directConnectionConfig, gatewayConnectionConfig); + } + + @Override + public CosmosConfig cosmosConfig() { + return CosmosConfig.builder() + .enableQueryMetrics(properties.isPopulateQueryMetrics()) + .responseDiagnosticsProcessor(new ResponseDiagnosticsProcessorImplementation()) + .build(); + } + + + public void switchToSecondaryKey() { + this.azureKeyCredential.update(properties.getSecondaryKey()); + } + + @Override + protected String getDatabaseName() { + return properties.getDatabase(); + } + + private static class ResponseDiagnosticsProcessorImplementation implements ResponseDiagnosticsProcessor { + + @Override + public void processResponseDiagnostics(@Nullable ResponseDiagnostics responseDiagnostics) { + logger.info("Response Diagnostics {}", responseDiagnostics); + } + } +} diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/Owner.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/Owner.java index 360e7652f3..498a197a37 100644 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/Owner.java +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/Owner.java @@ -15,28 +15,28 @@ */ package org.springframework.samples.petclinic.customers.model; +import com.azure.spring.data.cosmos.core.mapping.Container; +import com.azure.spring.data.cosmos.core.mapping.GeneratedValue; +import com.azure.spring.data.cosmos.core.mapping.PartitionKey; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import net.minidev.json.annotate.JsonIgnore; +import org.springframework.beans.support.MutableSortDefinition; +import org.springframework.beans.support.PropertyComparator; +import org.springframework.core.style.ToStringCreator; +import org.springframework.data.annotation.Id; + +import javax.validation.constraints.Digits; +import javax.validation.constraints.NotEmpty; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.OneToMany; -import javax.persistence.Table; -import javax.validation.constraints.Digits; -import javax.validation.constraints.NotEmpty; - -import org.springframework.beans.support.MutableSortDefinition; -import org.springframework.beans.support.PropertyComparator; -import org.springframework.core.style.ToStringCreator; - /** * Simple JavaBean domain object representing an owner. * @@ -46,39 +46,31 @@ * @author Michael Isvy * @author Maciej Szarlinski */ -@Entity -@Table(name = "owners") -public class Owner { +@Container(containerName = "owners") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class Owner implements Serializable { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; + @GeneratedValue + private String id; - @Column(name = "first_name") - @NotEmpty private String firstName; - @Column(name = "last_name") - @NotEmpty + @PartitionKey private String lastName; - @Column(name = "address") - @NotEmpty private String address; - @Column(name = "city") - @NotEmpty private String city; - @Column(name = "telephone") - @NotEmpty - @Digits(fraction = 0, integer = 10) private String telephone; - @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "owner") private Set pets; - public Integer getId() { + public String getId() { return id; } @@ -137,14 +129,12 @@ public List getPets() { public void addPet(Pet pet) { getPetsInternal().add(pet); - pet.setOwner(this); } @Override public String toString() { return new ToStringCreator(this) - - .append("id", this.getId()) + .append("id", this.getId()) .append("lastName", this.getLastName()) .append("firstName", this.getFirstName()) .append("address", this.address) diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/OwnerRepository.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/OwnerRepository.java index a9eec088a2..f7fd9f6d4d 100644 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/OwnerRepository.java +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/OwnerRepository.java @@ -15,7 +15,8 @@ */ package org.springframework.samples.petclinic.customers.model; -import org.springframework.data.jpa.repository.JpaRepository; +import com.azure.spring.data.cosmos.repository.CosmosRepository; +import org.springframework.stereotype.Repository; /** * Repository class for Owner domain objects All method names are compliant with Spring Data naming @@ -27,4 +28,6 @@ * @author Michael Isvy * @author Maciej Szarlinski */ -public interface OwnerRepository extends JpaRepository { } +@Repository +public interface OwnerRepository extends CosmosRepository { +} diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/Pet.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/Pet.java index e0d28a9fd9..8e8925a173 100644 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/Pet.java +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/Pet.java @@ -15,21 +15,15 @@ */ package org.springframework.samples.petclinic.customers.model; -import java.util.Date; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; - +import com.azure.spring.data.cosmos.core.mapping.Container; +import com.azure.spring.data.cosmos.core.mapping.GeneratedValue; import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.*; import org.springframework.core.style.ToStringCreator; +import org.springframework.data.annotation.Id; + +import java.io.Serializable; +import java.util.Date; /** * Simple business object representing a pet. @@ -39,34 +33,30 @@ * @author Sam Brannen * @author Maciej Szarlinski */ -@Entity -@Table(name = "pets") -public class Pet { +@Container(containerName = "pets") +@Builder(builderMethodName = "pet") +@AllArgsConstructor +@NoArgsConstructor +public class Pet implements Serializable { + @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; + @GeneratedValue + private String id; - @Column(name = "name") private String name; - @Column(name = "birth_date") - @Temporal(TemporalType.DATE) private Date birthDate; - @ManyToOne - @JoinColumn(name = "type_id") - private PetType type; + private String petType; - @ManyToOne - @JoinColumn(name = "owner_id") - @JsonIgnore - private Owner owner; +// @JsonIgnore +// private Owner owner; - public Integer getId() { + public String getId() { return id; } - public void setId(final Integer id) { + public void setId(final String id) { this.id = id; } @@ -86,21 +76,21 @@ public void setBirthDate(final Date birthDate) { this.birthDate = birthDate; } - public PetType getType() { - return type; - } - - public void setType(final PetType type) { - this.type = type; + public String getType() { + return petType; } - public Owner getOwner() { - return owner; + public void setType(final String type) { + this.petType = type; } - public void setOwner(final Owner owner) { - this.owner = owner; - } +// public Owner getOwner() { +// return owner; +// } +// +// public void setOwner(final Owner owner) { +// this.owner = owner; +// } @Override public String toString() { @@ -108,9 +98,10 @@ public String toString() { .append("id", this.getId()) .append("name", this.getName()) .append("birthDate", this.getBirthDate()) - .append("type", this.getType().getName()) - .append("ownerFirstname", this.getOwner().getFirstName()) - .append("ownerLastname", this.getOwner().getLastName()) + .append("type", this.getType() + ) +// .append("ownerFirstname", this.getOwner().getFirstName()) +// .append("ownerLastname", this.getOwner().getLastName()) .toString(); } diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/PetRepository.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/PetRepository.java index 93feb49abf..38a3fd4355 100644 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/PetRepository.java +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/PetRepository.java @@ -14,13 +14,11 @@ * limitations under the License. */ package org.springframework.samples.petclinic.customers.model; +import com.azure.spring.data.cosmos.repository.CosmosRepository; +import com.azure.spring.data.cosmos.repository.Query; +import org.springframework.stereotype.Repository; import java.util.List; -import java.util.Optional; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; /** * Repository class for Pet domain objects All method names are compliant with Spring Data naming @@ -32,18 +30,9 @@ * @author Michael Isvy * @author Maciej Szarlinski */ -public interface PetRepository extends JpaRepository { - - /** - * Retrieve all {@link PetType}s from the data store. - * @return a Collection of {@link PetType}s. - */ - @Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name") - List findPetTypes(); - - @Query("FROM PetType ptype WHERE ptype.id = :typeId") - Optional findPetTypeById(@Param("typeId") int typeId); - - +@Repository +public interface PetRepository extends CosmosRepository { + @Query(value = "select DISTINCT VALUE p.type from Pet p") + List getPetTypes(); } diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/PetType.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/PetType.java deleted file mode 100644 index 5ca44874b2..0000000000 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/model/PetType.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.samples.petclinic.customers.model; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; - -/** - * @author Juergen Hoeller - * Can be Cat, Dog, Hamster... - */ -@Entity -@Table(name = "types") -public class PetType { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - @Column(name = "name") - private String name; - - public Integer getId() { - return id; - } - - public void setId(final Integer id) { - this.id = id; - } - - public String getName() { - return this.name; - } -} diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java index c9ea4b929f..2d3a4ddcc8 100644 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/OwnerResource.java @@ -18,12 +18,21 @@ import io.micrometer.core.annotation.Timed; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.http.HttpStatus; import org.springframework.samples.petclinic.customers.model.Owner; import org.springframework.samples.petclinic.customers.model.OwnerRepository; +import org.springframework.samples.petclinic.customers.model.Pet; import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +// Required for AAD starter to work. For sample only +//import org.springframework.security.access.prepost.PreAuthorize; + import javax.validation.Valid; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -34,6 +43,8 @@ * @author Michael Isvy * @author Maciej Szarlinski */ + +//@PreAuthorize("hasRole('ROLE_owners')") // For AAD sample @RequestMapping("/owners") @RestController @Timed("petclinic.owner") @@ -45,6 +56,8 @@ class OwnerResource { /** * Create Owner + * + * @return */ @PostMapping @ResponseStatus(HttpStatus.CREATED) @@ -54,18 +67,45 @@ public Owner createOwner(@Valid @RequestBody Owner owner) { /** * Read single Owner + * + * @return */ @GetMapping(value = "/{ownerId}") - public Optional findOwner(@PathVariable("ownerId") int ownerId) { - return ownerRepository.findById(ownerId); + public Optional findOwner(@PathVariable("ownerId") String ownerId) { + return ownerRepository.findById(ownerId); } /** * Read List of Owners */ @GetMapping - public List findAll() { - return ownerRepository.findAll(); + @Cacheable("owners") + public List findAll() { + List list = new ArrayList<>(); + ownerRepository.findAll().forEach(list::add); + return list; + } + + /** + * Clears the cache for all Owners + */ + @GetMapping(value = "/clearcache") + @CacheEvict("owners") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void clearAllCache() { + } + + /** + * Read List of Owners + */ + @GetMapping(value = "/{ownerId}/pets") + public List findAllPets(@PathVariable("ownerId") String ownerId) { + Optional optionalOwner = ownerRepository.findById(ownerId); + List list = new ArrayList<>(); + if(optionalOwner.isPresent()) { + list = optionalOwner.get().getPets(); + } + return list; } /** @@ -73,17 +113,16 @@ public List findAll() { */ @PutMapping(value = "/{ownerId}") @ResponseStatus(HttpStatus.NO_CONTENT) - public void updateOwner(@PathVariable("ownerId") int ownerId, @Valid @RequestBody Owner ownerRequest) { - final Optional owner = ownerRepository.findById(ownerId); - - final Owner ownerModel = owner.orElseThrow(() -> new ResourceNotFoundException("Owner "+ownerId+" not found")); - // This is done by hand for simplicity purpose. In a real life use-case we should consider using MapStruct. - ownerModel.setFirstName(ownerRequest.getFirstName()); - ownerModel.setLastName(ownerRequest.getLastName()); - ownerModel.setCity(ownerRequest.getCity()); - ownerModel.setAddress(ownerRequest.getAddress()); - ownerModel.setTelephone(ownerRequest.getTelephone()); - log.info("Saving owner {}", ownerModel); - ownerRepository.save(ownerModel); + public void updateOwner(@PathVariable("ownerId") String ownerId, @Valid @RequestBody Owner ownerRequest) { + Optional ownerOptional = ownerRepository.findById(ownerId); + ownerOptional.ifPresent(owner -> { + owner.setFirstName(ownerRequest.getFirstName()); + owner.setLastName(ownerRequest.getLastName()); + owner.setCity(ownerRequest.getCity()); + owner.setAddress(ownerRequest.getAddress()); + owner.setTelephone(ownerRequest.getTelephone()); + log.info("Saving owner {}", owner); + ownerRepository.save(owner); + }); } } diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetDetails.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetDetails.java index 2cda1e7878..94accb7909 100644 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetDetails.java +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetDetails.java @@ -16,12 +16,12 @@ package org.springframework.samples.petclinic.customers.web; import lombok.Data; - -import java.util.Date; - import org.springframework.format.annotation.DateTimeFormat; import org.springframework.samples.petclinic.customers.model.Pet; -import org.springframework.samples.petclinic.customers.model.PetType; +import reactor.core.publisher.Mono; + +import java.util.Date; +import java.util.Optional; /** * @author mszarlinski@bravurasolutions.com on 2016-12-05. @@ -29,22 +29,24 @@ @Data class PetDetails { - private long id; + private String id; private String name; - private String owner; +// private String owner; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date birthDate; - private PetType type; + private String type; - PetDetails(Pet pet) { - this.id = pet.getId(); - this.name = pet.getName(); - this.owner = pet.getOwner().getFirstName() + " " + pet.getOwner().getLastName(); - this.birthDate = pet.getBirthDate(); - this.type = pet.getType(); + PetDetails(Optional petInfo) { + petInfo.ifPresent(pet -> { + this.id = pet.getId(); + this.name = pet.getName(); + // this.owner = pet.getOwner().getFirstName() + " " + pet.getOwner().getLastName(); + this.birthDate = pet.getBirthDate(); + this.type = pet.getType(); + }); } } diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetRequest.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetRequest.java index f7b68f6504..fda5af0b8e 100644 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetRequest.java +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetRequest.java @@ -28,7 +28,7 @@ */ @Data class PetRequest { - private int id; + private String id; @JsonFormat(pattern = "yyyy-MM-dd") private Date birthDate; @@ -36,5 +36,5 @@ class PetRequest { @Size(min = 1) private String name; - private int typeId; + private String type; } diff --git a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java index 3b8bdd8870..c47715b7b5 100644 --- a/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java +++ b/spring-petclinic-customers-service/src/main/java/org/springframework/samples/petclinic/customers/web/PetResource.java @@ -19,8 +19,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.samples.petclinic.customers.model.*; +import org.springframework.samples.petclinic.customers.model.Owner; +import org.springframework.samples.petclinic.customers.model.OwnerRepository; +import org.springframework.samples.petclinic.customers.model.Pet; +import org.springframework.samples.petclinic.customers.model.PetRepository; import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; import java.util.List; import java.util.Optional; @@ -40,58 +44,59 @@ class PetResource { private final PetRepository petRepository; private final OwnerRepository ownerRepository; - @GetMapping("/petTypes") - public List getPetTypes() { - return petRepository.findPetTypes(); + public List getPetTypes() { + List petTypes = petRepository.getPetTypes(); + return petTypes; } @PostMapping("/owners/{ownerId}/pets") @ResponseStatus(HttpStatus.CREATED) public Pet processCreationForm( @RequestBody PetRequest petRequest, - @PathVariable("ownerId") int ownerId) { + @PathVariable("ownerId") String ownerId) { final Pet pet = new Pet(); - final Optional optionalOwner = ownerRepository.findById(ownerId); - Owner owner = optionalOwner.orElseThrow(() -> new ResourceNotFoundException("Owner "+ownerId+" not found")); - owner.addPet(pet); + Optional optionalOwner = ownerRepository.findById(ownerId); + optionalOwner.ifPresent(owner -> { + owner.addPet(pet); + }); return save(pet, petRequest); } @PutMapping("/owners/*/pets/{petId}") @ResponseStatus(HttpStatus.NO_CONTENT) public void processUpdateForm(@RequestBody PetRequest petRequest) { - int petId = petRequest.getId(); - Pet pet = findPetById(petId); - save(pet, petRequest); + String petId = petRequest.getId(); + Optional petOptional = findPetById(petId); + petOptional.ifPresent(pet -> { + save(pet, petRequest); + }); } private Pet save(final Pet pet, final PetRequest petRequest) { pet.setName(petRequest.getName()); pet.setBirthDate(petRequest.getBirthDate()); - - petRepository.findPetTypeById(petRequest.getTypeId()) - .ifPresent(pet::setType); + pet.setType(petRequest.getType()); log.info("Saving pet {}", pet); return petRepository.save(pet); } @GetMapping("owners/*/pets/{petId}") - public PetDetails findPet(@PathVariable("petId") int petId) { + public PetDetails findPet(@PathVariable("petId") String petId) { return new PetDetails(findPetById(petId)); } + private Optional findPetById(String petId) { + Optional optionalPet = petRepository.findById(petId); - private Pet findPetById(int petId) { - Optional pet = petRepository.findById(petId); - if (!pet.isPresent()) { - throw new ResourceNotFoundException("Pet "+petId+" not found"); + if (!optionalPet.isPresent()) { + throw new ResourceNotFoundException("Pet " + petId + " not found"); } - return pet.get(); + return optionalPet; } } diff --git a/spring-petclinic-customers-service/src/main/resources/application.properties b/spring-petclinic-customers-service/src/main/resources/application.properties index e69de29bb2..c2176b9b31 100644 --- a/spring-petclinic-customers-service/src/main/resources/application.properties +++ b/spring-petclinic-customers-service/src/main/resources/application.properties @@ -0,0 +1,34 @@ +azure.keyvault.enabled=true +azure.keyvault.uri=${AZURE_KEYVAULT_URI} +azure.keyvault.client-id=${AZURE_KEYVAULT_CLIENTID} +azure.keyvault.client-key=${AZURE_KEYVAULT_CLIENTKEY} +azure.keyvault.tenant-id=${AZURE_KEYVAULT_TENANTID} +azure.keyvault.token-acquire-timeout-seconds=60 +azure.keyvault.refresh-interval=1800000 +azure.keyvault.secret-keys=cosmosdburi,cosmosdbkey,cosmosdbsecondarykey, redisuri, redispassword + +# Azure cosmos db properties +azure.cosmosdb.uri=${cosmosdburi} +azure.cosmosdb.key=${cosmosdbkey} +azure.cosmosdb.secondaryKey=${cosmosdbsecondarykey} +azure.cosmosdb.database=end2endsample +# Populate query metrics +cosmos.queryMetricsEnabled=true + +# Azure redis cache properties +spring.cache.type=redis +spring.redis.host=${redisuri} +spring.redis.password=${redispassword} +spring.redis.port=6380 +spring.redis.ssl=true + +# Azure AAD properties ## For AAD Sample code +## Specifies your Active Directory ID: +#azure.activedirectory.tenant-id=22222222-2222-2222-2222-222222222222 +## Specifies your App Registration's Application ID: +#azure.activedirectory.client-id=11111111-1111-1111-1111-1111111111111111 +## Specifies your App Registration's secret key: +#azure.activedirectory.client-secret=AbCdEfGhIjKlMnOpQrStUvWxYz== +## Specifies the list of Active Directory groups to use for authorization: +#azure.activedirectory.user-group.allowed-groups=group1 + diff --git a/spring-petclinic-customers-service/src/main/resources/bootstrap.yml b/spring-petclinic-customers-service/src/main/resources/bootstrap.yml index c0f6dca616..476bc55781 100644 --- a/spring-petclinic-customers-service/src/main/resources/bootstrap.yml +++ b/spring-petclinic-customers-service/src/main/resources/bootstrap.yml @@ -1,3 +1,7 @@ +eureka: + client: + register-with-eureka: true + fetch-registry: true spring: cloud: config: @@ -10,3 +14,16 @@ spring: cloud: config: uri: http://config-server:8888 + +--- +spring: + profiles: local + cloud: + config: + uri: http://localhost:8888 +server: + port: 8081 +eureka: + client: + serviceUrl: + defaultZone: http://localhost:8761/eureka/ diff --git a/spring-petclinic-customers-service/src/main/resources/db/hsqldb/data.sql b/spring-petclinic-customers-service/src/main/resources/db/hsqldb/data.sql deleted file mode 100644 index 676b55f3d6..0000000000 --- a/spring-petclinic-customers-service/src/main/resources/db/hsqldb/data.sql +++ /dev/null @@ -1,31 +0,0 @@ -INSERT INTO types VALUES (1, 'cat'); -INSERT INTO types VALUES (2, 'dog'); -INSERT INTO types VALUES (3, 'lizard'); -INSERT INTO types VALUES (4, 'snake'); -INSERT INTO types VALUES (5, 'bird'); -INSERT INTO types VALUES (6, 'hamster'); - -INSERT INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); -INSERT INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); -INSERT INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); -INSERT INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); -INSERT INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); -INSERT INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); -INSERT INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); -INSERT INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); -INSERT INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); -INSERT INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); - -INSERT INTO pets VALUES (1, 'Leo', '2010-09-07', 1, 1); -INSERT INTO pets VALUES (2, 'Basil', '2012-08-06', 6, 2); -INSERT INTO pets VALUES (3, 'Rosy', '2011-04-17', 2, 3); -INSERT INTO pets VALUES (4, 'Jewel', '2010-03-07', 2, 3); -INSERT INTO pets VALUES (5, 'Iggy', '2010-11-30', 3, 4); -INSERT INTO pets VALUES (6, 'George', '2010-01-20', 4, 5); -INSERT INTO pets VALUES (7, 'Samantha', '2012-09-04', 1, 6); -INSERT INTO pets VALUES (8, 'Max', '2012-09-04', 1, 6); -INSERT INTO pets VALUES (9, 'Lucky', '2011-08-06', 5, 7); -INSERT INTO pets VALUES (10, 'Mulligan', '2007-02-24', 2, 8); -INSERT INTO pets VALUES (11, 'Freddy', '2010-03-09', 5, 9); -INSERT INTO pets VALUES (12, 'Lucky', '2010-06-24', 2, 10); -INSERT INTO pets VALUES (13, 'Sly', '2012-06-08', 1, 10); diff --git a/spring-petclinic-customers-service/src/main/resources/db/hsqldb/schema.sql b/spring-petclinic-customers-service/src/main/resources/db/hsqldb/schema.sql deleted file mode 100644 index 9aa6666bf9..0000000000 --- a/spring-petclinic-customers-service/src/main/resources/db/hsqldb/schema.sql +++ /dev/null @@ -1,30 +0,0 @@ -DROP TABLE pets IF EXISTS; -DROP TABLE types IF EXISTS; -DROP TABLE owners IF EXISTS; - -CREATE TABLE types ( - id INTEGER IDENTITY PRIMARY KEY, - name VARCHAR(80) -); -CREATE INDEX types_name ON types (name); - -CREATE TABLE owners ( - id INTEGER IDENTITY PRIMARY KEY, - first_name VARCHAR(30), - last_name VARCHAR(30), - address VARCHAR(255), - city VARCHAR(80), - telephone VARCHAR(20) -); -CREATE INDEX owners_last_name ON owners (last_name); - -CREATE TABLE pets ( - id INTEGER IDENTITY PRIMARY KEY, - name VARCHAR(30), - birth_date DATE, - type_id INTEGER NOT NULL, - owner_id INTEGER NOT NULL -); -ALTER TABLE pets ADD CONSTRAINT fk_pets_owners FOREIGN KEY (owner_id) REFERENCES owners (id); -ALTER TABLE pets ADD CONSTRAINT fk_pets_types FOREIGN KEY (type_id) REFERENCES types (id); -CREATE INDEX pets_name ON pets (name); diff --git a/spring-petclinic-customers-service/src/main/resources/db/mysql/data.sql b/spring-petclinic-customers-service/src/main/resources/db/mysql/data.sql deleted file mode 100644 index d6e91b1b34..0000000000 --- a/spring-petclinic-customers-service/src/main/resources/db/mysql/data.sql +++ /dev/null @@ -1,31 +0,0 @@ -INSERT IGNORE INTO types VALUES (1, 'cat'); -INSERT IGNORE INTO types VALUES (2, 'dog'); -INSERT IGNORE INTO types VALUES (3, 'lizard'); -INSERT IGNORE INTO types VALUES (4, 'snake'); -INSERT IGNORE INTO types VALUES (5, 'bird'); -INSERT IGNORE INTO types VALUES (6, 'hamster'); - -INSERT IGNORE INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); -INSERT IGNORE INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); -INSERT IGNORE INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); -INSERT IGNORE INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); -INSERT IGNORE INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); -INSERT IGNORE INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); -INSERT IGNORE INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); -INSERT IGNORE INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); -INSERT IGNORE INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); -INSERT IGNORE INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); - -INSERT IGNORE INTO pets VALUES (1, 'Leo', '2000-09-07', 1, 1); -INSERT IGNORE INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2); -INSERT IGNORE INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3); -INSERT IGNORE INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3); -INSERT IGNORE INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4); -INSERT IGNORE INTO pets VALUES (6, 'George', '2000-01-20', 4, 5); -INSERT IGNORE INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6); -INSERT IGNORE INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6); -INSERT IGNORE INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7); -INSERT IGNORE INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8); -INSERT IGNORE INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9); -INSERT IGNORE INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10); -INSERT IGNORE INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10); diff --git a/spring-petclinic-customers-service/src/main/resources/db/mysql/schema.sql b/spring-petclinic-customers-service/src/main/resources/db/mysql/schema.sql deleted file mode 100644 index 720913829a..0000000000 --- a/spring-petclinic-customers-service/src/main/resources/db/mysql/schema.sql +++ /dev/null @@ -1,31 +0,0 @@ -CREATE DATABASE IF NOT EXISTS petclinic; -GRANT ALL PRIVILEGES ON petclinic.* TO pc@localhost IDENTIFIED BY 'pc'; - -USE petclinic; - -CREATE TABLE IF NOT EXISTS types ( - id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(80), - INDEX(name) -) engine=InnoDB; - -CREATE TABLE IF NOT EXISTS owners ( - id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - first_name VARCHAR(30), - last_name VARCHAR(30), - address VARCHAR(255), - city VARCHAR(80), - telephone VARCHAR(20), - INDEX(last_name) -) engine=InnoDB; - -CREATE TABLE IF NOT EXISTS pets ( - id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(30), - birth_date DATE, - type_id INT(4) UNSIGNED NOT NULL, - owner_id INT(4) UNSIGNED NOT NULL, - INDEX(name), - FOREIGN KEY (owner_id) REFERENCES owners(id), - FOREIGN KEY (type_id) REFERENCES types(id) -) engine=InnoDB; diff --git a/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java b/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java index 0bd2abb383..5303a41981 100644 --- a/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java +++ b/spring-petclinic-customers-service/src/test/java/org/springframework/samples/petclinic/customers/web/PetResourceTest.java @@ -1,34 +1,37 @@ package org.springframework.samples.petclinic.customers.web; -import java.util.Optional; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.samples.petclinic.customers.model.Owner; import org.springframework.samples.petclinic.customers.model.OwnerRepository; import org.springframework.samples.petclinic.customers.model.Pet; import org.springframework.samples.petclinic.customers.model.PetRepository; -import org.springframework.samples.petclinic.customers.model.PetType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; +import reactor.core.publisher.Mono; +import java.util.Optional; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * @author Maciej Szarlinski */ +@RunWith(SpringJUnit4ClassRunner.class) @ExtendWith(SpringExtension.class) -@WebMvcTest(PetResource.class) +@AutoConfigureMockMvc +@SpringBootTest @ActiveProfiles("test") class PetResourceTest { @@ -46,15 +49,14 @@ void shouldGetAPetInJSonFormat() throws Exception { Pet pet = setupPet(); - given(petRepository.findById(2)).willReturn(Optional.of(pet)); + given(petRepository.findById("2")).willReturn(Optional.of(pet)); mvc.perform(get("/owners/2/pets/2").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.id").value(2)) - .andExpect(jsonPath("$.name").value("Basil")) - .andExpect(jsonPath("$.type.id").value(6)); + .andExpect(jsonPath("$.name").value("Basil")); } private Pet setupPet() { @@ -65,11 +67,10 @@ private Pet setupPet() { Pet pet = new Pet(); pet.setName("Basil"); - pet.setId(2); - PetType petType = new PetType(); - petType.setId(6); - pet.setType(petType); + pet.setId("2"); + + pet.setType("dog"); owner.addPet(pet); return pet; diff --git a/spring-petclinic-customers-service/src/test/resources/application-test.yml b/spring-petclinic-customers-service/src/test/resources/application-test.yml index 0abdac4430..07e39e6271 100644 --- a/spring-petclinic-customers-service/src/test/resources/application-test.yml +++ b/spring-petclinic-customers-service/src/test/resources/application-test.yml @@ -1,4 +1,3 @@ -spring.jpa.hibernate.ddl-auto: none spring: datasource: diff --git a/spring-petclinic-discovery-server/pom.xml b/spring-petclinic-discovery-server/pom.xml index c4f69924a1..b4a06a8c5e 100644 --- a/spring-petclinic-discovery-server/pom.xml +++ b/spring-petclinic-discovery-server/pom.xml @@ -1,73 +1,73 @@ - - - 4.0.0 - - org.springframework.samples.petclinic.discovery - spring-petclinic-discovery-server - jar - Spring PetClinic Discovery Server - - - org.springframework.samples - spring-petclinic-microservices - 2.2.1 - - - - 8761 - ${basedir}/../docker - - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - org.springframework.cloud - spring-cloud-starter-config - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-server - - - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - - - org.glassfish.jaxb - jaxb-runtime - - - - - - buildDocker - - - - com.spotify - docker-maven-plugin - ${docker.plugin.version} - - - - - - + + + 4.0.0 + + org.springframework.samples.petclinic.discovery + spring-petclinic-discovery-server + jar + Spring PetClinic Discovery Server + + + org.springframework.samples + spring-petclinic-microservices + 2.3.6 + + + + 8761 + ${basedir}/../docker + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.cloud + spring-cloud-starter-config + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-server + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + org.glassfish.jaxb + jaxb-runtime + + + + + + buildDocker + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + + + + + diff --git a/spring-petclinic-discovery-server/src/main/resources/bootstrap.yml b/spring-petclinic-discovery-server/src/main/resources/bootstrap.yml index 9c07ca22b5..a0a0b0d4a3 100644 --- a/spring-petclinic-discovery-server/src/main/resources/bootstrap.yml +++ b/spring-petclinic-discovery-server/src/main/resources/bootstrap.yml @@ -10,3 +10,12 @@ spring: cloud: config: uri: http://config-server:8888 + +--- +spring: + profiles: local + cloud: + config: + uri: http://localhost:8888 +server: + port: 8761 diff --git a/spring-petclinic-vets-service/pom.xml b/spring-petclinic-vets-service/pom.xml index fb3f6fd936..61edd128f0 100644 --- a/spring-petclinic-vets-service/pom.xml +++ b/spring-petclinic-vets-service/pom.xml @@ -11,7 +11,7 @@ org.springframework.samples spring-petclinic-microservices - 2.2.1 + 2.3.6 @@ -38,10 +38,6 @@ org.springframework.boot spring-boot-starter-cache - - org.springframework.boot - spring-boot-starter-data-jpa - org.springframework.boot spring-boot-starter-test @@ -88,15 +84,33 @@ hsqldb runtime - + + + com.azure + azure-spring-data-cosmos + 3.2.0 + + + com.azure.spring + azure-spring-boot-starter-keyvault-secrets + 3.1.0 + + + org.springframework.data + spring-data-rest-core + io.micrometer micrometer-registry-prometheus + + javax.validation + validation-api + @@ -109,7 +123,16 @@ junit-jupiter-engine test - + + jakarta.persistence + jakarta.persistence-api + + + junit + junit + test + + diff --git a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/PopulateSeedData.java b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/PopulateSeedData.java new file mode 100644 index 0000000000..414796ddee --- /dev/null +++ b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/PopulateSeedData.java @@ -0,0 +1,50 @@ +package org.springframework.samples.petclinic.vets; + +import com.azure.cosmos.implementation.guava25.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.vets.model.Specialty; +import org.springframework.samples.petclinic.vets.model.Vet; +import org.springframework.samples.petclinic.vets.model.VetRepository; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; + +import javax.annotation.PostConstruct; +import java.text.ParseException; +import java.util.HashSet; + + +@Component +public class PopulateSeedData { + + private static final Logger logger = LoggerFactory.getLogger(PopulateSeedData.class); + + @Autowired + private VetRepository repository; + + @PostConstruct + public void populateSeedData() throws ParseException { + final Specialty speciality1 = new Specialty(1, "radiology"); + final Specialty speciality2 = new Specialty(2, "surgery"); + final Specialty speciality3 = new Specialty(3, "dentistry"); + + final Vet vet1 = new Vet(1, "James", "Carter", null); + final Vet vet2 = new Vet(2, "Helen", "Leary", new HashSet() {{ + add(speciality1); + }}); + final Vet vet3 = new Vet(3, "Linda", "Douglas", new HashSet() {{ + add(speciality2); + add(speciality3); + }}); + final Vet vet4 = new Vet(4, "Rafael", "Ortega", new HashSet() {{ + add(speciality2); + }}); + final Vet vet5 = new Vet(5, "Henry", "Stevens", new HashSet() {{ + add(speciality1); + }}); + final Vet vet6 = new Vet(6, "James", "Carter", null); + + this.repository.saveAll(Lists.newArrayList(vet1, vet2, vet3, vet4, vet5, vet6)); + } +} diff --git a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/VetsServiceApplication.java b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/VetsServiceApplication.java index 2f5cb36636..de969f4ebc 100644 --- a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/VetsServiceApplication.java +++ b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/VetsServiceApplication.java @@ -15,10 +15,12 @@ */ package org.springframework.samples.petclinic.vets; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.samples.petclinic.vets.model.VetRepository; import org.springframework.samples.petclinic.vets.system.VetsProperties; /** @@ -29,7 +31,7 @@ @EnableConfigurationProperties(VetsProperties.class) public class VetsServiceApplication { - public static void main(String[] args) { - SpringApplication.run(VetsServiceApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(VetsServiceApplication.class, args); + } } diff --git a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/CosmosProperties.java b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/CosmosProperties.java new file mode 100644 index 0000000000..cfa48186f4 --- /dev/null +++ b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/CosmosProperties.java @@ -0,0 +1,22 @@ +package org.springframework.samples.petclinic.vets.model; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + + +@Getter +@Setter +@ConfigurationProperties(prefix = "azure.cosmosdb") +class CosmosProperties { + + private String uri; + + private String key; + + private String secondaryKey; + + private String database; + + private boolean populateQueryMetrics; +} diff --git a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/Specialty.java b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/Specialty.java index 4a22dd167d..05fa560dac 100644 --- a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/Specialty.java +++ b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/Specialty.java @@ -15,26 +15,22 @@ */ package org.springframework.samples.petclinic.vets.model; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + import javax.persistence.Id; -import javax.persistence.Table; /** * Models a {@link Vet Vet's} specialty (for example, dentistry). * * @author Juergen Hoeller */ -@Entity -@Table(name = "specialties") -public class Specialty { +@AllArgsConstructor +@NoArgsConstructor +public class Specialty { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; - @Column(name = "name") private String name; public Integer getId() { diff --git a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/Vet.java b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/Vet.java index cddb6a9e3b..d731db9e72 100644 --- a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/Vet.java +++ b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/Vet.java @@ -15,27 +15,23 @@ */ package org.springframework.samples.petclinic.vets.model; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import com.azure.spring.data.cosmos.core.mapping.Container; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NoArgsConstructor; +import org.springframework.beans.support.MutableSortDefinition; +import org.springframework.beans.support.PropertyComparator; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; -import javax.persistence.Table; import javax.validation.constraints.NotEmpty; import javax.xml.bind.annotation.XmlElement; - -import org.springframework.beans.support.MutableSortDefinition; -import org.springframework.beans.support.PropertyComparator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Simple JavaBean domain object representing a veterinarian. @@ -46,25 +42,23 @@ * @author Arjen Poutsma * @author Maciej Szarlinski */ -@Entity -@Table(name = "vets") + +@Container(containerName = "vets") +@Builder(builderMethodName = "vet") +@AllArgsConstructor +@NoArgsConstructor public class Vet { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; - @Column(name = "first_name") @NotEmpty private String firstName; - @Column(name = "last_name") @NotEmpty private String lastName; - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"), - inverseJoinColumns = @JoinColumn(name = "specialty_id")) private Set specialties; public Integer getId() { diff --git a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/VetRepository.java b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/VetRepository.java index 67e2571333..2b77e1dc15 100644 --- a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/VetRepository.java +++ b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/VetRepository.java @@ -15,7 +15,8 @@ */ package org.springframework.samples.petclinic.vets.model; -import org.springframework.data.jpa.repository.JpaRepository; +import com.azure.spring.data.cosmos.repository.CosmosRepository; +import org.springframework.stereotype.Repository; /** * Repository class for Vet domain objects All method names are compliant with Spring Data naming @@ -27,5 +28,6 @@ * @author Michael Isvy * @author Maciej Szarlinski */ -public interface VetRepository extends JpaRepository { +@Repository +public interface VetRepository extends CosmosRepository { } diff --git a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/VetsAppConfiguration.java b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/VetsAppConfiguration.java new file mode 100644 index 0000000000..05a8c4c4a5 --- /dev/null +++ b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/model/VetsAppConfiguration.java @@ -0,0 +1,76 @@ +package org.springframework.samples.petclinic.vets.model; + + +import com.azure.core.credential.AzureKeyCredential; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.DirectConnectionConfig; +import com.azure.cosmos.GatewayConnectionConfig; +import com.azure.spring.data.cosmos.config.AbstractCosmosConfiguration; +import com.azure.spring.data.cosmos.config.CosmosConfig; +import com.azure.spring.data.cosmos.core.ResponseDiagnostics; +import com.azure.spring.data.cosmos.core.ResponseDiagnosticsProcessor; +import com.azure.spring.data.cosmos.repository.config.EnableCosmosRepositories; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.lang.Nullable; + + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +@Configuration +@EnableConfigurationProperties(CosmosProperties.class) +@EnableCosmosRepositories +@PropertySource("classpath:application.properties") +public class VetsAppConfiguration extends AbstractCosmosConfiguration { + + private static final Logger logger = LoggerFactory.getLogger(VetsAppConfiguration.class); + + @Autowired + private CosmosProperties properties; + + private AzureKeyCredential azureKeyCredential; + + @Bean + public CosmosClientBuilder cosmosClientBuilder() { + this.azureKeyCredential = new AzureKeyCredential(properties.getKey()); + DirectConnectionConfig directConnectionConfig = new DirectConnectionConfig(); + GatewayConnectionConfig gatewayConnectionConfig = new GatewayConnectionConfig(); + return new CosmosClientBuilder() + .endpoint(properties.getUri()) + .credential(azureKeyCredential) + .directMode(directConnectionConfig, gatewayConnectionConfig); + } + + @Override + public CosmosConfig cosmosConfig() { + return CosmosConfig.builder() + .enableQueryMetrics(properties.isPopulateQueryMetrics()) + .responseDiagnosticsProcessor(new ResponseDiagnosticsProcessorImplementation()) + .build(); + } + + + public void switchToSecondaryKey() { + this.azureKeyCredential.update(properties.getSecondaryKey()); + } + + @Override + protected String getDatabaseName() { + return properties.getDatabase(); + } + + private static class ResponseDiagnosticsProcessorImplementation implements ResponseDiagnosticsProcessor { + + @Override + public void processResponseDiagnostics(@Nullable ResponseDiagnostics responseDiagnostics) { + logger.info("Response Diagnostics {}", responseDiagnostics); + } + } +} diff --git a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/web/VetResource.java b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/web/VetResource.java index d61d5dda17..d6d19611bd 100644 --- a/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/web/VetResource.java +++ b/spring-petclinic-vets-service/src/main/java/org/springframework/samples/petclinic/vets/web/VetResource.java @@ -15,16 +15,19 @@ */ package org.springframework.samples.petclinic.vets.web; +import io.micrometer.core.annotation.Timed; import lombok.RequiredArgsConstructor; - -import java.util.List; - +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.samples.petclinic.vets.model.Vet; import org.springframework.samples.petclinic.vets.model.VetRepository; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.ArrayList; +import java.util.List; + /** * @author Juergen Hoeller * @author Mark Fisher @@ -35,12 +38,18 @@ @RequestMapping("/vets") @RestController @RequiredArgsConstructor -class VetResource { +@Timed("petclinic.vet") +@Slf4j +class +VetResource { + private final VetRepository vetRepository; @GetMapping public List showResourcesVetList() { - return vetRepository.findAll(); + List list = new ArrayList<>(); + vetRepository.findAll().forEach(list::add); + return list; } } diff --git a/spring-petclinic-vets-service/src/main/resources/application.properties b/spring-petclinic-vets-service/src/main/resources/application.properties index 68f17e806f..2b931bd0c9 100644 --- a/spring-petclinic-vets-service/src/main/resources/application.properties +++ b/spring-petclinic-vets-service/src/main/resources/application.properties @@ -1,2 +1,30 @@ -spring.profiles.active=production spring.cache.cache-names=vets + +azure.keyvault.enabled=true +azure.keyvault.uri=${AZURE_KEYVAULT_URI} +azure.keyvault.client-id=${AZURE_KEYVAULT_CLIENTID} +azure.keyvault.client-key=${AZURE_KEYVAULT_CLIENTKEY} +azure.keyvault.tenant-id=${AZURE_KEYVAULT_TENANTID} + +azure.keyvault.token-acquire-timeout-seconds=60 +azure.keyvault.refresh-interval=1800000 +azure.keyvault.secret-keys=cosmosdburi,cosmosdbkey,cosmosdbsecondarykey, redisuri, redispassword + +# Azure cosmos db properties +azure.cosmosdb.uri=${cosmosdburi} +azure.cosmosdb.key=${cosmosdbkey} +azure.cosmosdb.secondaryKey=${cosmosdbsecondarykey} +azure.cosmosdb.database=end2endsample +# Populate query metrics +cosmos.queryMetricsEnabled=true + + +# Properties for Setting Azure active directory +## Specifies your Active Directory ID: +#azure.activedirectory.tenant-id=22222222-2222-2222-2222-222222222222 +## Specifies your App Registration's Application ID: +#azure.activedirectory.client-id=11111111-1111-1111-1111-1111111111111111 +## Specifies your App Registration's secret key: +#azure.activedirectory.client-secret=AbCdEfGhIjKlMnOpQrStUvWxYz== +## Specifies the list of Active Directory groups to use for authorization: +#azure.activedirectory.user-group.allowed-groups=group1 diff --git a/spring-petclinic-vets-service/src/main/resources/bootstrap.yml b/spring-petclinic-vets-service/src/main/resources/bootstrap.yml index 59c6dd2588..477d86f297 100644 --- a/spring-petclinic-vets-service/src/main/resources/bootstrap.yml +++ b/spring-petclinic-vets-service/src/main/resources/bootstrap.yml @@ -1,3 +1,7 @@ +eureka: + client: + register-with-eureka: true + fetch-registry: true spring: cloud: config: @@ -10,3 +14,17 @@ spring: cloud: config: uri: http://config-server:8888 +--- +spring: + profiles: local + cloud: + config: + uri: http://localhost:8888 + zipkin: + baseUrl: http://localhost:9411 +eureka: + client: + serviceUrl: + defaultZone: http://localhost:8761/eureka/ +server: + port: 8083 diff --git a/spring-petclinic-vets-service/src/main/resources/db/hsqldb/data.sql b/spring-petclinic-vets-service/src/main/resources/db/hsqldb/data.sql deleted file mode 100644 index e6658a527e..0000000000 --- a/spring-petclinic-vets-service/src/main/resources/db/hsqldb/data.sql +++ /dev/null @@ -1,16 +0,0 @@ -INSERT INTO vets VALUES (1, 'James', 'Carter'); -INSERT INTO vets VALUES (2, 'Helen', 'Leary'); -INSERT INTO vets VALUES (3, 'Linda', 'Douglas'); -INSERT INTO vets VALUES (4, 'Rafael', 'Ortega'); -INSERT INTO vets VALUES (5, 'Henry', 'Stevens'); -INSERT INTO vets VALUES (6, 'Sharon', 'Jenkins'); - -INSERT INTO specialties VALUES (1, 'radiology'); -INSERT INTO specialties VALUES (2, 'surgery'); -INSERT INTO specialties VALUES (3, 'dentistry'); - -INSERT INTO vet_specialties VALUES (2, 1); -INSERT INTO vet_specialties VALUES (3, 2); -INSERT INTO vet_specialties VALUES (3, 3); -INSERT INTO vet_specialties VALUES (4, 2); -INSERT INTO vet_specialties VALUES (5, 1); diff --git a/spring-petclinic-vets-service/src/main/resources/db/hsqldb/schema.sql b/spring-petclinic-vets-service/src/main/resources/db/hsqldb/schema.sql deleted file mode 100644 index eb916e1b7b..0000000000 --- a/spring-petclinic-vets-service/src/main/resources/db/hsqldb/schema.sql +++ /dev/null @@ -1,23 +0,0 @@ -DROP TABLE vet_specialties IF EXISTS; -DROP TABLE vets IF EXISTS; -DROP TABLE specialties IF EXISTS; - -CREATE TABLE vets ( - id INTEGER IDENTITY PRIMARY KEY, - first_name VARCHAR(30), - last_name VARCHAR(30) -); -CREATE INDEX vets_last_name ON vets (last_name); - -CREATE TABLE specialties ( - id INTEGER IDENTITY PRIMARY KEY, - name VARCHAR(80) -); -CREATE INDEX specialties_name ON specialties (name); - -CREATE TABLE vet_specialties ( - vet_id INTEGER NOT NULL, - specialty_id INTEGER NOT NULL -); -ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_vets FOREIGN KEY (vet_id) REFERENCES vets (id); -ALTER TABLE vet_specialties ADD CONSTRAINT fk_vet_specialties_specialties FOREIGN KEY (specialty_id) REFERENCES specialties (id); diff --git a/spring-petclinic-vets-service/src/main/resources/db/mysql/data.sql b/spring-petclinic-vets-service/src/main/resources/db/mysql/data.sql deleted file mode 100644 index 9159eca22e..0000000000 --- a/spring-petclinic-vets-service/src/main/resources/db/mysql/data.sql +++ /dev/null @@ -1,16 +0,0 @@ -INSERT IGNORE INTO vets VALUES (1, 'James', 'Carter'); -INSERT IGNORE INTO vets VALUES (2, 'Helen', 'Leary'); -INSERT IGNORE INTO vets VALUES (3, 'Linda', 'Douglas'); -INSERT IGNORE INTO vets VALUES (4, 'Rafael', 'Ortega'); -INSERT IGNORE INTO vets VALUES (5, 'Henry', 'Stevens'); -INSERT IGNORE INTO vets VALUES (6, 'Sharon', 'Jenkins'); - -INSERT IGNORE INTO specialties VALUES (1, 'radiology'); -INSERT IGNORE INTO specialties VALUES (2, 'surgery'); -INSERT IGNORE INTO specialties VALUES (3, 'dentistry'); - -INSERT IGNORE INTO vet_specialties VALUES (2, 1); -INSERT IGNORE INTO vet_specialties VALUES (3, 2); -INSERT IGNORE INTO vet_specialties VALUES (3, 3); -INSERT IGNORE INTO vet_specialties VALUES (4, 2); -INSERT IGNORE INTO vet_specialties VALUES (5, 1); diff --git a/spring-petclinic-vets-service/src/main/resources/db/mysql/schema.sql b/spring-petclinic-vets-service/src/main/resources/db/mysql/schema.sql deleted file mode 100644 index 4f9af3d1bd..0000000000 --- a/spring-petclinic-vets-service/src/main/resources/db/mysql/schema.sql +++ /dev/null @@ -1,25 +0,0 @@ -CREATE DATABASE IF NOT EXISTS petclinic; -GRANT ALL PRIVILEGES ON petclinic.* TO pc@localhost IDENTIFIED BY 'pc'; - -USE petclinic; - -CREATE TABLE IF NOT EXISTS vets ( - id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - first_name VARCHAR(30), - last_name VARCHAR(30), - INDEX(last_name) -) engine=InnoDB; - -CREATE TABLE IF NOT EXISTS specialties ( - id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(80), - INDEX(name) -) engine=InnoDB; - -CREATE TABLE IF NOT EXISTS vet_specialties ( - vet_id INT(4) UNSIGNED NOT NULL, - specialty_id INT(4) UNSIGNED NOT NULL, - FOREIGN KEY (vet_id) REFERENCES vets(id), - FOREIGN KEY (specialty_id) REFERENCES specialties(id), - UNIQUE (vet_id,specialty_id) -) engine=InnoDB; diff --git a/spring-petclinic-vets-service/src/test/java/org/springframework/samples/petclinic/vets/web/VetResourceTest.java b/spring-petclinic-vets-service/src/test/java/org/springframework/samples/petclinic/vets/web/VetResourceTest.java index 83e6a6fb61..81d2da8c3e 100644 --- a/spring-petclinic-vets-service/src/test/java/org/springframework/samples/petclinic/vets/web/VetResourceTest.java +++ b/spring-petclinic-vets-service/src/test/java/org/springframework/samples/petclinic/vets/web/VetResourceTest.java @@ -17,18 +17,28 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.samples.petclinic.vets.model.Vet; import org.springframework.samples.petclinic.vets.model.VetRepository; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import reactor.core.publisher.Flux; + +import java.util.Arrays; +import java.util.List; import static java.util.Arrays.asList; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -36,8 +46,10 @@ /** * @author Maciej Szarlinski */ +@RunWith(SpringJUnit4ClassRunner.class) @ExtendWith(SpringExtension.class) -@WebMvcTest(VetResource.class) +@AutoConfigureMockMvc +@SpringBootTest @ActiveProfiles("test") class VetResourceTest { @@ -49,11 +61,10 @@ class VetResourceTest { @Test void shouldGetAListOfVets() throws Exception { + List vet = Arrays.asList(Vet.vet().id(1).build(), Vet.vet().id(2).build() + ); - Vet vet = new Vet(); - vet.setId(1); - - given(vetRepository.findAll()).willReturn(asList(vet)); + given(vetRepository.findAll()).willReturn(vet); mvc.perform(get("/vets").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) diff --git a/spring-petclinic-vets-service/src/test/resources/application-test.yml b/spring-petclinic-vets-service/src/test/resources/application-test.yml index 2d18cfdeaf..a00a1d789c 100644 --- a/spring-petclinic-vets-service/src/test/resources/application-test.yml +++ b/spring-petclinic-vets-service/src/test/resources/application-test.yml @@ -1,4 +1,3 @@ -spring.jpa.hibernate.ddl-auto: none spring: datasource: diff --git a/spring-petclinic-visits-service/pom.xml b/spring-petclinic-visits-service/pom.xml index 2316f8cbc8..5b2b8328b1 100644 --- a/spring-petclinic-visits-service/pom.xml +++ b/spring-petclinic-visits-service/pom.xml @@ -11,7 +11,7 @@ org.springframework.samples spring-petclinic-microservices - 2.2.1 + 2.3.6 @@ -25,14 +25,19 @@ org.springframework.boot spring-boot-starter-actuator - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-web - + + com.azure + azure-spring-data-cosmos + 3.2.0 + + + org.springframework.data + spring-data-rest-webmvc + + + org.springframework.boot + spring-boot-starter-web + org.springframework.boot spring-boot-starter-test @@ -71,15 +76,17 @@ org.jolokia jolokia-core - - mysql - mysql-connector-java - runtime - + io.micrometer micrometer-registry-prometheus + + javax.validation + validation-api + + + @@ -92,20 +99,39 @@ junit-jupiter-engine test - + + jakarta.persistence + jakarta.persistence-api + + + junit + junit + test + + + com.azure + azure-spring-data-cosmos + 3.2.0 + + + com.azure.spring + azure-spring-boot-starter-keyvault-secrets + 3.1.0 + + buildDocker - - - - com.spotify - docker-maven-plugin - ${docker.plugin.version} - - - + + + + com.spotify + docker-maven-plugin + ${docker.plugin.version} + + + diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/PopulateSeedData.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/PopulateSeedData.java new file mode 100644 index 0000000000..faea99b3c9 --- /dev/null +++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/PopulateSeedData.java @@ -0,0 +1,40 @@ +package org.springframework.samples.petclinic.visits; + +import com.azure.cosmos.implementation.guava25.collect.Lists; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.samples.petclinic.visits.model.Visit; +import org.springframework.samples.petclinic.visits.model.VisitRepository; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; + +import javax.annotation.PostConstruct; +import java.text.ParseException; +import java.text.SimpleDateFormat; + + +@Component +public class PopulateSeedData { + + private static final Logger logger = LoggerFactory.getLogger(PopulateSeedData.class); + + @Autowired + private VisitRepository visitRepository; + + // @PostConstruct +// @Order(2) // not evaluated by Spring +// void postConstruct(){ +// logger.info("@PostConstruct"); +// } + @PostConstruct + // @Order(2) // not evaluated by Spring + public void populateSeedData() throws ParseException { + final Visit visit1 = new Visit("1", "7", new SimpleDateFormat("yyyy-dd-MM").parse("2010-03-04"), "rabies shot"); + final Visit visit2 = new Visit("2", "8", new SimpleDateFormat("yyyy-dd-MM").parse("2011-03-04"), "rabies shot"); + final Visit visit3 = new Visit("3", "8", new SimpleDateFormat("yyyy-dd-MM").parse("2009-06-04"), "neutered"); + final Visit visit4 = new Visit("4", "7", new SimpleDateFormat("yyyy-dd-MM").parse("2008-09-04"), "spayed"); + + this.visitRepository.saveAll(Lists.newArrayList(visit1, visit2, visit3, visit4)); + } +} diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/VisitsServiceApplication.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/VisitsServiceApplication.java index 51a0ecd8c6..f5f8d3f826 100644 --- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/VisitsServiceApplication.java +++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/VisitsServiceApplication.java @@ -15,9 +15,12 @@ */ package org.springframework.samples.petclinic.visits; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.samples.petclinic.visits.model.VisitRepository; /** * @author Maciej Szarlinski diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/CosmosProperties.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/CosmosProperties.java new file mode 100644 index 0000000000..ad70d03fd7 --- /dev/null +++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/CosmosProperties.java @@ -0,0 +1,22 @@ +package org.springframework.samples.petclinic.visits.model; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + + +@Getter +@Setter +@ConfigurationProperties(prefix = "azure.cosmosdb") +class CosmosProperties { + + private String uri; + + private String key; + + private String secondaryKey; + + private String database; + + private boolean populateQueryMetrics; +} diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java index f799b43a03..9c91e411cd 100644 --- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java +++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/Visit.java @@ -15,20 +15,18 @@ */ package org.springframework.samples.petclinic.visits.model; +import com.azure.spring.data.cosmos.core.mapping.Container; +import com.azure.spring.data.cosmos.core.mapping.GeneratedValue; import com.fasterxml.jackson.annotation.JsonFormat; -import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; + +import javax.persistence.GenerationType; +import javax.persistence.criteria.CriteriaBuilder; +import javax.validation.constraints.Size; +import java.util.Date; /** * Simple JavaBean domain object representing a visit. @@ -36,47 +34,48 @@ * @author Ken Krebs * @author Maciej Szarlinski */ -@Entity -@Table(name = "visits") + +@Container(containerName = "visits") @Builder(builderMethodName = "visit") -@NoArgsConstructor @AllArgsConstructor +@NoArgsConstructor public class Visit { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; + @GeneratedValue + private String id; + + private String petId; @Builder.Default - @Column(name = "visit_date") - @Temporal(TemporalType.TIMESTAMP) @JsonFormat(pattern = "yyyy-MM-dd") - private Date date = new Date(); + private Date visit_date = new Date(); @Size(max = 8192) - @Column(name = "description") private String description; - @Column(name = "pet_id") - private int petId; - public Integer getId() { + public void setId(String id) { + this.id = id; + } + + public String getId() { return id; } public Date getDate() { - return date; + return visit_date; } public String getDescription() { return description; } - public int getPetId() { + public String getPetId() { return petId; } - public void setPetId(final int petId) { + public void setPetId(final String petId) { this.petId = petId; } diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java index 063b3d2709..5037e10cb5 100644 --- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java +++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitRepository.java @@ -15,10 +15,12 @@ */ package org.springframework.samples.petclinic.visits.model; +import com.azure.spring.data.cosmos.repository.CosmosRepository; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; + import java.util.Collection; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; /** * Repository class for Visit domain objects All method names are compliant with Spring Data naming conventions so this interface can easily be extended for Spring @@ -30,9 +32,12 @@ * @author Michael Isvy * @author Maciej Szarlinski */ -public interface VisitRepository extends JpaRepository { +@Repository +public interface VisitRepository extends CosmosRepository { - List findByPetId(int petId); +/* + Flux findByPetId(int petId); - List findByPetIdIn(Collection petIds); + Flux findByPetIdIn(Collection petIds); +*/ } diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitsAppConfiguration.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitsAppConfiguration.java new file mode 100644 index 0000000000..ec971f60bd --- /dev/null +++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/model/VisitsAppConfiguration.java @@ -0,0 +1,79 @@ +package org.springframework.samples.petclinic.visits.model; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +/** + * WARNING: MODIFYING THIS FILE WILL REQUIRE CORRESPONDING UPDATES TO README.md FILE. LINE NUMBERS + * ARE USED TO EXTRACT APPROPRIATE CODE SEGMENTS FROM THIS FILE. ADD NEW CODE AT THE BOTTOM TO AVOID CHANGING + * LINE NUMBERS OF EXISTING CODE SAMPLES. + */ + +import com.azure.core.credential.AzureKeyCredential; +import com.azure.cosmos.CosmosClientBuilder; +import com.azure.cosmos.DirectConnectionConfig; +import com.azure.cosmos.GatewayConnectionConfig; +import com.azure.spring.data.cosmos.config.AbstractCosmosConfiguration; +import com.azure.spring.data.cosmos.config.CosmosConfig; +import com.azure.spring.data.cosmos.core.ResponseDiagnostics; +import com.azure.spring.data.cosmos.core.ResponseDiagnosticsProcessor; +import com.azure.spring.data.cosmos.repository.config.EnableCosmosRepositories; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.lang.Nullable; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +@Configuration +@EnableConfigurationProperties(CosmosProperties.class) +@EnableCosmosRepositories +@PropertySource("classpath:application.properties") +public class VisitsAppConfiguration extends AbstractCosmosConfiguration { + + private static final Logger logger = LoggerFactory.getLogger(VisitsAppConfiguration.class); + + @Autowired + private CosmosProperties properties; + + private AzureKeyCredential azureKeyCredential; + + @Bean + public CosmosClientBuilder cosmosClientBuilder() { + this.azureKeyCredential = new AzureKeyCredential(properties.getKey()); + DirectConnectionConfig directConnectionConfig = new DirectConnectionConfig(); + GatewayConnectionConfig gatewayConnectionConfig = new GatewayConnectionConfig(); + return new CosmosClientBuilder() + .endpoint(properties.getUri()) + .credential(azureKeyCredential) + .directMode(directConnectionConfig, gatewayConnectionConfig); + } + + @Override + public CosmosConfig cosmosConfig() { + return CosmosConfig.builder() + .enableQueryMetrics(properties.isPopulateQueryMetrics()) + .responseDiagnosticsProcessor(new ResponseDiagnosticsProcessorImplementation()) + .build(); + } + + + public void switchToSecondaryKey() { + this.azureKeyCredential.update(properties.getSecondaryKey()); + } + + @Override + protected String getDatabaseName() { + return properties.getDatabase(); + } + + private static class ResponseDiagnosticsProcessorImplementation implements ResponseDiagnosticsProcessor { + + @Override + public void processResponseDiagnostics(@Nullable ResponseDiagnostics responseDiagnostics) { + logger.info("Response Diagnostics {}", responseDiagnostics); + } + } +} diff --git a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java index 3bcf7f76d6..00fb1c98bf 100644 --- a/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java +++ b/spring-petclinic-visits-service/src/main/java/org/springframework/samples/petclinic/visits/web/VisitResource.java @@ -15,9 +15,7 @@ */ package org.springframework.samples.petclinic.visits.web; -import java.util.List; -import javax.validation.Valid; - +import com.azure.cosmos.implementation.guava25.collect.Lists; import io.micrometer.core.annotation.Timed; import lombok.RequiredArgsConstructor; import lombok.Value; @@ -25,13 +23,12 @@ import org.springframework.http.HttpStatus; import org.springframework.samples.petclinic.visits.model.Visit; import org.springframework.samples.petclinic.visits.model.VisitRepository; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; + +import javax.validation.Valid; +import java.util.List; +import java.util.Optional; /** * @author Juergen Hoeller @@ -52,7 +49,7 @@ class VisitResource { @ResponseStatus(HttpStatus.CREATED) Visit create( @Valid @RequestBody Visit visit, - @PathVariable("petId") int petId) { + @PathVariable("petId") String petId) { visit.setPetId(petId); log.info("Saving visit {}", visit); @@ -60,18 +57,22 @@ Visit create( } @GetMapping("owners/*/pets/{petId}/visits") - List visits(@PathVariable("petId") int petId) { - return visitRepository.findByPetId(petId); + Optional visits(@PathVariable("petId") int petId) { + return visitRepository.findById(petId); } @GetMapping("pets/visits") Visits visitsMultiGet(@RequestParam("petId") List petIds) { - final List byPetIdIn = visitRepository.findByPetIdIn(petIds); - return new Visits(byPetIdIn); + Iterable visitIterable = visitRepository.findAllById(petIds); + return new Visits(Lists.newArrayList(visitIterable)); } @Value static class Visits { private final List items; + + Visits(List items) { + this.items = items; + } } } diff --git a/spring-petclinic-visits-service/src/main/resources/application.properties b/spring-petclinic-visits-service/src/main/resources/application.properties index e69de29bb2..00228fd42f 100644 --- a/spring-petclinic-visits-service/src/main/resources/application.properties +++ b/spring-petclinic-visits-service/src/main/resources/application.properties @@ -0,0 +1,18 @@ +azure.keyvault.enabled=true +azure.keyvault.uri=${AZURE_KEYVAULT_URI} +azure.keyvault.client-id=${AZURE_KEYVAULT_CLIENTID} +azure.keyvault.client-key=${AZURE_KEYVAULT_CLIENTKEY} +azure.keyvault.tenant-id=${AZURE_KEYVAULT_TENANTID} +azure.keyvault.token-acquire-timeout-seconds=60 +azure.keyvault.refresh-interval=1800000 +azure.keyvault.secret-keys=cosmosdburi,cosmosdbkey,cosmosdbsecondarykey, redisuri, redispassword + +# Azure cosmos db properties +azure.cosmosdb.uri=${cosmosdburi} +azure.cosmosdb.key=${cosmosdbkey} +azure.cosmosdb.secondaryKey=${cosmosdbsecondarykey} +azure.cosmosdb.database=end2endsample +# Populate query metrics +cosmos.queryMetricsEnabled=true + + diff --git a/spring-petclinic-visits-service/src/main/resources/bootstrap.yml b/spring-petclinic-visits-service/src/main/resources/bootstrap.yml index 1ddd133b1e..459638352a 100644 --- a/spring-petclinic-visits-service/src/main/resources/bootstrap.yml +++ b/spring-petclinic-visits-service/src/main/resources/bootstrap.yml @@ -1,3 +1,7 @@ +eureka: + client: + register-with-eureka: true + fetch-registry: true spring: cloud: config: @@ -10,3 +14,16 @@ spring: cloud: config: uri: http://config-server:8888 + +--- +spring: + profiles: local + cloud: + config: + uri: http://localhost:8888 +eureka: + client: + serviceUrl: + defaultZone: http://localhost:8761/eureka/ +server: + port: 8082 diff --git a/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java b/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java index 25d33f24ba..645add0e70 100644 --- a/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java +++ b/spring-petclinic-visits-service/src/test/java/org/springframework/samples/petclinic/visits/web/VisitResourceTest.java @@ -3,25 +3,33 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.autoconfigure.OverrideAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.samples.petclinic.visits.model.Visit; import org.springframework.samples.petclinic.visits.model.VisitRepository; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; +import reactor.core.publisher.Flux; +import java.util.Arrays; +import java.util.List; import static java.util.Arrays.asList; -import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; import static org.springframework.samples.petclinic.visits.model.Visit.visit; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(SpringExtension.class) -@WebMvcTest(VisitResource.class) +@AutoConfigureMockMvc +@SpringBootTest +@OverrideAutoConfiguration(enabled = true) @ActiveProfiles("test") -class VisitResourceTest { +public class VisitResourceTest { @Autowired MockMvc mvc; @@ -31,31 +39,17 @@ class VisitResourceTest { @Test void shouldFetchVisits() throws Exception { - given(visitRepository.findByPetIdIn(asList(111, 222))) - .willReturn( - asList( - visit() - .id(1) - .petId(111) - .build(), - visit() - .id(2) - .petId(222) - .build(), - visit() - .id(3) - .petId(222) - .build() - ) - ); + + List list = Arrays.asList(visit().id("1").petId("111").build(), + visit().id("2").petId("222").build() + ); + when(visitRepository.findAllById(asList(111, 222))).thenReturn(list); mvc.perform(get("/pets/visits?petId=111,222")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.items[0].id").value(1)) - .andExpect(jsonPath("$.items[1].id").value(2)) - .andExpect(jsonPath("$.items[2].id").value(3)) - .andExpect(jsonPath("$.items[0].petId").value(111)) - .andExpect(jsonPath("$.items[1].petId").value(222)) - .andExpect(jsonPath("$.items[2].petId").value(222)); + .andExpect(jsonPath("$.items[0].id").value("1")) + .andExpect(jsonPath("$.items[1].id").value("2")) + .andExpect(jsonPath("$.items[0].petId").value("111")) + .andExpect(jsonPath("$.items[1].petId").value("222")); } } diff --git a/spring-petclinic-visits-service/src/test/resources/application-test.yml b/spring-petclinic-visits-service/src/test/resources/application-test.yml index 0abdac4430..07e39e6271 100644 --- a/spring-petclinic-visits-service/src/test/resources/application-test.yml +++ b/spring-petclinic-visits-service/src/test/resources/application-test.yml @@ -1,4 +1,3 @@ -spring.jpa.hibernate.ddl-auto: none spring: datasource: