├── retail-store-webapp ├── src │ ├── main │ │ ├── resources │ │ │ ├── static │ │ │ │ ├── css │ │ │ │ │ └── styles.css │ │ │ │ ├── images │ │ │ │ │ └── books.png │ │ │ │ └── js │ │ │ │ │ ├── orders.js │ │ │ │ │ └── orderDetails.js │ │ │ └── templates │ │ │ │ ├── fragments │ │ │ │ ├── inventoryPagination.html │ │ │ │ └── productsPagination.html │ │ │ │ ├── login.html │ │ │ │ └── orders.html │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── retailstore │ │ │ └── webapp │ │ │ ├── clients │ │ │ ├── order │ │ │ │ ├── OrderConfirmationDTO.java │ │ │ │ ├── OrderItemExternal.java │ │ │ │ ├── OrderItemRequest.java │ │ │ │ ├── OrderItemResponse.java │ │ │ │ ├── Address.java │ │ │ │ ├── OrderRequestExternal.java │ │ │ │ ├── CreateOrderRequest.java │ │ │ │ └── OrderServiceClient.java │ │ │ ├── customer │ │ │ │ ├── CustomerResponse.java │ │ │ │ ├── CustomerRequest.java │ │ │ │ └── CustomerServiceClient.java │ │ │ ├── inventory │ │ │ │ ├── InventoryResponse.java │ │ │ │ ├── InventoryUpdateRequest.java │ │ │ │ └── InventoryServiceClient.java │ │ │ ├── PagedResult.java │ │ │ └── catalog │ │ │ │ ├── ProductResponse.java │ │ │ │ ├── ProductRequest.java │ │ │ │ └── CatalogServiceClient.java │ │ │ ├── config │ │ │ ├── ApplicationProperties.java │ │ │ ├── SecurityConstants.java │ │ │ └── WebConfig.java │ │ │ ├── exception │ │ │ ├── InvalidRequestException.java │ │ │ └── ResourceNotFoundException.java │ │ │ ├── RetailStoreWebappApplication.java │ │ │ └── web │ │ │ ├── controller │ │ │ └── LoginController.java │ │ │ └── model │ │ │ └── request │ │ │ └── RegistrationRequest.java │ └── test │ │ ├── resources │ │ └── application-test.yml │ │ └── java │ │ └── com │ │ └── example │ │ └── retailstore │ │ └── webapp │ │ ├── ApplicationIntegrationTest.java │ │ ├── clients │ │ └── order │ │ │ └── CreateOrderRequestTest.java │ │ └── common │ │ └── AbstractIntegrationTest.java ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── docker │ └── docker-compose.yml ├── .gitignore └── sonar-project.properties ├── api-gateway ├── src │ ├── test │ │ ├── resources │ │ │ ├── junit-platform.properties │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── api │ │ │ │ └── gateway │ │ │ │ ├── filter │ │ │ │ ├── CorrelationIdFilterIntegrationTest │ │ │ │ │ └── correlation-test.json │ │ │ │ └── LoggingFilterIntegrationTest │ │ │ │ │ └── logging-test.json │ │ │ │ └── config │ │ │ │ ├── ApiGatewayConfigurationIntegrationTest │ │ │ │ ├── get-mapping.json │ │ │ │ └── test-routing.json │ │ │ │ ├── RateLimiterConfigurationIntegrationTest │ │ │ │ └── mocks-config.json │ │ │ │ └── CircuitBreakerConfigurationIntegrationTest │ │ │ │ └── circuit-breaker.json │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── api │ │ │ └── gateway │ │ │ ├── TestAPIGatewayApplication.java │ │ │ ├── APIGatewayApplicationIntegrationTest.java │ │ │ └── config │ │ │ └── ContainerConfig.java │ └── main │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── api │ │ │ └── gateway │ │ │ ├── model │ │ │ ├── ServiceResult.java │ │ │ ├── UsernameDTO.java │ │ │ ├── ServiceType.java │ │ │ └── GenerationResponse.java │ │ │ ├── APIGatewayApplication.java │ │ │ ├── bootstrap │ │ │ └── DataInitializer.java │ │ │ ├── config │ │ │ ├── WebClientConfiguration.java │ │ │ └── WebFluxConfig.java │ │ │ └── web │ │ │ └── controller │ │ │ ├── HomeController.java │ │ │ └── GatewayInventoryFallback.java │ │ └── resources │ │ └── logback-spring.xml ├── license-header ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── build-config │ └── checkstyle │ │ └── suppressions.xml └── sonar-project.properties ├── images ├── swagger.jpg ├── zipkin.jpg ├── gatewayArchitecture.JPG └── microservicesArchitecture.png ├── catalog-service ├── license-header ├── src │ ├── main │ │ ├── resources │ │ │ ├── db │ │ │ │ └── changelog │ │ │ │ │ └── db.changelog-master.yaml │ │ │ └── logback-spring.xml │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── catalogservice │ │ │ ├── model │ │ │ ├── response │ │ │ │ ├── InventoryResponse.java │ │ │ │ ├── PagedResult.java │ │ │ │ └── ProductResponse.java │ │ │ ├── payload │ │ │ │ └── ProductDto.java │ │ │ └── request │ │ │ │ └── ProductRequest.java │ │ │ ├── config │ │ │ ├── SwaggerConfig.java │ │ │ ├── logging │ │ │ │ ├── Loggable.java │ │ │ │ └── LogWriter.java │ │ │ ├── Initializer.java │ │ │ └── WebFluxConfig.java │ │ │ ├── utils │ │ │ └── AppConstants.java │ │ │ ├── CatalogServiceApplication.java │ │ │ ├── exception │ │ │ ├── CustomResponseStatusException.java │ │ │ └── ProductAlreadyExistsException.java │ │ │ └── mapper │ │ │ └── ProductMapper.java │ └── test │ │ ├── resources │ │ └── logback-test.xml │ │ └── java │ │ └── com │ │ └── example │ │ └── catalogservice │ │ ├── TestCatalogServiceApplication.java │ │ ├── common │ │ ├── SQLContainerConfig.java │ │ └── AbstractCircuitBreakerTest.java │ │ └── config │ │ └── TestKafkaListenerConfig.java ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── build-config │ └── checkstyle │ │ └── suppressions.xml ├── .yo-rc.json ├── Jenkinsfile ├── docker │ └── docker-compose-app.yml ├── .github │ └── workflows │ │ ├── maven-main.yml │ │ └── maven-dev.yml ├── .gitignore ├── sonar-project.properties └── README.md ├── order-service ├── license-header ├── src │ ├── main │ │ ├── resources │ │ │ ├── db │ │ │ │ └── changelog │ │ │ │ │ └── db.changelog-master.yaml │ │ │ ├── application-h2.yml │ │ │ └── logback-spring.xml │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ ├── orderservice │ │ │ ├── entities │ │ │ │ └── OrderStatus.java │ │ │ ├── exception │ │ │ │ ├── OrderNotFoundException.java │ │ │ │ └── ProductNotFoundException.java │ │ │ ├── repositories │ │ │ │ └── OrderItemRepository.java │ │ │ ├── model │ │ │ │ ├── response │ │ │ │ │ ├── OrderItemResponse.java │ │ │ │ │ ├── OrderResponse.java │ │ │ │ │ └── PagedResult.java │ │ │ │ ├── Address.java │ │ │ │ └── request │ │ │ │ │ ├── OrderRequest.java │ │ │ │ │ └── OrderItemRequest.java │ │ │ ├── config │ │ │ │ ├── ObservedAspectConfiguration.java │ │ │ │ ├── AuditConfiguration.java │ │ │ │ ├── logging │ │ │ │ │ ├── Loggable.java │ │ │ │ │ └── LogWriter.java │ │ │ │ ├── ApplicationProperties.java │ │ │ │ ├── SwaggerConfig.java │ │ │ │ ├── Initializer.java │ │ │ │ ├── HttpClientConfig.java │ │ │ │ └── WebMvcConfig.java │ │ │ ├── OrderServiceApplication.java │ │ │ ├── services │ │ │ │ └── CatalogServiceProxy.java │ │ │ └── utils │ │ │ │ └── AppConstants.java │ │ │ └── common │ │ │ └── dtos │ │ │ ├── OrderItemDto.java │ │ │ └── OrderDto.java │ └── test │ │ ├── resources │ │ └── logback-test.xml │ │ └── java │ │ └── com │ │ └── example │ │ └── orderservice │ │ ├── TestOrderServiceApplication.java │ │ └── common │ │ ├── PostGreSQLContainer.java │ │ └── ContainersConfig.java ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── build-config │ └── checkstyle │ │ └── suppressions.xml ├── .yo-rc.json ├── Jenkinsfile ├── docker │ └── docker-compose-app.yml ├── .github │ └── workflows │ │ ├── maven-main.yml │ │ └── maven-dev.yml ├── .gitignore └── sonar-project.properties ├── inventory-service ├── license-header ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── src │ ├── main │ │ ├── resources │ │ │ ├── db │ │ │ │ └── changelog │ │ │ │ │ └── db.changelog-master.yaml │ │ │ ├── application-local.properties │ │ │ ├── application.yml │ │ │ └── logback-spring.xml │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ ├── inventoryservice │ │ │ ├── model │ │ │ │ ├── payload │ │ │ │ │ └── ProductDto.java │ │ │ │ ├── request │ │ │ │ │ └── InventoryRequest.java │ │ │ │ └── response │ │ │ │ │ └── PagedResult.java │ │ │ ├── repositories │ │ │ │ ├── InventoryRepository.java │ │ │ │ └── InventoryJOOQRepository.java │ │ │ ├── config │ │ │ │ ├── SwaggerConfig.java │ │ │ │ ├── RestTemplateConfig.java │ │ │ │ ├── logging │ │ │ │ │ ├── Loggable.java │ │ │ │ │ └── LogWriter.java │ │ │ │ └── WebMvcConfig.java │ │ │ ├── InventoryServiceApplication.java │ │ │ ├── mapper │ │ │ │ └── InventoryMapper.java │ │ │ └── utils │ │ │ │ └── AppConstants.java │ │ │ └── common │ │ │ └── dtos │ │ │ └── OrderItemDto.java │ └── test │ │ ├── resources │ │ └── logback-test.xml │ │ └── java │ │ └── com │ │ └── example │ │ └── inventoryservice │ │ ├── util │ │ └── MockTestData.java │ │ ├── InventoryServiceApplicationIntegrationTest.java │ │ ├── TestInventoryApplication.java │ │ ├── common │ │ ├── SQLContainersConfig.java │ │ └── NonSQLContainersConfig.java │ │ ├── config │ │ └── TestStockOrderListenerConfig.java │ │ └── repositories │ │ └── InventoryRepositoryTest.java ├── build-config │ └── checkstyle │ │ └── suppressions.xml ├── Jenkinsfile ├── .yo-rc.json ├── docker │ └── docker-compose-app.yml ├── .github │ └── workflows │ │ ├── maven-main.yml │ │ └── maven-dev.yml ├── .gitignore ├── sonar-project.properties └── README.md ├── config-server ├── src │ ├── main │ │ ├── resources │ │ │ ├── config-repository │ │ │ │ ├── order-service-docker.properties │ │ │ │ ├── application-native.yml │ │ │ │ ├── naming-server-docker.yml │ │ │ │ ├── api-gateway.properties │ │ │ │ ├── application-docker.yml │ │ │ │ ├── catalog-service-docker.properties │ │ │ │ └── inventory-service.properties │ │ │ └── application.properties │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── configserver │ │ │ ├── ConfigServerApplication.java │ │ │ └── config │ │ │ └── SecurityConfig.java │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── configserver │ │ └── ConfigServerApplicationTests.java ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── sonar-project.properties └── ReadMe.md ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── gatling-tests ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── src │ └── test │ │ ├── java │ │ └── simulation │ │ │ ├── InventoryResponseDTO.java │ │ │ └── GatlingHelper.java │ │ └── resources │ │ └── logback-test.xml └── run-optimized-test.cmd ├── service-registry ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── src │ ├── test │ │ └── java │ │ │ └── org │ │ │ └── service │ │ │ └── registry │ │ │ ├── NamingServerApplicationTests.java │ │ │ └── TestNamingServerApplication.java │ └── main │ │ ├── resources │ │ └── application.properties │ │ └── java │ │ └── org │ │ └── service │ │ └── registry │ │ └── NamingServerApplication.java ├── docker-compose.yml ├── sonar-project.properties └── ReadMe.md ├── payment-service ├── src │ ├── main │ │ ├── resources │ │ │ ├── db │ │ │ │ └── changelog │ │ │ │ │ ├── db.changelog-master.yaml │ │ │ │ │ └── migration │ │ │ │ │ └── 00-create_payment_schema.xml │ │ │ ├── application-local.properties │ │ │ ├── application.properties │ │ │ └── logback-spring.xml │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ ├── paymentservice │ │ │ ├── model │ │ │ │ ├── query │ │ │ │ │ └── FindCustomersQuery.java │ │ │ │ ├── response │ │ │ │ │ ├── CustomerResponse.java │ │ │ │ │ └── PagedResult.java │ │ │ │ └── request │ │ │ │ │ └── CustomerRequest.java │ │ │ ├── exception │ │ │ │ └── CustomerNotFoundException.java │ │ │ ├── config │ │ │ │ ├── SwaggerConfig.java │ │ │ │ ├── RestTemplateConfig.java │ │ │ │ ├── logging │ │ │ │ │ ├── Loggable.java │ │ │ │ │ └── LogWriter.java │ │ │ │ └── WebMvcConfig.java │ │ │ ├── PaymentApplication.java │ │ │ ├── repositories │ │ │ │ └── CustomerRepository.java │ │ │ └── utils │ │ │ │ └── AppConstants.java │ │ │ └── common │ │ │ └── dtos │ │ │ ├── OrderItemDto.java │ │ │ └── OrderDto.java │ └── test │ │ ├── resources │ │ ├── application-test.properties │ │ └── logback-test.xml │ │ └── java │ │ └── com │ │ └── example │ │ └── paymentservice │ │ ├── TestPaymentApplication.java │ │ ├── util │ │ └── TestData.java │ │ └── common │ │ ├── SQLContainerConfig.java │ │ └── NonSQLContainerConfig.java ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── Jenkinsfile ├── .yo-rc.json ├── docker │ └── docker-compose-app.yml ├── .gitignore ├── .github │ └── workflows │ │ ├── maven-main.yml │ │ └── maven-dev.yml ├── sonar-project.properties └── README.md ├── deployment └── config │ ├── grafana │ └── provisioning │ │ └── dashboards │ │ └── dashboard.yml │ ├── alert-manager │ └── config │ │ └── alertmanager.yml │ ├── promtail │ └── promtail.yml │ ├── prometheus │ └── config │ │ └── alert-rules.yml │ └── tempo │ └── tempo.yml ├── renovate.json ├── .github ├── workflows │ ├── end-to-end-tests.yml │ ├── greetings.yml │ ├── label.yml │ └── gatling-tests.yml └── dependabot.yml ├── .vscode └── settings.json ├── run-circuit-test.sh ├── .gitignore ├── .gitattributes ├── install.sh ├── LICENSE ├── pom.xml └── .devcontainer └── devcontainer.json /retail-store-webapp/src/main/resources/static/css/styles.css: -------------------------------------------------------------------------------- 1 | #app { 2 | padding-top: 90px; 3 | } -------------------------------------------------------------------------------- /api-gateway/src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.testinstance.lifecycle.default=per_class -------------------------------------------------------------------------------- /images/swagger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajadilipkolli/spring-boot-microservices-series-v2/HEAD/images/swagger.jpg -------------------------------------------------------------------------------- /images/zipkin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajadilipkolli/spring-boot-microservices-series-v2/HEAD/images/zipkin.jpg -------------------------------------------------------------------------------- /api-gateway/license-header: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) $YEAR Raja Kolli. 4 |

5 | ***/ 6 | 7 | -------------------------------------------------------------------------------- /catalog-service/license-header: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) $YEAR Raja Kolli. 4 |

5 | ***/ 6 | 7 | -------------------------------------------------------------------------------- /order-service/license-header: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) $YEAR Raja Kolli. 4 |

5 | ***/ 6 | 7 | -------------------------------------------------------------------------------- /inventory-service/license-header: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) $YEAR Raja Kolli. 4 |

5 | ***/ 6 | 7 | -------------------------------------------------------------------------------- /images/gatewayArchitecture.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajadilipkolli/spring-boot-microservices-series-v2/HEAD/images/gatewayArchitecture.JPG -------------------------------------------------------------------------------- /images/microservicesArchitecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajadilipkolli/spring-boot-microservices-series-v2/HEAD/images/microservicesArchitecture.png -------------------------------------------------------------------------------- /config-server/src/main/resources/config-repository/order-service-docker.properties: -------------------------------------------------------------------------------- 1 | application.catalog-service-url=http://catalog-service:18080/catalog-service 2 | 3 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionType=only-script 2 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip 3 | -------------------------------------------------------------------------------- /config-server/src/main/resources/config-repository/application-native.yml: -------------------------------------------------------------------------------- 1 | 2 | spring: 3 | config.import: optional:configserver:http://localhost:8888/ 4 | threads.virtual.enabled: true 5 | -------------------------------------------------------------------------------- /api-gateway/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionType=only-script 2 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip 3 | -------------------------------------------------------------------------------- /config-server/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionType=only-script 2 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip 3 | -------------------------------------------------------------------------------- /gatling-tests/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionType=only-script 2 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip 3 | -------------------------------------------------------------------------------- /retail-store-webapp/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionType=only-script 2 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip 3 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/resources/static/images/books.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rajadilipkolli/spring-boot-microservices-series-v2/HEAD/retail-store-webapp/src/main/resources/static/images/books.png -------------------------------------------------------------------------------- /service-registry/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionType=only-script 2 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip 3 | -------------------------------------------------------------------------------- /catalog-service/src/main/resources/db/changelog/db.changelog-master.yaml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - includeAll: 3 | path: migration/ 4 | errorIfMissingOrEmpty: true 5 | relativeToChangelogFile: true -------------------------------------------------------------------------------- /order-service/src/main/resources/db/changelog/db.changelog-master.yaml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - includeAll: 3 | path: /migration 4 | relativeToChangelogFile: true 5 | errorsOnMissingFile: true 6 | -------------------------------------------------------------------------------- /payment-service/src/main/resources/db/changelog/db.changelog-master.yaml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - includeAll: 3 | path: /migration 4 | relativeToChangelogFile: true 5 | errorsOnMissingFile: true 6 | -------------------------------------------------------------------------------- /deployment/config/grafana/provisioning/dashboards/dashboard.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'Default' 5 | folder: 'Applications' 6 | options: 7 | path: /etc/grafana/provisioning/dashboards -------------------------------------------------------------------------------- /catalog-service/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | wrapperVersion=3.3.4 2 | distributionType=only-script 3 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip 4 | -------------------------------------------------------------------------------- /order-service/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | wrapperVersion=3.3.4 2 | distributionType=only-script 3 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip 4 | -------------------------------------------------------------------------------- /payment-service/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | wrapperVersion=3.3.4 2 | distributionType=only-script 3 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip 4 | -------------------------------------------------------------------------------- /inventory-service/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | wrapperVersion=3.3.4 2 | distributionType=only-script 3 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip 4 | -------------------------------------------------------------------------------- /inventory-service/src/main/resources/db/changelog/db.changelog-master.yaml: -------------------------------------------------------------------------------- 1 | databaseChangeLog: 2 | - includeAll: 3 | path: migration/ 4 | errorIfMissingOrEmpty: true 5 | relativeToChangelogFile: true 6 | -------------------------------------------------------------------------------- /config-server/src/main/resources/config-repository/naming-server-docker.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | instance: 3 | hostname: localhost 4 | client: 5 | serviceUrl: 6 | defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ -------------------------------------------------------------------------------- /config-server/src/main/resources/config-repository/api-gateway.properties: -------------------------------------------------------------------------------- 1 | #spring.cloud.gateway.discovery.locator.enabled=true 2 | #spring.cloud.gateway.discovery.locator.lower-case-service-id=true 3 | 4 | management.endpoints.web.exposure.include=* -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/order/OrderConfirmationDTO.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.order; 2 | 3 | public record OrderConfirmationDTO(Long orderId, Long customerId, String status) {} 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:recommended" 4 | ], 5 | "packageRules": [ 6 | { 7 | "matchUpdateTypes": [ 8 | "minor", 9 | "patch", 10 | "pin" 11 | ], 12 | "automerge": true 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/order/OrderItemExternal.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.order; 2 | 3 | import java.math.BigDecimal; 4 | 5 | record OrderItemExternal(String productCode, int quantity, BigDecimal productPrice) {} 6 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/order/OrderItemRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.order; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record OrderItemRequest(String productCode, int quantity, BigDecimal price) {} 6 | -------------------------------------------------------------------------------- /api-gateway/build-config/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /order-service/build-config/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /catalog-service/build-config/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /inventory-service/build-config/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/model/query/FindCustomersQuery.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2023 Raja Kolli. ***/ 2 | package com.example.paymentservice.model.query; 3 | 4 | public record FindCustomersQuery(int pageNo, int pageSize, String sortBy, String sortDir) {} 5 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/example/api/gateway/model/ServiceResult.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway.model; 8 | 9 | /** Internal result type for service calls */ 10 | public record ServiceResult(int status, String response) {} 11 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/example/api/gateway/model/UsernameDTO.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway.model; 8 | 9 | import java.io.Serializable; 10 | 11 | public record UsernameDTO(String username) implements Serializable {} 12 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/config/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | @ConfigurationProperties(prefix = "retailstore") 6 | public record ApplicationProperties(String apiGatewayUrl) {} 7 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/entities/OrderStatus.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.entities; 8 | 9 | public enum OrderStatus { 10 | NEW, 11 | ACCEPT, 12 | REJECTED, 13 | CONFIRMED, 14 | FAILED, 15 | ROLLBACK 16 | } 17 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/customer/CustomerResponse.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2023 Raja Kolli. ***/ 2 | package com.example.retailstore.webapp.clients.customer; 3 | 4 | public record CustomerResponse( 5 | Long customerId, String name, String email, String phone, String address, int amountAvailable) {} 6 | -------------------------------------------------------------------------------- /order-service/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-springboot": { 3 | "appName": "order-service", 4 | "packageName": "com.example.orderservice", 5 | "databaseType": "postgresql", 6 | "dbMigrationTool": "flywaydb", 7 | "features": [], 8 | "buildTool": "maven", 9 | "packageFolder": "com/example/orderservice", 10 | "flywayMigrationCounter": 2 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /order-service/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | triggers { 5 | pollSCM('* * * * *') 6 | } 7 | 8 | environment { 9 | APPLICATION_NAME = 'order-service' 10 | } 11 | 12 | stages { 13 | stage('Build') { 14 | steps { 15 | sh './mvnw clean verify' 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /catalog-service/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-springboot": { 3 | "appName": "catalogservice", 4 | "packageName": "com.example.catalogservice", 5 | "databaseType": "postgresql", 6 | "dbMigrationTool": "flywaydb", 7 | "features": [], 8 | "buildTool": "maven", 9 | "packageFolder": "com/example/catalogservice", 10 | "flywayMigrationCounter": 2 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /catalog-service/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | triggers { 5 | pollSCM('* * * * *') 6 | } 7 | 8 | environment { 9 | APPLICATION_NAME = 'catalogservice' 10 | } 11 | 12 | stages { 13 | stage('Build') { 14 | steps { 15 | sh './mvnw clean verify' 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /payment-service/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | triggers { 5 | pollSCM('* * * * *') 6 | } 7 | 8 | environment { 9 | APPLICATION_NAME = 'payment-service' 10 | } 11 | 12 | stages { 13 | stage('Build') { 14 | steps { 15 | sh './mvnw clean verify' 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /inventory-service/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | triggers { 5 | pollSCM('* * * * *') 6 | } 7 | 8 | environment { 9 | APPLICATION_NAME = 'inventory-service' 10 | } 11 | 12 | stages { 13 | stage('Build') { 14 | steps { 15 | sh './mvnw clean verify' 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /payment-service/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-springboot": { 3 | "appName": "payment-service", 4 | "packageName": "com.example.paymentservice", 5 | "databaseType": "postgresql", 6 | "dbMigrationTool": "liquibase", 7 | "features": [], 8 | "buildTool": "maven", 9 | "packageFolder": "com/example/paymentservice", 10 | "liquibaseMigrationCounter": 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /service-registry/src/test/java/org/service/registry/NamingServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | /* Licensed under Apache-2.0 2021-2023 */ 2 | package org.service.registry; 3 | 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @SpringBootTest 8 | class NamingServerApplicationTests { 9 | 10 | @Test 11 | void contextLoads() {} 12 | } 13 | -------------------------------------------------------------------------------- /config-server/src/test/java/com/example/configserver/ConfigServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | /* Licensed under Apache-2.0 2021-2022 */ 2 | package com.example.configserver; 3 | 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @SpringBootTest 8 | class ConfigServerApplicationTests { 9 | 10 | @Test 11 | void contextLoads() {} 12 | } 13 | -------------------------------------------------------------------------------- /deployment/config/alert-manager/config/alertmanager.yml: -------------------------------------------------------------------------------- 1 | route: 2 | receiver: app-down-alert-manager 3 | repeat_interval: 1m 4 | receivers: 5 | - name: 'app-down-alert-manager' 6 | email_configs: 7 | - to: 'dockertmt@gmail.com' 8 | from: 'no-reply-retail@gmail.com' 9 | smarthost: 'smtp.gmail.com:587' 10 | auth_username: 'username' 11 | auth_password: 'password' -------------------------------------------------------------------------------- /inventory-service/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-springboot": { 3 | "appName": "inventory-service", 4 | "packageName": "com.example.inventoryservice", 5 | "databaseType": "postgresql", 6 | "dbMigrationTool": "liquibase", 7 | "features": [], 8 | "buildTool": "maven", 9 | "packageFolder": "com/example/inventoryservice", 10 | "liquibaseMigrationCounter": 2 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /service-registry/src/test/java/org/service/registry/TestNamingServerApplication.java: -------------------------------------------------------------------------------- 1 | /* Licensed under Apache-2.0 2023 */ 2 | package org.service.registry; 3 | 4 | import org.springframework.boot.SpringApplication; 5 | 6 | public class TestNamingServerApplication { 7 | public static void main(String[] args) { 8 | SpringApplication.from(NamingServerApplication::main).run(args); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/model/response/InventoryResponse.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.model.response; 8 | 9 | import java.io.Serializable; 10 | 11 | public record InventoryResponse(String productCode, Integer availableQuantity) 12 | implements Serializable {} 13 | -------------------------------------------------------------------------------- /.github/workflows/end-to-end-tests.yml: -------------------------------------------------------------------------------- 1 | name: End-to-End Tests (Manual) 2 | on: 3 | workflow_dispatch: # Only allow manual triggering of the workflow 4 | 5 | jobs: 6 | e2e-tests: 7 | name: Run End-to-End Tests 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v6 11 | 12 | - name: Run End to End Tests 13 | run: ./test-em-all.sh --detailed-logs start stop 14 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/inventory/InventoryResponse.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.inventory; 2 | 3 | public record InventoryResponse(Long id, String productCode, Integer availableQuantity, Integer reservedItems) { 4 | public InventoryUpdateRequest createInventoryUpdateRequest() { 5 | return InventoryUpdateRequest.fromInventoryResponse(this); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/model/response/CustomerResponse.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. ***/ 2 | package com.example.paymentservice.model.response; 3 | 4 | public record CustomerResponse( 5 | Long customerId, 6 | String name, 7 | String email, 8 | String phone, 9 | String address, 10 | double amountAvailable) {} 11 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/exception/OrderNotFoundException.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2022-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.exception; 8 | 9 | public class OrderNotFoundException extends RuntimeException { 10 | public OrderNotFoundException(Long orderId) { 11 | super("Order with Id " + orderId + " not found"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request, issues] 4 | 5 | permissions: 6 | issues: write 7 | pull-requests: write 8 | 9 | jobs: 10 | greeting: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/first-interaction@v3 14 | with: 15 | repo_token: ${{ secrets.GITHUB_TOKEN }} 16 | issue_message: 'Thanks for reporting issues' 17 | pr_message: 'Thanks for sending your prs' 18 | -------------------------------------------------------------------------------- /config-server/src/main/resources/config-repository/application-docker.yml: -------------------------------------------------------------------------------- 1 | 2 | spring: 3 | datasource: 4 | password: secret 5 | url: jdbc:postgresql://postgresql:5432/appdb 6 | username: appuser 7 | kafka.bootstrap-servers: 8 | - kafka:29092 9 | threads.virtual.enabled: true 10 | eureka.client.service-url.defaultZone: http://naming-server:8761/eureka/ 11 | management.tracing.export.zipkin.endpoint: http://zipkin-server:9411/api/v2/spans 12 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/repositories/OrderItemRepository.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2022-2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.repositories; 8 | 9 | import com.example.orderservice.entities.OrderItem; 10 | import org.springframework.data.jpa.repository.JpaRepository; 11 | 12 | public interface OrderItemRepository extends JpaRepository {} 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic", 3 | "java.compile.nullAnalysis.mode": "automatic", 4 | "java.debug.settings.onBuildFailureProceed": true, 5 | "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx8G -Xms100m -Xlog:disable -javaagent:\"/workspace/.vscode-remote/extensions/gabrielbb.vscode-lombok-1.0.1-universal/server/lombok.jar\"" 6 | } -------------------------------------------------------------------------------- /payment-service/src/main/resources/application-local.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.driver-class-name=org.postgresql.Driver 2 | spring.datasource.url=jdbc:postgresql://localhost:5432/appdb 3 | spring.datasource.username=appuser 4 | spring.datasource.password=secret 5 | 6 | spring.config.import=optional:configserver:http://${CONFIG_SERVER_USR:dev-usr}:${CONFIG_SERVER_PWD:dev-pass}@${CONFIG_SERVER_HOST:localhost}:${CONFIG_SERVER_PORT:8888}/ 7 | spring.threads.virtual.enabled=true 8 | -------------------------------------------------------------------------------- /service-registry/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | config-server: 4 | container_name: config-server 5 | image: dockertmt/mmv2-config-server-25:0.0.1-SNAPSHOT 6 | deploy: 7 | resources: 8 | limits: 9 | memory: 700m 10 | ports: 11 | - "8888:8888" 12 | networks: 13 | - microservice-network 14 | environment: 15 | SPRING_PROFILES_ACTIVE: native 16 | 17 | networks: 18 | microservice-network: -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/exception/InvalidRequestException.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.BAD_REQUEST) 7 | public class InvalidRequestException extends RuntimeException { 8 | public InvalidRequestException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/inventory/InventoryUpdateRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.inventory; 2 | 3 | public record InventoryUpdateRequest(String productCode, Integer availableQuantity) { 4 | 5 | public static InventoryUpdateRequest fromInventoryResponse(InventoryResponse inventoryResponse) { 6 | return new InventoryUpdateRequest(inventoryResponse.productCode(), inventoryResponse.availableQuantity()); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /api-gateway/src/test/resources/com/example/api/gateway/filter/CorrelationIdFilterIntegrationTest/correlation-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "mappings": [ 3 | { 4 | "request": { 5 | "method": "GET", 6 | "url": "/test" 7 | }, 8 | "response": { 9 | "status": 200, 10 | "headers": { 11 | "Content-Type": "application/json" 12 | }, 13 | "body": "{\"message\": \"Test endpoint for correlation ID\"}" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /inventory-service/src/main/resources/application-local.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.driver-class-name=org.postgresql.Driver 2 | spring.datasource.url=jdbc:postgresql://localhost:5432/appdb 3 | spring.datasource.username=appuser 4 | spring.datasource.password=secret 5 | 6 | spring.config.import=optional:configserver:http://${CONFIG_SERVER_USR:dev-usr}:${CONFIG_SERVER_PWD:dev-pass}@${CONFIG_SERVER_HOST:localhost}:${CONFIG_SERVER_PORT:8888}/ 7 | spring.threads.virtual.enabled=true 8 | spring.mvc.problemdetails.enabled=true -------------------------------------------------------------------------------- /gatling-tests/src/test/java/simulation/InventoryResponseDTO.java: -------------------------------------------------------------------------------- 1 | package simulation; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public record InventoryResponseDTO( 7 | Long id, String productCode, Integer availableQuantity, Integer reservedItems) { 8 | public InventoryResponseDTO withAvailableQuantity(int nextInt) { 9 | return new InventoryResponseDTO(id(), productCode(), nextInt, reservedItems()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/exception/ProductNotFoundException.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2022-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.exception; 8 | 9 | import java.util.List; 10 | 11 | public class ProductNotFoundException extends RuntimeException { 12 | 13 | public ProductNotFoundException(List productIds) { 14 | super("One or More products Not found from " + productIds); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /retail-store-webapp/src/test/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | security: 3 | oauth2: 4 | client: 5 | provider: 6 | keycloak: 7 | issuer-uri: http://test-auth-server 8 | user-name-attribute: preferred_username 9 | registration: 10 | keycloak: 11 | client-id: test-client-id 12 | client-secret: test-client-secret 13 | scope: 14 | - openid 15 | - profile 16 | - email 17 | -------------------------------------------------------------------------------- /gatling-tests/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/resources/static/js/orders.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('alpine:init', () => { 2 | Alpine.data('initData', () => ({ 3 | orders: [], 4 | init() { 5 | this.loadOrders(); 6 | updateCartItemCount(); 7 | }, 8 | loadOrders() { 9 | $.getJSON("/api/orders", (data) => { 10 | //console.log("orders :", data) 11 | this.orders = data 12 | }); 13 | }, 14 | })) 15 | }); 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for more information: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | # https://containers.dev/guide/dependabot 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "devcontainers" 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /catalog-service/docker/docker-compose-app.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | 4 | catalogservice: 5 | build: .. 6 | ports: 7 | - "18080:18080" 8 | restart: always 9 | depends_on: 10 | - postgresqldb 11 | environment: 12 | - SPRING_PROFILES_ACTIVE=docker 13 | - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver 14 | - SPRING_DATASOURCE_URL=jdbc:postgresql://postgresqldb:5432/appdb 15 | - SPRING_DATASOURCE_USERNAME=appuser 16 | - SPRING_DATASOURCE_PASSWORD=secret 17 | -------------------------------------------------------------------------------- /run-circuit-test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Simple runner to execute only the circuit breaker checks in test-em-all.sh 3 | # Usage: ./run-circuit-test.sh 4 | 5 | # Default host/port (can be overridden via env) 6 | : ${HOST:=localhost} 7 | : ${PORT:=8765} 8 | 9 | # Enable strict circuit-breaker induction by default when using this wrapper. 10 | : ${CB_STRICT:=true} 11 | export CB_STRICT 12 | 13 | echo "Running circuit breaker checks (CB_STRICT=${CB_STRICT}) against http://${HOST}:${PORT}" 14 | bash test-em-all.sh circuit-test 15 | -------------------------------------------------------------------------------- /order-service/docker/docker-compose-app.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | order-service: 4 | build: .. 5 | ports: 6 | - "18080:8080" 7 | - "18787:8787" 8 | restart: always 9 | depends_on: 10 | - postgresqldb 11 | environment: 12 | - SPRING_PROFILES_ACTIVE=docker 13 | - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver 14 | - SPRING_DATASOURCE_URL=jdbc:postgresql://postgresqldb:5432/appdb 15 | - SPRING_DATASOURCE_USERNAME=siva 16 | - SPRING_DATASOURCE_PASSWORD=secret 17 | -------------------------------------------------------------------------------- /inventory-service/docker/docker-compose-app.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | 4 | inventory-service: 5 | build: .. 6 | ports: 7 | - "18080:8080" 8 | - "18787:8787" 9 | restart: always 10 | depends_on: 11 | - postgresql 12 | environment: 13 | - SPRING_PROFILES_ACTIVE=docker 14 | - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver 15 | - SPRING_DATASOURCE_URL=jdbc:postgresql://postgresql:5432/appdb 16 | - SPRING_DATASOURCE_USERNAME=siva 17 | - SPRING_DATASOURCE_PASSWORD=secret 18 | -------------------------------------------------------------------------------- /payment-service/docker/docker-compose-app.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | 4 | payment-service: 5 | build: .. 6 | ports: 7 | - "18080:8080" 8 | - "18787:8787" 9 | restart: always 10 | depends_on: 11 | - postgresql 12 | environment: 13 | - SPRING_PROFILES_ACTIVE=docker 14 | - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.postgresql.Driver 15 | - SPRING_DATASOURCE_URL=jdbc:postgresql://postgresql:5432/appdb 16 | - SPRING_DATASOURCE_USERNAME=appuser 17 | - SPRING_DATASOURCE_PASSWORD=secret 18 | -------------------------------------------------------------------------------- /api-gateway/src/test/resources/com/example/api/gateway/config/ApiGatewayConfigurationIntegrationTest/get-mapping.json: -------------------------------------------------------------------------------- 1 | { 2 | "mappings": [ 3 | { 4 | "request": { 5 | "method": "GET", 6 | "urlPath": "/get" 7 | }, 8 | "response": { 9 | "status": 200, 10 | "headers": { 11 | "Content-Type": "application/json" 12 | }, 13 | "jsonBody": { 14 | "args": { "Param": "MyValue" }, 15 | "headers": { "Myheader": "MyURI" } 16 | } 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /payment-service/src/test/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | server.shutdown=graceful 2 | spring.main.allow-bean-definition-overriding=true 3 | spring.jmx.enabled=false 4 | 5 | ################ Actuator ##################### 6 | management.endpoints.web.exposure.include=configprops,env,health,info,logfile,loggers,metrics 7 | management.endpoint.health.show-details=always 8 | 9 | #313 10 | spring.cloud.discovery.enabled=false 11 | spring.threads.virtual.enabled=true 12 | 13 | spring.config.import=optional:configserver:${CONFIG_SERVER:http://localhost:8888}/ 14 | -------------------------------------------------------------------------------- /api-gateway/src/test/java/com/example/api/gateway/TestAPIGatewayApplication.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway; 8 | 9 | import com.example.api.gateway.config.ContainerConfig; 10 | import org.springframework.boot.SpringApplication; 11 | 12 | public class TestAPIGatewayApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.from(APIGatewayApplication::main).with(ContainerConfig.class).run(args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /catalog-service/.github/workflows/maven-main.yml: -------------------------------------------------------------------------------- 1 | name: Main Branch CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | name: Run Unit & Integration Tests 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | 15 | - name: Set up JDK 21 16 | uses: actions/setup-java@v5.1.0 17 | with: 18 | java-version: 21 19 | distribution: 'temurin' 20 | cache: 'maven' 21 | 22 | - name: Build with Maven 23 | run: ./mvn clean install 24 | -------------------------------------------------------------------------------- /inventory-service/.github/workflows/maven-main.yml: -------------------------------------------------------------------------------- 1 | name: Main Branch CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | name: Run Unit & Integration Tests 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | 15 | - name: Set up JDK 21 16 | uses: actions/setup-java@v5.1.0 17 | with: 18 | java-version: 21 19 | distribution: 'temurin' 20 | cache: 'maven' 21 | 22 | - name: Build with Maven 23 | run: ./mvn clean install 24 | -------------------------------------------------------------------------------- /order-service/.github/workflows/maven-main.yml: -------------------------------------------------------------------------------- 1 | name: Main Branch CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | name: Run Unit & Integration Tests 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | 15 | - name: Set up JDK 21 16 | uses: actions/setup-java@v5.1.0 17 | with: 18 | java-version: 21 19 | distribution: 'temurin' 20 | cache: 'maven' 21 | 22 | - name: Build with Maven 23 | run: ./mvn clean install 24 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/example/api/gateway/model/ServiceType.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway.model; 8 | 9 | /** Service identifiers for logging and error handling */ 10 | public enum ServiceType { 11 | CATALOG("catalog"), 12 | INVENTORY("inventory"); 13 | 14 | private final String id; 15 | 16 | ServiceType(String id) { 17 | this.id = id; 18 | } 19 | 20 | public String getId() { 21 | return id; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/model/payload/ProductDto.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.model.payload; 8 | 9 | import jakarta.validation.constraints.NotBlank; 10 | import jakarta.validation.constraints.Positive; 11 | 12 | public record ProductDto( 13 | @NotBlank(message = "Product code can't be blank") String code, 14 | String productName, 15 | String description, 16 | @Positive Double price) {} 17 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/model/payload/ProductDto.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.model.payload; 8 | 9 | import jakarta.validation.constraints.NotBlank; 10 | import jakarta.validation.constraints.Positive; 11 | 12 | public record ProductDto( 13 | @NotBlank(message = "Product code can't be blank") String code, 14 | String productName, 15 | String description, 16 | @Positive Double price) {} 17 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/order/OrderItemResponse.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.order; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import java.math.BigDecimal; 5 | 6 | public record OrderItemResponse( 7 | Long itemId, 8 | String productId, 9 | int quantity, 10 | @JsonFormat(shape = JsonFormat.Shape.NUMBER_FLOAT, pattern = "0.00") BigDecimal productPrice, 11 | @JsonFormat(shape = JsonFormat.Shape.NUMBER_FLOAT, pattern = "0.00") BigDecimal price) {} 12 | -------------------------------------------------------------------------------- /inventory-service/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /order-service/.github/workflows/maven-dev.yml: -------------------------------------------------------------------------------- 1 | name: Dev Branch CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | - '!main' 8 | 9 | jobs: 10 | build: 11 | name: Run Unit & Integration Tests 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v6 15 | 16 | - name: Set up JDK 17 17 | uses: actions/setup-java@v5.1.0 18 | with: 19 | java-version: 17 20 | distribution: 'temurin' 21 | cache: 'maven' 22 | 23 | - name: Build with Maven 24 | run: ./mvn clean install 25 | -------------------------------------------------------------------------------- /catalog-service/.github/workflows/maven-dev.yml: -------------------------------------------------------------------------------- 1 | name: Dev Branch CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | - '!main' 8 | 9 | jobs: 10 | build: 11 | name: Run Unit & Integration Tests 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v6 15 | 16 | - name: Set up JDK 21 17 | uses: actions/setup-java@v5.1.0 18 | with: 19 | java-version: 21 20 | distribution: 'temurin' 21 | cache: 'maven' 22 | 23 | - name: Build with Maven 24 | run: ./mvn clean install 25 | -------------------------------------------------------------------------------- /inventory-service/.github/workflows/maven-dev.yml: -------------------------------------------------------------------------------- 1 | name: Dev Branch CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | - '!main' 8 | 9 | jobs: 10 | build: 11 | name: Run Unit & Integration Tests 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v6 15 | 16 | - name: Set up JDK 21 17 | uses: actions/setup-java@v5.1.0 18 | with: 19 | java-version: 21 20 | distribution: 'temurin' 21 | cache: 'maven' 22 | 23 | - name: Build with Maven 24 | run: ./mvn clean install 25 | -------------------------------------------------------------------------------- /retail-store-webapp/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: 'spring-boot-microservices-series-v2' 2 | services: 3 | keycloak: 4 | image: quay.io/keycloak/keycloak:26.4.7 5 | command: [ 'start-dev', '--import-realm', '--http-port=9191' ] 6 | container_name: keycloak 7 | hostname: keycloak 8 | volumes: 9 | - ./realm-config:/opt/keycloak/data/import 10 | environment: 11 | - KEYCLOAK_ADMIN=admin 12 | - KEYCLOAK_ADMIN_PASSWORD=admin1234 13 | ports: 14 | - "9191:9191" 15 | deploy: 16 | resources: 17 | limits: 18 | memory: 2gb -------------------------------------------------------------------------------- /service-registry/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=naming-server 2 | server.port=8761 3 | 4 | eureka.instance.preferIpAddress=true 5 | eureka.client.registerWithEureka=false 6 | eureka.client.fetchRegistry=false 7 | eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/ 8 | eureka.server.peer-node-read-timeout-ms= 300 9 | 10 | spring.config.import=optional:configserver:http://${CONFIG_SERVER_USR:dev-usr}:${CONFIG_SERVER_PWD:dev-pass}@${CONFIG_SERVER_HOST:localhost}:${CONFIG_SERVER_PORT:8888}/ 11 | spring.threads.virtual.enabled=true 12 | -------------------------------------------------------------------------------- /order-service/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /catalog-service/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /payment-service/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /config-server/src/main/java/com/example/configserver/ConfigServerApplication.java: -------------------------------------------------------------------------------- 1 | /* Licensed under Apache-2.0 2021-2022 */ 2 | package com.example.configserver; 3 | 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.config.server.EnableConfigServer; 7 | 8 | @SpringBootApplication 9 | @EnableConfigServer 10 | public class ConfigServerApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(ConfigServerApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/exception/CustomerNotFoundException.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2023 Raja Kolli. ***/ 2 | package com.example.paymentservice.exception; 3 | 4 | public class CustomerNotFoundException extends RuntimeException { 5 | 6 | public CustomerNotFoundException(Long customerId) { 7 | super("Customer with Id '%d' not found".formatted(customerId)); 8 | } 9 | 10 | public CustomerNotFoundException(String customerName) { 11 | super("Customer with Name '%s' not found".formatted(customerName)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /retail-store-webapp/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /catalog-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | ### Misc ### 36 | *.log 37 | .DS_Store -------------------------------------------------------------------------------- /gatling-tests/run-optimized-test.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo Running Gatling performance tests with optimized configuration... 3 | 4 | :: Change to script directory (drive- and space-safe) 5 | cd /d "%~dp0" 6 | 7 | :: Run Maven and propagate failure to caller 8 | call mvn clean gatling:test ^ 9 | -Dgatling.simulationClass=simulation.CreateProductSimulation ^ 10 | -DrampUsers=50 ^ 11 | -DconstantUsers=100 ^ 12 | -DrampDuration=30 ^ 13 | -DtestDuration=180 ^ 14 | -DkafkaInitDelay=10 || exit /b %ERRORLEVEL% 15 | 16 | echo Test execution completed. Check reports in target/gatling folder. 17 | -------------------------------------------------------------------------------- /inventory-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | ### Misc ### 36 | *.log 37 | .DS_Store -------------------------------------------------------------------------------- /order-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | ### Misc ### 36 | *.log 37 | .DS_Store -------------------------------------------------------------------------------- /payment-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | ### Misc ### 36 | *.log 37 | .DS_Store -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/PagedResult.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.util.List; 5 | 6 | public record PagedResult( 7 | List data, 8 | Long totalElements, 9 | Integer pageNumber, 10 | Integer totalPages, 11 | @JsonProperty("isFirst") Boolean isFirst, 12 | @JsonProperty("isLast") Boolean isLast, 13 | @JsonProperty("hasNext") Boolean hasNext, 14 | @JsonProperty("hasPrevious") Boolean hasPrevious) {} 15 | -------------------------------------------------------------------------------- /service-registry/src/main/java/org/service/registry/NamingServerApplication.java: -------------------------------------------------------------------------------- 1 | /* Licensed under Apache-2.0 2021-2022 */ 2 | package org.service.registry; 3 | 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 7 | 8 | @EnableEurekaServer 9 | @SpringBootApplication 10 | public class NamingServerApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(NamingServerApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/model/request/ProductRequest.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.model.request; 8 | 9 | import jakarta.validation.constraints.NotBlank; 10 | import jakarta.validation.constraints.Positive; 11 | 12 | public record ProductRequest( 13 | @NotBlank(message = "Product code can't be blank") String productCode, 14 | String productName, 15 | String description, 16 | String imageUrl, 17 | @Positive Double price) {} 18 | -------------------------------------------------------------------------------- /order-service/src/main/resources/application-h2.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | enabled: false 5 | discovery: 6 | enabled: false 7 | datasource: 8 | url: jdbc:h2:file:/data/demo 9 | username: sa 10 | password: password 11 | driverClassName: org.h2.Driver 12 | jpa: 13 | spring: 14 | jpa: 15 | database-platform: org.hibernate.dialect.H2Dialect 16 | config: 17 | import: optional:configserver:${CONFIG_SERVER:http://localhost:8888}/ 18 | application: 19 | catalog-service-url: http://localhost:18080/catalog-service 20 | byPassCircuitBreaker: true 21 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/RetailStoreWebappApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 6 | 7 | @SpringBootApplication 8 | @ConfigurationPropertiesScan 9 | public class RetailStoreWebappApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(RetailStoreWebappApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /payment-service/.github/workflows/maven-main.yml: -------------------------------------------------------------------------------- 1 | name: Main Branch CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | 15 | - name: Set up JDK 21 16 | uses: actions/setup-java@v5.1.0 17 | with: 18 | java-version: 21 19 | distribution: 'temurin' 20 | cache: 'maven' 21 | 22 | - name: Grant execute permission for mvnw 23 | run: chmod +x mvnw 24 | 25 | - name: Build with Maven 26 | run: ./mvnw clean verify 27 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/config/SecurityConstants.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.config; 2 | 3 | public final class SecurityConstants { 4 | private SecurityConstants() {} // Prevent instantiation 5 | 6 | public static final String[] PUBLIC_URLS = { 7 | "/js/**", 8 | "/css/**", 9 | "/images/**", 10 | "/error", 11 | "/webjars/**", 12 | "/", 13 | "/actuator/**", 14 | "/products/**", 15 | "/api/products/**", 16 | "/api/register", 17 | "/registration", 18 | "/login" 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /api-gateway/src/test/resources/com/example/api/gateway/config/ApiGatewayConfigurationIntegrationTest/test-routing.json: -------------------------------------------------------------------------------- 1 | { 2 | "mappings": [ 3 | { 4 | "request": { 5 | "method": "GET", 6 | "url": "/example" 7 | }, 8 | "response": { 9 | "status": 200, 10 | "headers": { 11 | "Content-Type": "application/json" 12 | }, 13 | "jsonBody": { 14 | "message": "test route response" 15 | } 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /api-gateway/src/test/resources/com/example/api/gateway/filter/LoggingFilterIntegrationTest/logging-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "mappings": [ 3 | { 4 | "request": { 5 | "method": "GET", 6 | "urlPattern": "/test/endpoint" 7 | }, 8 | "response": { 9 | "status": 200, 10 | "headers": { 11 | "Content-Type": "application/json" 12 | }, 13 | "jsonBody": { 14 | "message": "test response" 15 | } 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/common/dtos/OrderItemDto.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. ***/ 2 | package com.example.common.dtos; 3 | 4 | import java.io.Serial; 5 | import java.io.Serializable; 6 | import java.math.BigDecimal; 7 | 8 | public record OrderItemDto(Long itemId, String productId, int quantity, BigDecimal productPrice) 9 | implements Serializable { 10 | 11 | @Serial private static final long serialVersionUID = 1L; 12 | 13 | public BigDecimal getPrice() { 14 | return this.productPrice().multiply(BigDecimal.valueOf(this.quantity())); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/catalog/ProductResponse.java: -------------------------------------------------------------------------------- 1 | /*** 2 | *

3 | * Licensed under MIT License Copyright (c) 2024 Raja Kolli. 4 | *

5 | ***/ 6 | package com.example.retailstore.webapp.clients.catalog; 7 | 8 | import com.fasterxml.jackson.annotation.JsonFormat; 9 | 10 | public record ProductResponse( 11 | Long id, 12 | String productCode, 13 | String productName, 14 | String description, 15 | String imageUrl, 16 | @JsonFormat(shape = JsonFormat.Shape.NUMBER_FLOAT, pattern = "0.00") Double price, 17 | boolean inStock) {} 18 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/repositories/InventoryRepository.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.repositories; 8 | 9 | import com.example.inventoryservice.entities.Inventory; 10 | import java.util.List; 11 | import org.springframework.data.jpa.repository.JpaRepository; 12 | 13 | public interface InventoryRepository extends JpaRepository { 14 | 15 | List findByProductCodeIn(List productCodes); 16 | 17 | boolean existsByProductCode(String productCode); 18 | } 19 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2022-2024 Raja Kolli. ***/ 2 | package com.example.paymentservice.config; 3 | 4 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 5 | import io.swagger.v3.oas.annotations.info.Info; 6 | import io.swagger.v3.oas.annotations.servers.Server; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | @OpenAPIDefinition( 11 | info = @Info(title = "payment-service", version = "v1"), 12 | servers = @Server(url = "${server.servlet.contextPath}")) 13 | class SwaggerConfig {} 14 | -------------------------------------------------------------------------------- /payment-service/.github/workflows/maven-dev.yml: -------------------------------------------------------------------------------- 1 | name: Dev Branch CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | - '!main' 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v6 15 | 16 | - name: Set up JDK 21 17 | uses: actions/setup-java@v5.1.0 18 | with: 19 | java-version: 21 20 | distribution: 'temurin' 21 | cache: 'maven' 22 | 23 | - name: Grant execute permission for mvnw 24 | run: chmod +x mvnw 25 | 26 | - name: Build with Maven 27 | run: ./mvnw clean install 28 | -------------------------------------------------------------------------------- /payment-service/src/main/resources/db/changelog/migration/00-create_payment_schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | CREATE SCHEMA IF NOT EXISTS payment 10 | Create payment schema 11 | 12 | 13 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/model/request/InventoryRequest.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.model.request; 8 | 9 | import jakarta.validation.constraints.NotBlank; 10 | import jakarta.validation.constraints.PositiveOrZero; 11 | import java.io.Serializable; 12 | 13 | public record InventoryRequest( 14 | @NotBlank(message = "ProductCode can't be blank") String productCode, 15 | @PositiveOrZero(message = "Quantity can't be negative") Integer availableQuantity) 16 | implements Serializable {} 17 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/common/dtos/OrderItemDto.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.common.dtos; 8 | 9 | import java.io.Serial; 10 | import java.io.Serializable; 11 | import java.math.BigDecimal; 12 | 13 | public record OrderItemDto(Long itemId, String productId, int quantity, BigDecimal productPrice) 14 | implements Serializable { 15 | 16 | @Serial private static final long serialVersionUID = 1L; 17 | 18 | public BigDecimal getPrice() { 19 | return this.productPrice().multiply(BigDecimal.valueOf(this.quantity())); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/common/dtos/OrderItemDto.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.common.dtos; 8 | 9 | import java.io.Serial; 10 | import java.io.Serializable; 11 | import java.math.BigDecimal; 12 | 13 | public record OrderItemDto(Long itemId, String productId, int quantity, BigDecimal productPrice) 14 | implements Serializable { 15 | 16 | @Serial private static final long serialVersionUID = 1L; 17 | 18 | public BigDecimal getPrice() { 19 | return this.productPrice().multiply(BigDecimal.valueOf(this.quantity())); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/catalog/ProductRequest.java: -------------------------------------------------------------------------------- 1 | /*** 2 | *

3 | * Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. 4 | *

5 | ***/ 6 | package com.example.retailstore.webapp.clients.catalog; 7 | 8 | import jakarta.validation.constraints.NotBlank; 9 | import jakarta.validation.constraints.Positive; 10 | 11 | public record ProductRequest( 12 | @NotBlank(message = "Product code can't be blank") String productCode, 13 | @NotBlank(message = "Product name can't be blank") String productName, 14 | String description, 15 | String imageUrl, 16 | @Positive Double price) {} 17 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/example/api/gateway/APIGatewayApplication.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway; 8 | 9 | import org.springframework.boot.SpringApplication; 10 | import org.springframework.boot.autoconfigure.SpringBootApplication; 11 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 12 | 13 | @ConfigurationPropertiesScan 14 | @SpringBootApplication 15 | public class APIGatewayApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(APIGatewayApplication.class, args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /inventory-service/src/test/java/com/example/inventoryservice/util/MockTestData.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2024-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.util; 8 | 9 | import com.example.common.dtos.OrderDto; 10 | import com.example.common.dtos.OrderItemDto; 11 | import java.math.BigDecimal; 12 | import java.util.List; 13 | 14 | public class MockTestData { 15 | public static OrderDto getOrderDto(String source) { 16 | OrderItemDto orderItemDto = new OrderItemDto(1L, "JUNIT_000", 10, BigDecimal.TEN); 17 | return new OrderDto(151L, 1001L, "NEW", source, List.of(orderItemDto)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/model/response/OrderItemResponse.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.model.response; 8 | 9 | import com.fasterxml.jackson.annotation.JsonFormat; 10 | import java.math.BigDecimal; 11 | 12 | public record OrderItemResponse( 13 | Long itemId, 14 | String productId, 15 | int quantity, 16 | @JsonFormat(shape = JsonFormat.Shape.NUMBER_FLOAT, pattern = "0.00") 17 | BigDecimal productPrice, 18 | @JsonFormat(shape = JsonFormat.Shape.NUMBER_FLOAT, pattern = "0.00") BigDecimal price) {} 19 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/order/Address.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.order; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | import java.io.Serializable; 5 | 6 | public record Address( 7 | @NotBlank(message = "AddressLine1 is required") String addressLine1, 8 | String addressLine2, 9 | @NotBlank(message = "City is required") String city, 10 | @NotBlank(message = "State is required") String state, 11 | @NotBlank(message = "ZipCode is required") String zipCode, 12 | @NotBlank(message = "Country is required") String country) 13 | implements Serializable {} 14 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2022-2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.config; 8 | 9 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 10 | import io.swagger.v3.oas.annotations.info.Info; 11 | import io.swagger.v3.oas.annotations.servers.Server; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | @Configuration 15 | @OpenAPIDefinition( 16 | info = @Info(title = "catalog-service", version = "v1"), 17 | servers = @Server(url = "${spring.webflux.base-path}")) 18 | public class SwaggerConfig {} 19 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2022-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.config; 8 | 9 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 10 | import io.swagger.v3.oas.annotations.info.Info; 11 | import io.swagger.v3.oas.annotations.servers.Server; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | @Configuration 15 | @OpenAPIDefinition( 16 | info = @Info(title = "inventory-service", version = "v1"), 17 | servers = @Server(url = "${server.servlet.contextPath}")) 18 | class SwaggerConfig {} 19 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/model/Address.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.model; 8 | 9 | import jakarta.validation.constraints.NotBlank; 10 | 11 | public record Address( 12 | @NotBlank(message = "AddressLine1 is required") String addressLine1, 13 | String addressLine2, 14 | @NotBlank(message = "City is required") String city, 15 | @NotBlank(message = "State is required") String state, 16 | @NotBlank(message = "ZipCode is required") String zipCode, 17 | @NotBlank(message = "Country is required") String country) {} 18 | -------------------------------------------------------------------------------- /.github/workflows/label.yml: -------------------------------------------------------------------------------- 1 | # This workflow will triage pull requests and apply a label based on the 2 | # paths that are modified in the pull request. 3 | # 4 | # To use this workflow, you will need to set up a .github/labeler.yml 5 | # file with configuration. For more information, see: 6 | # https://github.com/actions/labeler 7 | 8 | name: "Pull Request Labeler" 9 | on: 10 | - pull_request_target 11 | 12 | jobs: 13 | triage: 14 | permissions: 15 | contents: read 16 | pull-requests: write 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/labeler@v6 20 | with: 21 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 22 | sync-labels: true 23 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class ResourceNotFoundException extends RuntimeException { 8 | public ResourceNotFoundException(String message) { 9 | super(message); 10 | } 11 | 12 | public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) { 13 | super("%s not found with %s : '%s'".formatted(resourceName, fieldName, fieldValue)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/customer/CustomerRequest.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2023 Raja Kolli. ***/ 2 | package com.example.retailstore.webapp.clients.customer; 3 | 4 | import jakarta.validation.constraints.Email; 5 | import jakarta.validation.constraints.NotBlank; 6 | 7 | public record CustomerRequest( 8 | @NotBlank(message = "Name cannot be Blank") String name, 9 | @NotBlank(message = "Email cannot be Blank") @Email(message = "supplied email is not valid") String email, 10 | @NotBlank(message = "Customer Phone number is required") String phone, 11 | String address, 12 | int amountAvailable) {} 13 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/example/api/gateway/model/GenerationResponse.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway.model; 8 | 9 | import io.swagger.v3.oas.annotations.media.Schema; 10 | import java.util.Map; 11 | 12 | @Schema(description = "Response containing data generation results") 13 | public record GenerationResponse( 14 | @Schema(description = "Status of the generation process") String status, 15 | @Schema(description = "Detailed message about the generation process") String message, 16 | @Schema(description = "Map of service responses") Map serviceResponses) {} 17 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/order/OrderRequestExternal.java: -------------------------------------------------------------------------------- 1 | /*** 2 | *

3 | * Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 | *

5 | ***/ 6 | package com.example.retailstore.webapp.clients.order; 7 | 8 | import jakarta.validation.Valid; 9 | import jakarta.validation.constraints.NotEmpty; 10 | import jakarta.validation.constraints.Positive; 11 | import java.util.List; 12 | 13 | public record OrderRequestExternal( 14 | @Positive(message = "CustomerId should be positive") Long customerId, 15 | @NotEmpty(message = "Order without items not valid") List items, 16 | @Valid Address deliveryAddress) {} 17 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/model/request/OrderRequest.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.model.request; 8 | 9 | import com.example.orderservice.model.Address; 10 | import jakarta.validation.Valid; 11 | import jakarta.validation.constraints.NotEmpty; 12 | import jakarta.validation.constraints.Positive; 13 | import java.util.List; 14 | 15 | public record OrderRequest( 16 | @Positive(message = "CustomerId should be positive") Long customerId, 17 | @Valid @NotEmpty(message = "Order without items not valid") List items, 18 | @Valid Address deliveryAddress) {} 19 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/PaymentApplication.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. ***/ 2 | package com.example.paymentservice; 3 | 4 | import com.example.paymentservice.config.ApplicationProperties; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 8 | 9 | @SpringBootApplication 10 | @EnableConfigurationProperties({ApplicationProperties.class}) 11 | class PaymentApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(PaymentApplication.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/config/ObservedAspectConfiguration.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.config; 8 | 9 | import io.micrometer.observation.ObservationRegistry; 10 | import io.micrometer.observation.aop.ObservedAspect; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | @Configuration(proxyBeanMethods = false) 15 | class ObservedAspectConfiguration { 16 | 17 | @Bean 18 | ObservedAspect observedAspect(ObservationRegistry observationRegistry) { 19 | return new ObservedAspect(observationRegistry); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api-gateway/src/test/resources/com/example/api/gateway/config/RateLimiterConfigurationIntegrationTest/mocks-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mappings": [ 3 | { 4 | "request": { 5 | "method": "GET", 6 | "urlPattern": "/order-service/api/1" 7 | }, 8 | "response": { 9 | "status": 200, 10 | "headers": { 11 | "Content-Type": "application/json" 12 | }, 13 | "jsonBody": { 14 | "orderId": 1, 15 | "customerId" : 1, 16 | "status" : "COMPLETED", 17 | "items" : [ 18 | { 19 | "itemId" : 1, 20 | "productId" : "P001" 21 | } 22 | ] 23 | } 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /config-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=configserver 2 | server.port=8888 3 | management.endpoints.web.exposure.include=* 4 | management.endpoint.health.probes.enabled=true 5 | management.endpoint.health.show-details=always 6 | management.endpoint.health.show-components=always 7 | 8 | spring.profiles.include=native 9 | spring.output.ansi.enabled= ALWAYS 10 | spring.cloud.config.server.native.search-locations=classpath:/config-repository 11 | spring.threads.virtual.enabled=true 12 | #Disable Refresh for native build 13 | #spring.cloud.refresh.enabled=false 14 | 15 | #Enabling Spring security 16 | spring.security.user.name=dev-usr 17 | spring.security.user.password=dev-pass 18 | logging.level.org.springframework.security=INFO -------------------------------------------------------------------------------- /retail-store-webapp/src/main/resources/static/js/orderDetails.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('alpine:init', () => { 2 | Alpine.data('initData', (orderNumber) => ({ 3 | orderNumber: orderNumber, 4 | orderDetails: { 5 | items: [], 6 | customer: {}, 7 | deliveryAddress: {} 8 | }, 9 | init() { 10 | updateCartItemCount(); 11 | this.getOrderDetails(this.orderNumber) 12 | }, 13 | getOrderDetails(orderNumber) { 14 | $.getJSON("/api/orders/"+ orderNumber, (data) => { 15 | //console.log("Get Order Resp:", data) 16 | this.orderDetails = data 17 | }); 18 | } 19 | })) 20 | }); 21 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/config/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2022-2024 Raja Kolli. ***/ 2 | package com.example.paymentservice.config; 3 | 4 | import org.springframework.boot.restclient.RestTemplateBuilder; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | @Configuration(proxyBeanMethods = false) 10 | class RestTemplateConfig { 11 | 12 | // IMPORTANT! To instrument RestTemplate you must inject the RestTemplateBuilder 13 | @Bean 14 | RestTemplate restTemplate(RestTemplateBuilder builder) { 15 | return builder.build(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/utils/AppConstants.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.utils; 8 | 9 | public final class AppConstants { 10 | public static final String PROFILE_LOCAL = "local"; 11 | public static final String PROFILE_PROD = "prod"; 12 | public static final String PROFILE_NOT_PROD = "!" + PROFILE_PROD; 13 | public static final String PROFILE_TEST = "test"; 14 | 15 | public static final String DEFAULT_PAGE_NUMBER = "0"; 16 | public static final String DEFAULT_PAGE_SIZE = "10"; 17 | public static final String DEFAULT_SORT_BY = "id"; 18 | public static final String DEFAULT_SORT_DIRECTION = "asc"; 19 | } 20 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/OrderServiceApplication.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice; 8 | 9 | import com.example.orderservice.config.ApplicationProperties; 10 | import org.springframework.boot.SpringApplication; 11 | import org.springframework.boot.autoconfigure.SpringBootApplication; 12 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 13 | 14 | @SpringBootApplication 15 | @EnableConfigurationProperties({ApplicationProperties.class}) 16 | class OrderServiceApplication { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(OrderServiceApplication.class, args); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/example/api/gateway/bootstrap/DataInitializer.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway.bootstrap; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.boot.context.event.ApplicationReadyEvent; 12 | import org.springframework.context.event.EventListener; 13 | import org.springframework.stereotype.Component; 14 | 15 | @Component 16 | class DataInitializer { 17 | 18 | private static final Logger log = LoggerFactory.getLogger(DataInitializer.class); 19 | 20 | @EventListener(ApplicationReadyEvent.class) 21 | public void init() { 22 | log.info("start data initialization..."); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | ### Misc ### 36 | *.log 37 | .DS_Store 38 | logs/ 39 | LOG_FILE_IS_UNDEFINED 40 | catalog-service/logs/ 41 | inventory-service/logs/ 42 | inventory-service/LOG_FILE_IS_UNDEFINED 43 | order-service/logs/ 44 | order-service/LOG_FILE_IS_UNDEFINED 45 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/config/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2022-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.config; 8 | 9 | import org.springframework.boot.restclient.RestTemplateBuilder; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.web.client.RestTemplate; 13 | 14 | @Configuration(proxyBeanMethods = false) 15 | class RestTemplateConfig { 16 | 17 | // IMPORTANT! To instrument RestTemplate you must inject the RestTemplateBuilder 18 | @Bean 19 | RestTemplate restTemplate(RestTemplateBuilder builder) { 20 | return builder.build(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/model/request/OrderItemRequest.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.model.request; 8 | 9 | import jakarta.validation.constraints.DecimalMin; 10 | import jakarta.validation.constraints.NotBlank; 11 | import jakarta.validation.constraints.Positive; 12 | import java.math.BigDecimal; 13 | 14 | public record OrderItemRequest( 15 | @NotBlank(message = "Product code must be provided") String productCode, 16 | @Positive(message = "Quantity should be greater than zero") int quantity, 17 | @DecimalMin(value = "0.01", inclusive = true, message = "Price should be greater than zero") 18 | BigDecimal productPrice) {} 19 | -------------------------------------------------------------------------------- /payment-service/src/test/java/com/example/paymentservice/TestPaymentApplication.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. ***/ 2 | package com.example.paymentservice; 3 | 4 | import com.example.paymentservice.common.NonSQLContainerConfig; 5 | import com.example.paymentservice.common.SQLContainerConfig; 6 | import com.example.paymentservice.utils.AppConstants; 7 | import org.springframework.boot.SpringApplication; 8 | 9 | public class TestPaymentApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.from(PaymentApplication::main) 13 | .with(SQLContainerConfig.class, NonSQLContainerConfig.class) 14 | .withAdditionalProfiles(AppConstants.PROFILE_LOCAL) 15 | .run(args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/CatalogServiceApplication.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice; 8 | 9 | import com.example.catalogservice.config.ApplicationProperties; 10 | import org.springframework.boot.SpringApplication; 11 | import org.springframework.boot.autoconfigure.SpringBootApplication; 12 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 13 | 14 | @SpringBootApplication 15 | @EnableConfigurationProperties({ApplicationProperties.class}) 16 | public class CatalogServiceApplication { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(CatalogServiceApplication.class, args); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/InventoryServiceApplication.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice; 8 | 9 | import com.example.inventoryservice.config.ApplicationProperties; 10 | import org.springframework.boot.SpringApplication; 11 | import org.springframework.boot.autoconfigure.SpringBootApplication; 12 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 13 | 14 | @SpringBootApplication 15 | @EnableConfigurationProperties({ApplicationProperties.class}) 16 | class InventoryServiceApplication { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(InventoryServiceApplication.class, args); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /inventory-service/src/test/java/com/example/inventoryservice/InventoryServiceApplicationIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice; 8 | 9 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 10 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 11 | 12 | import com.example.inventoryservice.common.AbstractIntegrationTest; 13 | import org.junit.jupiter.api.Test; 14 | 15 | class InventoryServiceApplicationIntegrationTest extends AbstractIntegrationTest { 16 | 17 | @Test 18 | void contextLoads() throws Exception { 19 | this.mockMvc.perform(get("/actuator")).andExpect(status().isOk()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /payment-service/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.sourceEncoding=UTF-8 2 | sonar.projectKey=rajadilipkolli-microserviesv2-payment-service 3 | sonar.organization=rajadilipkolli 4 | sonar.host.url=https://sonarcloud.io 5 | 6 | sonar.sources=src/main/java 7 | sonar.tests=src/test/java 8 | sonar.exclusions=src/main/java/**/config/**,src/main/java/**/entities/*.*,src/main/java/**/models/*.*,src/main/java/**/exceptions/*.*,src/main/java/**/utils/*.*,src/main/java/**/common/dtos/*.*,src/main/java/**/*Application.* 9 | sonar.test.inclusions=**/*Test.java,**/*IntegrationTest.java,**/*IT.java 10 | sonar.java.codeCoveragePlugin=jacoco 11 | sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml 12 | sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest 13 | 14 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/model/response/OrderResponse.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.model.response; 8 | 9 | import com.example.orderservice.model.Address; 10 | import com.fasterxml.jackson.annotation.JsonFormat; 11 | import java.math.BigDecimal; 12 | import java.time.LocalDateTime; 13 | import java.util.List; 14 | 15 | public record OrderResponse( 16 | Long orderId, 17 | Long customerId, 18 | String status, 19 | String source, 20 | Address deliveryAddress, 21 | LocalDateTime createdDate, 22 | @JsonFormat(shape = JsonFormat.Shape.NUMBER_FLOAT, pattern = "0.00") BigDecimal totalPrice, 23 | List items) {} 24 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/model/request/CustomerRequest.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. ***/ 2 | package com.example.paymentservice.model.request; 3 | 4 | import jakarta.validation.constraints.Email; 5 | import jakarta.validation.constraints.NotBlank; 6 | import jakarta.validation.constraints.Positive; 7 | 8 | public record CustomerRequest( 9 | @NotBlank(message = "Name cannot be Blank") String name, 10 | @NotBlank(message = "Email cannot be Blank") @Email(message = "supplied email is not valid") 11 | String email, 12 | @NotBlank(message = "Customer Phone number is required") String phone, 13 | String address, 14 | @Positive(message = "AmountAvailable must be greater than 0") double amountAvailable) {} 15 | -------------------------------------------------------------------------------- /payment-service/src/test/java/com/example/paymentservice/util/TestData.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2024-2025 Raja Kolli. ***/ 2 | package com.example.paymentservice.util; 3 | 4 | import com.example.common.dtos.OrderDto; 5 | import com.example.paymentservice.entities.Customer; 6 | 7 | public class TestData { 8 | public static Customer getCustomer() { 9 | return new Customer().setId(1L).setAmountAvailable(1000).setAmountReserved(100); 10 | } 11 | 12 | public static OrderDto withCustomerId(long nonExistentCustomerId, OrderDto orderDto) { 13 | return new OrderDto( 14 | orderDto.orderId(), 15 | nonExistentCustomerId, 16 | orderDto.status(), 17 | orderDto.source(), 18 | orderDto.items()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /order-service/src/test/java/com/example/orderservice/TestOrderServiceApplication.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice; 8 | 9 | import com.example.orderservice.common.ContainersConfig; 10 | import com.example.orderservice.common.PostGreSQLContainer; 11 | import com.example.orderservice.utils.AppConstants; 12 | import org.springframework.boot.SpringApplication; 13 | 14 | public class TestOrderServiceApplication { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.from(OrderServiceApplication::main) 18 | .with(ContainersConfig.class, PostGreSQLContainer.class) 19 | .withAdditionalProfiles(AppConstants.PROFILE_LOCAL) 20 | .run(args); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/config/logging/Loggable.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. ***/ 2 | package com.example.paymentservice.config.logging; 3 | 4 | import static java.lang.annotation.ElementType.METHOD; 5 | import static java.lang.annotation.ElementType.TYPE; 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | import java.lang.annotation.Inherited; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.Target; 11 | import org.springframework.boot.logging.LogLevel; 12 | 13 | @Target({TYPE, METHOD}) 14 | @Retention(RUNTIME) 15 | @Inherited 16 | public @interface Loggable { 17 | 18 | boolean params() default true; 19 | 20 | boolean result() default true; 21 | 22 | LogLevel value() default LogLevel.DEBUG; 23 | } 24 | -------------------------------------------------------------------------------- /catalog-service/src/test/java/com/example/catalogservice/TestCatalogServiceApplication.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice; 8 | 9 | import com.example.catalogservice.common.ContainersConfig; 10 | import com.example.catalogservice.common.SQLContainerConfig; 11 | import com.example.catalogservice.utils.AppConstants; 12 | import org.springframework.boot.SpringApplication; 13 | 14 | public class TestCatalogServiceApplication { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.from(CatalogServiceApplication::main) 18 | .with(ContainersConfig.class, SQLContainerConfig.class) 19 | .withAdditionalProfiles(AppConstants.PROFILE_TEST) 20 | .run(args); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/config/AuditConfiguration.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.config; 8 | 9 | import java.util.Optional; 10 | import org.jspecify.annotations.NonNull; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.data.domain.AuditorAware; 14 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 15 | 16 | @Configuration(proxyBeanMethods = false) 17 | @EnableJpaAuditing(auditorAwareRef = "auditorProvider") 18 | class AuditConfiguration { 19 | 20 | @Bean 21 | AuditorAware<@NonNull String> auditorProvider() { 22 | return () -> Optional.of("AppUser"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/example/api/gateway/config/WebClientConfiguration.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway.config; 8 | 9 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.web.reactive.function.client.WebClient; 13 | 14 | @Configuration(proxyBeanMethods = false) 15 | class WebClientConfiguration { 16 | 17 | @Bean 18 | @LoadBalanced 19 | WebClient.Builder loadBalancedWebClientBuilder() { 20 | return WebClient.builder(); 21 | } 22 | 23 | @Bean 24 | WebClient webClient(WebClient.Builder builder) { 25 | return builder.build(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/config/logging/Loggable.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.config.logging; 8 | 9 | import static java.lang.annotation.ElementType.METHOD; 10 | import static java.lang.annotation.ElementType.TYPE; 11 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 12 | 13 | import java.lang.annotation.Inherited; 14 | import java.lang.annotation.Retention; 15 | import java.lang.annotation.Target; 16 | import org.springframework.boot.logging.LogLevel; 17 | 18 | @Target({TYPE, METHOD}) 19 | @Retention(RUNTIME) 20 | @Inherited 21 | public @interface Loggable { 22 | 23 | boolean params() default true; 24 | 25 | boolean result() default true; 26 | 27 | LogLevel value() default LogLevel.DEBUG; 28 | } 29 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/config/logging/Loggable.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.config.logging; 8 | 9 | import static java.lang.annotation.ElementType.METHOD; 10 | import static java.lang.annotation.ElementType.TYPE; 11 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 12 | 13 | import java.lang.annotation.Inherited; 14 | import java.lang.annotation.Retention; 15 | import java.lang.annotation.Target; 16 | import org.springframework.boot.logging.LogLevel; 17 | 18 | @Target({TYPE, METHOD}) 19 | @Retention(RUNTIME) 20 | @Inherited 21 | public @interface Loggable { 22 | 23 | boolean params() default true; 24 | 25 | boolean result() default true; 26 | 27 | LogLevel value() default LogLevel.DEBUG; 28 | } 29 | -------------------------------------------------------------------------------- /inventory-service/src/test/java/com/example/inventoryservice/TestInventoryApplication.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice; 8 | 9 | import com.example.inventoryservice.common.NonSQLContainersConfig; 10 | import com.example.inventoryservice.common.SQLContainersConfig; 11 | import com.example.inventoryservice.utils.AppConstants; 12 | import org.springframework.boot.SpringApplication; 13 | 14 | public class TestInventoryApplication { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.from(InventoryServiceApplication::main) 18 | .with(NonSQLContainersConfig.class, SQLContainersConfig.class) 19 | .withAdditionalProfiles(AppConstants.PROFILE_TEST) 20 | .run(args); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/config/logging/Loggable.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.config.logging; 8 | 9 | import static java.lang.annotation.ElementType.METHOD; 10 | import static java.lang.annotation.ElementType.TYPE; 11 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 12 | 13 | import java.lang.annotation.Inherited; 14 | import java.lang.annotation.Retention; 15 | import java.lang.annotation.Target; 16 | import org.springframework.boot.logging.LogLevel; 17 | 18 | @Target({TYPE, METHOD}) 19 | @Retention(RUNTIME) 20 | @Inherited 21 | public @interface Loggable { 22 | 23 | boolean params() default true; 24 | 25 | boolean result() default true; 26 | 27 | LogLevel value() default LogLevel.DEBUG; 28 | } 29 | -------------------------------------------------------------------------------- /payment-service/src/test/java/com/example/paymentservice/common/SQLContainerConfig.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. ***/ 2 | package com.example.paymentservice.common; 3 | 4 | import org.springframework.boot.test.context.TestConfiguration; 5 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 6 | import org.springframework.context.annotation.Bean; 7 | import org.testcontainers.postgresql.PostgreSQLContainer; 8 | import org.testcontainers.utility.DockerImageName; 9 | 10 | @TestConfiguration(proxyBeanMethods = false) 11 | public class SQLContainerConfig { 12 | 13 | @Bean 14 | @ServiceConnection 15 | PostgreSQLContainer postgreSQLContainer() { 16 | return new PostgreSQLContainer(DockerImageName.parse("postgres").withTag("18-alpine")) 17 | .withReuse(true); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/exception/CustomResponseStatusException.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.exception; 8 | 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.HttpStatusCode; 11 | import org.springframework.http.ProblemDetail; 12 | import org.springframework.web.ErrorResponseException; 13 | 14 | public class CustomResponseStatusException extends ErrorResponseException { 15 | 16 | public CustomResponseStatusException(HttpStatusCode status, String message) { 17 | super(status, problemDetail(message), null); 18 | } 19 | 20 | private static ProblemDetail problemDetail(String message) { 21 | return ProblemDetail.forStatusAndDetail(HttpStatus.SERVICE_UNAVAILABLE, message); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /inventory-service/src/test/java/com/example/inventoryservice/common/SQLContainersConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.common; 8 | 9 | import org.springframework.boot.test.context.TestConfiguration; 10 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 11 | import org.springframework.context.annotation.Bean; 12 | import org.testcontainers.postgresql.PostgreSQLContainer; 13 | import org.testcontainers.utility.DockerImageName; 14 | 15 | @TestConfiguration(proxyBeanMethods = false) 16 | public class SQLContainersConfig { 17 | 18 | @Bean 19 | @ServiceConnection 20 | PostgreSQLContainer postgreSQLContainer() { 21 | return new PostgreSQLContainer(DockerImageName.parse("postgres:18-alpine")).withReuse(true); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/catalog/CatalogServiceClient.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.catalog; 2 | 3 | import com.example.retailstore.webapp.clients.PagedResult; 4 | import org.springframework.web.bind.annotation.RequestBody; 5 | import org.springframework.web.bind.annotation.RequestParam; 6 | import org.springframework.web.service.annotation.GetExchange; 7 | import org.springframework.web.service.annotation.HttpExchange; 8 | import org.springframework.web.service.annotation.PostExchange; 9 | 10 | @HttpExchange("/catalog-service") 11 | public interface CatalogServiceClient { 12 | 13 | @GetExchange("/api/catalog") 14 | PagedResult getProducts(@RequestParam int pageNo); 15 | 16 | @PostExchange("/api/catalog") 17 | ProductResponse createProduct(@RequestBody ProductRequest productRequest); 18 | } 19 | -------------------------------------------------------------------------------- /api-gateway/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.sourceEncoding=UTF-8 2 | sonar.projectKey=rajadilipkolli-microserviesv2-api-gateway 3 | sonar.organization=rajadilipkolli 4 | sonar.host.url=https://sonarcloud.io 5 | 6 | sonar.sources=src/main/java 7 | sonar.tests=src/test/java 8 | sonar.exclusions=src/main/java/**/config/*.*,src/main/java/**/entities/*.*,src/main/java/**/models/*.*,src/main/java/**/exceptions/*.*,src/main/java/**/utils/*.*,src/main/java/**/*Application.* 9 | sonar.test.inclusions=**/*Test.java,**/*IntegrationTest.java,**/*IT.java 10 | sonar.java.codeCoveragePlugin=jacoco 11 | sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml 12 | sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest 13 | sonar.java.checkstyle.reportPaths=target/checkstyle-result.xml 14 | sonar.java.pmd.reportPaths=target/pmd/main.xml 15 | -------------------------------------------------------------------------------- /catalog-service/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.sourceEncoding=UTF-8 2 | sonar.projectKey=rajadilipkolli-microserviesv2-catalog-service 3 | sonar.organization=rajadilipkolli 4 | sonar.host.url=https://sonarcloud.io 5 | 6 | sonar.sources=src/main/java 7 | sonar.tests=src/test/java 8 | sonar.exclusions=src/main/java/**/config/**,src/main/java/**/entities/*.*,src/main/java/**/models/*.*,src/main/java/**/exceptions/*.*,src/main/java/**/utils/*.*,src/main/java/**/*Application.* 9 | sonar.test.inclusions=**/*Test.java,**/*IntegrationTest.java,**/*IT.java 10 | sonar.java.codeCoveragePlugin=jacoco 11 | sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml 12 | sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest 13 | sonar.java.checkstyle.reportPaths=target/checkstyle-result.xml 14 | sonar.java.pmd.reportPaths=target/pmd/main.xml 15 | -------------------------------------------------------------------------------- /config-server/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.sourceEncoding=UTF-8 2 | sonar.projectKey=rajadilipkolli-microserviesv2-config-server 3 | sonar.organization=rajadilipkolli 4 | sonar.host.url=https://sonarcloud.io 5 | 6 | sonar.sources=src/main/java 7 | sonar.tests=src/test/java 8 | sonar.exclusions=src/main/java/**/config/*.*,src/main/java/**/entities/*.*,src/main/java/**/models/*.*,src/main/java/**/exceptions/*.*,src/main/java/**/utils/*.*,src/main/java/**/*Application.* 9 | sonar.test.inclusions=**/*Test.java,**/*IntegrationTest.java,**/*IT.java 10 | sonar.java.codeCoveragePlugin=jacoco 11 | sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml 12 | sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest 13 | sonar.java.checkstyle.reportPaths=target/checkstyle-result.xml 14 | sonar.java.pmd.reportPaths=target/pmd/main.xml 15 | -------------------------------------------------------------------------------- /payment-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=payment-service 2 | server.port=18085 3 | server.servlet.contextPath= /${spring.application.name} 4 | 5 | ######## Kafka Configuration ######### 6 | spring.kafka.consumer.auto-offset-reset=earliest 7 | spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.LongDeserializer 8 | spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer 9 | spring.kafka.consumer.properties.spring.json.trusted.packages=* 10 | spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.LongSerializer 11 | spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer 12 | spring.kafka.producer.properties.spring.json.add.type.headers=true 13 | 14 | spring.testcontainers.beans.startup= parallel 15 | spring.threads.virtual.enabled=true -------------------------------------------------------------------------------- /order-service/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.sourceEncoding=UTF-8 2 | sonar.projectKey=rajadilipkolli-microserviesv2-order-service 3 | sonar.organization=rajadilipkolli 4 | sonar.host.url=https://sonarcloud.io 5 | 6 | sonar.sources=src/main/java 7 | sonar.tests=src/test/java 8 | sonar.exclusions=src/main/java/**/config/**,src/main/java/**/entities/*.*,src/main/java/**/models/*.*,src/main/java/**/exceptions/*.*,src/main/java/**/common/dtos/*.*,src/main/java/**/*Application.* 9 | sonar.test.inclusions=**/*Test.java,**/*IntegrationTest.java,**/*IT.java 10 | sonar.java.codeCoveragePlugin=jacoco 11 | sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml 12 | sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest 13 | sonar.java.checkstyle.reportPaths=target/checkstyle-result.xml 14 | sonar.java.pmd.reportPaths=target/pmd/main.xml 15 | -------------------------------------------------------------------------------- /service-registry/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.sourceEncoding=UTF-8 2 | sonar.projectKey=rajadilipkolli-microserviesv2-service-registry 3 | sonar.organization=rajadilipkolli 4 | sonar.host.url=https://sonarcloud.io 5 | 6 | sonar.sources=src/main/java 7 | sonar.tests=src/test/java 8 | sonar.exclusions=src/main/java/**/config/*.*,src/main/java/**/entities/*.*,src/main/java/**/models/*.*,src/main/java/**/exceptions/*.*,src/main/java/**/utils/*.*,src/main/java/**/*Application.* 9 | sonar.test.inclusions=**/*Test.java,**/*IntegrationTest.java,**/*IT.java 10 | sonar.java.codeCoveragePlugin=jacoco 11 | sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml 12 | sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest 13 | sonar.java.checkstyle.reportPaths=target/checkstyle-result.xml 14 | sonar.java.pmd.reportPaths=target/pmd/main.xml 15 | -------------------------------------------------------------------------------- /order-service/src/test/java/com/example/orderservice/common/PostGreSQLContainer.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.common; 8 | 9 | import org.springframework.boot.test.context.TestConfiguration; 10 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 11 | import org.springframework.context.annotation.Bean; 12 | import org.testcontainers.postgresql.PostgreSQLContainer; 13 | import org.testcontainers.utility.DockerImageName; 14 | 15 | @TestConfiguration(proxyBeanMethods = false) 16 | public class PostGreSQLContainer { 17 | 18 | @ServiceConnection 19 | @Bean 20 | PostgreSQLContainer postgreSQLContainer() { 21 | return new PostgreSQLContainer(DockerImageName.parse("postgres").withTag("18-alpine")) 22 | .withReuse(true); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /retail-store-webapp/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.sourceEncoding=UTF-8 2 | sonar.projectKey=rajadilipkolli-microservicesv2-retail-store-webapp 3 | sonar.organization=rajadilipkolli 4 | sonar.host.url=https://sonarcloud.io 5 | 6 | sonar.sources=src/main/java 7 | sonar.tests=src/test/java 8 | sonar.coverage.exclusions=src/main/java/**/config/**,src/main/java/**/entities/*.*,src/main/java/**/models/*.*,src/main/java/**/exceptions/*.*,src/main/java/**/utils/*.*,src/main/java/**/*Application.* 9 | sonar.test.inclusions=**/*Test.java,**/*IntegrationTest.java,**/*IT.java 10 | sonar.java.codeCoveragePlugin=jacoco 11 | sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml 12 | sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest 13 | sonar.java.checkstyle.reportPaths=target/checkstyle-result.xml 14 | sonar.java.pmd.reportPaths=target/pmd/main.xml 15 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/config/Initializer.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.config; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.boot.CommandLineRunner; 12 | import org.springframework.stereotype.Component; 13 | 14 | @Component 15 | public class Initializer implements CommandLineRunner { 16 | 17 | private final Logger log = LoggerFactory.getLogger(this.getClass()); 18 | 19 | private final ApplicationProperties properties; 20 | 21 | public Initializer(ApplicationProperties properties) { 22 | this.properties = properties; 23 | } 24 | 25 | @Override 26 | public void run(String... args) { 27 | log.info("Running Initializer.....{}", properties.inventoryServiceUrl()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /inventory-service/sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.sourceEncoding=UTF-8 2 | sonar.projectKey=rajadilipkolli-microserviesv2-inventory-service 3 | sonar.organization=rajadilipkolli 4 | sonar.host.url=https://sonarcloud.io 5 | 6 | sonar.sources=src/main/java 7 | sonar.tests=src/test/java 8 | sonar.exclusions=src/main/java/**/config/**,src/main/java/**/entities/*.*,src/main/java/**/models/*.*,src/main/java/**/exceptions/*.*,src/main/java/**/utils/*.*,src/main/java/**/common/dtos/*.*,src/main/java/**/*Application.* 9 | sonar.test.inclusions=**/*Test.java,**/*IntegrationTest.java,**/*IT.java 10 | sonar.java.codeCoveragePlugin=jacoco 11 | sonar.coverage.jacoco.xmlReportPaths=target/jacoco/test/jacoco.xml,target/jacoco/integrationTest/jacoco.xml 12 | sonar.junit.reportPaths=target/test-results/test,target/test-results/integrationTest 13 | sonar.java.checkstyle.reportPaths=target/checkstyle-result.xml 14 | sonar.java.pmd.reportPaths=target/pmd/main.xml 15 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/config/logging/LogWriter.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2024 Raja Kolli. ***/ 2 | package com.example.paymentservice.config.logging; 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.logging.LogLevel; 7 | 8 | class LogWriter { 9 | 10 | public static void write(Class originClass, LogLevel logLevel, String message) { 11 | Logger logger = LoggerFactory.getLogger(originClass); 12 | switch (logLevel) { 13 | case TRACE -> logger.trace(message); 14 | case DEBUG -> logger.debug(message); 15 | case INFO -> logger.info(message); 16 | case WARN -> logger.warn(message); 17 | case ERROR, FATAL -> logger.error(message); 18 | default -> logger.info("Defaulting to INFO level for message: {}", message); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/repositories/InventoryJOOQRepository.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.repositories; 8 | 9 | import com.example.inventoryservice.entities.Inventory; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.data.domain.Pageable; 14 | import org.springframework.stereotype.Repository; 15 | 16 | @Repository 17 | public interface InventoryJOOQRepository { 18 | 19 | Optional findById(Long customerId); 20 | 21 | Page findAll(Pageable pageable); 22 | 23 | Optional findByProductCode(String productCode); 24 | 25 | List findByProductCodeIn(List productCodes); 26 | 27 | int deleteByProductCode(String productCode); 28 | } 29 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/config/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.config; 8 | 9 | import jakarta.validation.Valid; 10 | import jakarta.validation.constraints.NotBlank; 11 | import org.springframework.boot.context.properties.ConfigurationProperties; 12 | import org.springframework.boot.context.properties.NestedConfigurationProperty; 13 | import org.springframework.validation.annotation.Validated; 14 | 15 | @ConfigurationProperties("application") 16 | @Validated 17 | public record ApplicationProperties( 18 | @NotBlank(message = "CatalogServiceUrl Cant be Blank") String catalogServiceUrl, 19 | boolean byPassCircuitBreaker, 20 | @NestedConfigurationProperty @Valid Cors cors) { 21 | 22 | public ApplicationProperties { 23 | cors = new Cors(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /retail-store-webapp/src/test/java/com/example/retailstore/webapp/ApplicationIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp; 2 | 3 | import com.example.retailstore.webapp.common.AbstractIntegrationTest; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.http.HttpStatus; 6 | 7 | class ApplicationIntegrationTest extends AbstractIntegrationTest { 8 | 9 | @Test 10 | void shouldReturnHealthyStatusFromActuatorEndpoint() { 11 | mockMvcTester 12 | .get() 13 | .uri("/actuator/health") 14 | .assertThat() 15 | .hasContentType("application/vnd.spring-boot.actuator.v3+json") 16 | .hasHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate") 17 | .hasStatus(HttpStatus.OK) 18 | .bodyJson() 19 | .extractingPath("$.status") 20 | .isEqualTo("UP"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/mapper/InventoryMapper.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.mapper; 8 | 9 | import com.example.inventoryservice.entities.Inventory; 10 | import com.example.inventoryservice.model.request.InventoryRequest; 11 | import org.mapstruct.InheritConfiguration; 12 | import org.mapstruct.Mapper; 13 | import org.mapstruct.Mapping; 14 | import org.mapstruct.MappingTarget; 15 | 16 | @Mapper(componentModel = "spring") 17 | public interface InventoryMapper { 18 | 19 | @Mapping(target = "id", ignore = true) 20 | @Mapping(target = "reservedItems", ignore = true) 21 | Inventory toEntity(InventoryRequest inventoryRequest); 22 | 23 | @InheritConfiguration 24 | void updateInventoryFromRequest( 25 | InventoryRequest inventoryRequest, @MappingTarget Inventory inventory); 26 | } 27 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/config/logging/LogWriter.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.config.logging; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.boot.logging.LogLevel; 12 | 13 | public class LogWriter { 14 | 15 | public static void write(Class originClass, LogLevel logLevel, String message) { 16 | Logger logger = LoggerFactory.getLogger(originClass); 17 | switch (logLevel) { 18 | case TRACE -> logger.trace(message); 19 | case DEBUG -> logger.debug(message); 20 | case INFO -> logger.info(message); 21 | case WARN -> logger.warn(message); 22 | case ERROR, FATAL -> logger.error(message); 23 | default -> logger.warn("No suitable logLevel found"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/customer/CustomerServiceClient.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.customer; 2 | 3 | import org.springframework.web.bind.annotation.PathVariable; 4 | import org.springframework.web.bind.annotation.RequestBody; 5 | import org.springframework.web.service.annotation.GetExchange; 6 | import org.springframework.web.service.annotation.HttpExchange; 7 | import org.springframework.web.service.annotation.PostExchange; 8 | 9 | @HttpExchange("/payment-service") 10 | public interface CustomerServiceClient { 11 | 12 | @PostExchange("/api/customers") 13 | CustomerResponse getOrCreateCustomer(@RequestBody CustomerRequest customer); 14 | 15 | @GetExchange("/api/customers/name/{name}") 16 | CustomerResponse getCustomerByName(@PathVariable String name); 17 | 18 | @GetExchange("/api/customers/{id}") 19 | CustomerResponse getCustomerById(@PathVariable Long id); 20 | } 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto-detect text files and normalize 2 | * text=auto 3 | 4 | # Force LF for shell scripts and common config/source files 5 | *.sh text eol=lf 6 | *.yml text eol=lf 7 | *.yaml text eol=lf 8 | *.properties text eol=lf 9 | *.xml text eol=lf 10 | *.java text eol=lf 11 | *.gradle text eol=lf 12 | pom.xml text eol=lf 13 | *.md text eol=lf 14 | Dockerfile text eol=lf 15 | deployment/** text eol=lf 16 | 17 | # Keep Windows script files in CRLF form (helpful for PowerShell/Batch authors) 18 | *.ps1 text eol=crlf 19 | *.bat text eol=crlf 20 | 21 | # Treat common binary files as binary to avoid EOL conversion 22 | *.png binary 23 | *.jpg binary 24 | *.jpeg binary 25 | *.gif binary 26 | *.jar binary 27 | *.war binary 28 | *.zip binary 29 | *.gz binary 30 | 31 | # Ensure the attributes file itself uses LF 32 | .gitattributes text eol=lf 33 | *.xml text eol=lf 34 | *.yaml text eol=lf 35 | *.properties text eol=lf 36 | /mvnw text eol=lf 37 | *.cmd text eol=crlf 38 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/config/logging/LogWriter.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.config.logging; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.boot.logging.LogLevel; 12 | 13 | public class LogWriter { 14 | 15 | public static void write(Class originClass, LogLevel logLevel, String message) { 16 | Logger logger = LoggerFactory.getLogger(originClass); 17 | switch (logLevel) { 18 | case TRACE -> logger.trace(message); 19 | case DEBUG -> logger.debug(message); 20 | case INFO -> logger.info(message); 21 | case WARN -> logger.warn(message); 22 | case ERROR, FATAL -> logger.error(message); 23 | default -> logger.warn("No suitable logLevel found"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /catalog-service/src/test/java/com/example/catalogservice/common/SQLContainerConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.common; 8 | 9 | import org.springframework.boot.test.context.TestConfiguration; 10 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 11 | import org.springframework.context.annotation.Bean; 12 | import org.testcontainers.postgresql.PostgreSQLContainer; 13 | import org.testcontainers.utility.DockerImageName; 14 | 15 | @TestConfiguration(proxyBeanMethods = false) 16 | public class SQLContainerConfig { 17 | 18 | @Bean 19 | @ServiceConnection 20 | PostgreSQLContainer postgreSQLContainer() { 21 | return new PostgreSQLContainer(DockerImageName.parse("postgres").withTag("18-alpine")) 22 | .withDatabaseName("catalog-service") 23 | .withReuse(true); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/config/logging/LogWriter.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.config.logging; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.boot.logging.LogLevel; 12 | 13 | public class LogWriter { 14 | 15 | public static void write(Class originClass, LogLevel logLevel, String message) { 16 | Logger logger = LoggerFactory.getLogger(originClass); 17 | switch (logLevel) { 18 | case TRACE -> logger.trace(message); 19 | case DEBUG -> logger.debug(message); 20 | case INFO -> logger.info(message); 21 | case WARN -> logger.warn(message); 22 | case ERROR, FATAL -> logger.error(message); 23 | default -> logger.info("Defaulting to INFO level for message: {}", message); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /deployment/config/promtail/promtail.yml: -------------------------------------------------------------------------------- 1 | # https://grafana.com/docs/loki/latest/clients/promtail/configuration/ 2 | # https://docs.docker.com/engine/api/v1.41/#operation/ContainerList 3 | server: 4 | http_listen_port: 9080 5 | grpc_listen_port: 0 6 | 7 | positions: 8 | filename: /tmp/positions.yaml 9 | 10 | clients: 11 | - url: http://loki:3100/loki/api/v1/push 12 | 13 | scrape_configs: 14 | - job_name: flog_scrape 15 | docker_sd_configs: 16 | - host: unix:///var/run/docker.sock 17 | refresh_interval: 5s 18 | filters: 19 | - name: label 20 | values: ["logging=promtail"] 21 | relabel_configs: 22 | - source_labels: ['__meta_docker_container_name'] 23 | regex: '/(.*)' 24 | target_label: 'container' 25 | - source_labels: ['__meta_docker_container_log_stream'] 26 | target_label: 'logstream' 27 | - source_labels: ['__meta_docker_container_label_logging_jobname'] 28 | target_label: 'job' -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Download mod cli 4 | curl --request GET \ 5 | --output mod.tar.gz \ 6 | --url 'https://api.app.moderne.io/cli/download?operatingSystem=linux-tar&environment=stable' \ 7 | --header 'Authorization: Bearer ************************************************' # IGNORE 8 | 9 | # Extract the archive 10 | tar -xzf mod.tar.gz 11 | 12 | # Make the binary executable 13 | chmod +x "${PWD}/mod" 14 | 15 | # Create .moderne directory in user's home 16 | mkdir -p $HOME/.moderne/bin 17 | 18 | # Copy mod to .moderne/bin directory 19 | cp "${PWD}/mod" "$HOME/.moderne/bin/mod" 20 | 21 | # Add .moderne/bin to PATH if not already present 22 | if [[ $PATH != *".moderne/bin"* ]]; then 23 | echo "export PATH=\$PATH:\$HOME/.moderne/bin" >> $HOME/.bashrc 24 | echo "source <(mod generate-completion)" >> $HOME/.bashrc 25 | source $HOME/.bashrc 26 | fi 27 | 28 | # Clean up the downloaded files 29 | rm mod.tar.gz mod 30 | 31 | echo "mod CLI has been installed successfully!" 32 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/repositories/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2022-2024 Raja Kolli. ***/ 2 | package com.example.paymentservice.repositories; 3 | 4 | import com.example.paymentservice.entities.Customer; 5 | import com.example.paymentservice.model.response.CustomerResponse; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.Pageable; 10 | 11 | public interface CustomerRepository { 12 | 13 | Optional findByName(String name); 14 | 15 | Optional findById(Long customerId); 16 | 17 | Optional findByEmail(String email); 18 | 19 | Customer save(Customer customer); 20 | 21 | void deleteById(Long id); 22 | 23 | Page findAll(Pageable pageable); 24 | 25 | List saveAll(List customerList); 26 | 27 | void deleteAll(); 28 | } 29 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/example/api/gateway/config/WebFluxConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway.config; 8 | 9 | import org.jspecify.annotations.NonNull; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.web.reactive.config.CorsRegistry; 12 | import org.springframework.web.reactive.config.WebFluxConfigurer; 13 | 14 | @Configuration(proxyBeanMethods = false) 15 | class WebFluxConfig implements WebFluxConfigurer { 16 | 17 | @Override 18 | public void addCorsMappings(@NonNull CorsRegistry registry) { 19 | registry.addMapping("/**") 20 | .allowedMethods("GET", "POST", "OPTIONS", "HEAD", "PUT") 21 | .allowedHeaders("Access-Control-Allow-Origin") 22 | .allowedOrigins("http://localhost:8765", "mytrustedwebsite.com") 23 | .allowCredentials(true); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /service-registry/ReadMe.md: -------------------------------------------------------------------------------- 1 | #service-registry 2 | 3 | A service registry is a central directory that maintains a list of all the available microservices in a system. It allows services to discover and communicate with each other, enabling them to work together to achieve a common goal. The registry typically contains information about the location, availability, and capabilities of each service. It may also include details such as the version of the service, the protocols it uses to communicate, and any dependencies it has on other services. Service registries are an important component of microservice architecture as they enable services to be flexible and scalable, while still being able to interact with each other in a consistent and reliable manner. 4 | 5 | ### Run tests 6 | ```shell 7 | ./mvnw clean verify 8 | ``` 9 | 10 | ### Run locally 11 | ```shell 12 | docker compose -f docker-compose.yml up -d 13 | ./mvnw spring-boot:run 14 | ``` 15 | 16 | ### Useful Links 17 | * Accessing Eureka Server via http://localhost:8761/ 18 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/inventory/InventoryServiceClient.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.inventory; 2 | 3 | import com.example.retailstore.webapp.clients.PagedResult; 4 | import org.springframework.web.bind.annotation.PathVariable; 5 | import org.springframework.web.bind.annotation.RequestBody; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | import org.springframework.web.service.annotation.GetExchange; 8 | import org.springframework.web.service.annotation.HttpExchange; 9 | import org.springframework.web.service.annotation.PutExchange; 10 | 11 | @HttpExchange("/inventory-service") 12 | public interface InventoryServiceClient { 13 | 14 | @GetExchange("/api/inventory") 15 | PagedResult getInventories(@RequestParam int pageNo); 16 | 17 | @PutExchange("/api/inventory/{id}") 18 | InventoryResponse updateInventory( 19 | @PathVariable Long id, @RequestBody InventoryUpdateRequest inventoryUpdateRequest); 20 | } 21 | -------------------------------------------------------------------------------- /deployment/config/prometheus/config/alert-rules.yml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: app-down-rules 3 | rules: 4 | # Triggers a critical alert if a server is down for more than 1 minute. 5 | - alert: ServerDown 6 | expr: up < 1 7 | for: 1m 8 | labels: 9 | severity: critical 10 | annotations: 11 | summary: "Server {{ $labels.instance }} down" 12 | description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 1 minute." 13 | 14 | # Triggers a critical alert if CPU usage of job {{ $labels.job }} exceeds 80% over 5 minutes. 15 | - alert: HighCpuUsage 16 | expr: sum(rate(process_cpu_usage{job=~".+"}[5m])) > 0.8 17 | for: 5m 18 | labels: 19 | severity: critical 20 | annotations: 21 | summary: "High CPU usage detected for {{ $labels.instance }} of job {{ $labels.job }} " 22 | description: "CPU usage of {{ $labels.instance }} of job {{ $labels.job }} has exceeded 80% for the past 5 minutes." 23 | -------------------------------------------------------------------------------- /gatling-tests/src/test/java/simulation/GatlingHelper.java: -------------------------------------------------------------------------------- 1 | package simulation; 2 | 3 | import io.gatling.javaapi.core.Session; 4 | 5 | /** 6 | * Utility class to help with common Gatling patterns that may vary between versions. This helps 7 | * isolate version-specific implementation details. 8 | */ 9 | public class GatlingHelper { 10 | 11 | /** 12 | * Check if a response has a successful status code (200-299) 13 | * 14 | * @param session the Gatling session 15 | * @return true if the last response status code is between 200 and 299 16 | */ 17 | public static boolean isSuccessResponse(Session session) { 18 | try { 19 | String statusStr = session.getString("status"); 20 | if (statusStr != null) { 21 | int status = Integer.parseInt(statusStr); 22 | return status >= 200 && status < 300; 23 | } 24 | } catch (Exception e) { 25 | // Fall through to default check 26 | } 27 | 28 | return false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/example/api/gateway/web/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway.web.controller; 8 | 9 | import static org.springframework.web.reactive.function.server.RequestPredicates.GET; 10 | import static org.springframework.web.reactive.function.server.RouterFunctions.route; 11 | 12 | import java.net.URI; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.web.reactive.function.server.RouterFunction; 16 | import org.springframework.web.reactive.function.server.ServerResponse; 17 | 18 | @Configuration(proxyBeanMethods = false) 19 | public class HomeController { 20 | 21 | @Bean 22 | RouterFunction routerFunction() { 23 | return route( 24 | GET("/"), 25 | req -> ServerResponse.temporaryRedirect(URI.create("swagger-ui.html")).build()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /api-gateway/src/main/java/com/example/api/gateway/web/controller/GatewayInventoryFallback.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway.web.controller; 8 | 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | import reactor.core.publisher.Mono; 14 | 15 | @RestController 16 | @RequestMapping("/fallback/api/inventory") 17 | public class GatewayInventoryFallback { 18 | 19 | @GetMapping("/{id}") 20 | public Mono fallback(@PathVariable String id) { 21 | // IDs in this system are alphanumeric (e.g. P0001). Accept String to avoid 22 | // path variable conversion failures which can produce 404s when the 23 | // circuit-breaker fallback is invoked. 24 | return Mono.just("Hello %s".formatted(id)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/utils/AppConstants.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. ***/ 2 | package com.example.paymentservice.utils; 3 | 4 | public final class AppConstants { 5 | public static final String PROFILE_PROD = "prod"; 6 | public static final String PROFILE_NOT_PROD = "!" + PROFILE_PROD; 7 | public static final String PROFILE_TEST = "test"; 8 | public static final String PROFILE_NOT_TEST = "!" + PROFILE_TEST; 9 | public static final String SOURCE = "PAYMENT"; 10 | public static final String ORDERS_TOPIC = "orders"; 11 | public static final String PAYMENT_ORDERS_TOPIC = "payment-orders"; 12 | public static final String ROLLBACK = "ROLLBACK"; 13 | 14 | public static final String DEFAULT_PAGE_NUMBER = "1"; 15 | public static final String DEFAULT_PAGE_SIZE = "10"; 16 | public static final String DEFAULT_SORT_BY = "id"; 17 | public static final String DEFAULT_SORT_DIRECTION = "asc"; 18 | public static final String PROFILE_LOCAL = "local"; 19 | } 20 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/services/CatalogServiceProxy.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2022-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.services; 8 | 9 | import com.example.orderservice.config.logging.Loggable; 10 | import java.util.List; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | import org.springframework.web.service.annotation.GetExchange; 14 | import org.springframework.web.service.annotation.HttpExchange; 15 | 16 | // @HttpExchange("lb://catalog-service/") 17 | // @HttpExchange("http://localhost:18080/catalog-service") 18 | @HttpExchange( 19 | // url = "${application.catalog-service-url}", 20 | accept = MediaType.APPLICATION_JSON_VALUE, 21 | contentType = MediaType.APPLICATION_JSON_VALUE) 22 | @Loggable 23 | public interface CatalogServiceProxy { 24 | 25 | @GetExchange("/api/catalog/exists") 26 | boolean productsExistsByCodes(@RequestParam List productCodes); 27 | } 28 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/utils/AppConstants.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.utils; 8 | 9 | public final class AppConstants { 10 | public static final String PROFILE_LOCAL = "local"; 11 | public static final String PROFILE_PROD = "prod"; 12 | public static final String PROFILE_NOT_PROD = "!" + PROFILE_PROD; 13 | public static final String PROFILE_TEST = "test"; 14 | public static final String ORDERS_TOPIC = "orders"; 15 | public static final String STOCK_ORDERS_TOPIC = "stock-orders"; 16 | public static final String SOURCE = "INVENTORY"; 17 | public static final String ROLLBACK = "ROLLBACK"; 18 | public static final String DEFAULT_PAGE_NUMBER = "0"; 19 | public static final String DEFAULT_PAGE_SIZE = "10"; 20 | public static final String DEFAULT_SORT_BY = "id"; 21 | public static final String DEFAULT_SORT_DIRECTION = "asc"; 22 | public static final String PRODUCT_TOPIC = "productTopic"; 23 | } 24 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/utils/AppConstants.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.utils; 8 | 9 | public final class AppConstants { 10 | public static final String PROFILE_LOCAL = "local"; 11 | public static final String PROFILE_PROD = "prod"; 12 | public static final String PROFILE_NOT_PROD = "!" + PROFILE_PROD; 13 | public static final String PROFILE_TEST = "test"; 14 | public static final String ORDERS_TOPIC = "orders"; 15 | public static final String PAYMENT_ORDERS_TOPIC = "payment-orders"; 16 | public static final String STOCK_ORDERS_TOPIC = "stock-orders"; 17 | public static final String RECOVER_DLQ_TOPIC = "recovererDLQ"; 18 | public static final String ROLLBACK = "ROLLBACK"; 19 | public static final String DEFAULT_PAGE_NUMBER = "0"; 20 | public static final String DEFAULT_PAGE_SIZE = "10"; 21 | public static final String DEFAULT_SORT_BY = "id"; 22 | public static final String DEFAULT_SORT_DIRECTION = "asc"; 23 | } 24 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2022-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.config; 8 | 9 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 10 | import io.swagger.v3.oas.annotations.info.Info; 11 | import io.swagger.v3.oas.annotations.servers.Server; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.web.filter.ForwardedHeaderFilter; 15 | 16 | @Configuration(proxyBeanMethods = false) 17 | @OpenAPIDefinition( 18 | info = 19 | @Info( 20 | title = "order-service", 21 | version = "v1", 22 | description = "APIs related to Orders"), 23 | servers = @Server(url = "${server.servlet.contextPath}")) 24 | class SwaggerConfig { 25 | 26 | @Bean 27 | ForwardedHeaderFilter forwardedHeaderFilter() { 28 | return new ForwardedHeaderFilter(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/order/CreateOrderRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.order; 2 | 3 | import com.example.retailstore.webapp.clients.customer.CustomerRequest; 4 | import jakarta.validation.Valid; 5 | import jakarta.validation.constraints.NotEmpty; 6 | import java.io.Serializable; 7 | import java.util.List; 8 | 9 | public record CreateOrderRequest( 10 | @NotEmpty(message = "Items cannot be empty.") List items, 11 | @Valid CustomerRequest customer, 12 | @Valid Address deliveryAddress) 13 | implements Serializable { 14 | public OrderRequestExternal withCustomerId(Long customerId) { 15 | return new OrderRequestExternal( 16 | customerId, 17 | items().stream() 18 | .map(orderItemRequest -> new OrderItemExternal( 19 | orderItemRequest.productCode(), orderItemRequest.quantity(), orderItemRequest.price())) 20 | .toList(), 21 | deliveryAddress()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /config-server/src/main/resources/config-repository/catalog-service-docker.properties: -------------------------------------------------------------------------------- 1 | application.inventory-service-url=http://inventory-service:18181/inventory-service 2 | 3 | spring.r2dbc.url=r2dbc:postgresql://postgresql:5432/appdb?maxIdleTime=PT60S 4 | spring.r2dbc.username=appuser 5 | spring.r2dbc.password=secret 6 | 7 | #liquibase 8 | spring.liquibase.url=jdbc:postgresql://postgresql:5432/appdb?maxIdleTime=PT60S 9 | spring.liquibase.user=${spring.r2dbc.username} 10 | spring.liquibase.password=${spring.r2dbc.password} 11 | 12 | resilience4j.circuitbreaker.instances.getInventoryByProductCodes.registerHealthIndicator=true 13 | resilience4j.circuitbreaker.instances.getInventoryByProductCodes.slidingWindowSize=2 14 | resilience4j.circuitbreaker.instances.getInventoryByProductCodes.minimumNumberOfCalls=1 15 | resilience4j.circuitbreaker.instances.getInventoryByProductCodes.failureRateThreshold=50 16 | resilience4j.circuitbreaker.instances.getInventoryByProductCodes.permittedNumberOfCallsInHalfOpenState=1 17 | resilience4j.circuitbreaker.instances.getInventoryByProductCodes.automaticTransitionFromOpenToHalfOpenEnabled=true 18 | 19 | -------------------------------------------------------------------------------- /inventory-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 18181 3 | servlet: 4 | contextPath: /${spring.application.name} 5 | spring: 6 | application: 7 | name: inventory-service 8 | testcontainers: 9 | beans: 10 | startup: parallel 11 | kafka: 12 | # bootstrap-servers: 127.0.0.1:9092 13 | consumer: 14 | key-deserializer: org.apache.kafka.common.serialization.LongDeserializer 15 | value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer 16 | properties: 17 | spring: 18 | json: 19 | trusted: 20 | packages: "com.example.common.dtos" 21 | producer: 22 | key-serializer: org.apache.kafka.common.serialization.LongSerializer 23 | value-serializer: org.springframework.kafka.support.serializer.JsonSerializer 24 | properties: 25 | spring: 26 | json: 27 | add: 28 | type: 29 | headers: true 30 | threads: 31 | virtual: 32 | enabled: true 33 | http: 34 | clients.imperative.factory: jdk 35 | main: 36 | web-application-type: servlet -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Raja Kolli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config-server/src/main/java/com/example/configserver/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | /* Licensed under Apache-2.0 2023 */ 2 | package com.example.configserver.config; 3 | 4 | import static org.springframework.security.config.Customizer.withDefaults; 5 | 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 10 | import org.springframework.security.web.SecurityFilterChain; 11 | 12 | @Configuration(proxyBeanMethods = false) 13 | public class SecurityConfig { 14 | 15 | @Bean 16 | SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 17 | http 18 | // Disable CRCF to allow POST to /encrypt and /decrypt endpoints 19 | .csrf(AbstractHttpConfigurer::disable) 20 | .authorizeHttpRequests(requests -> requests.anyRequest().authenticated()) 21 | .httpBasic(withDefaults()); 22 | return http.build(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/common/dtos/OrderDto.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.common.dtos; 8 | 9 | import jakarta.validation.constraints.NotEmpty; 10 | import jakarta.validation.constraints.Positive; 11 | import java.io.Serial; 12 | import java.io.Serializable; 13 | import java.util.List; 14 | import java.util.Objects; 15 | 16 | public record OrderDto( 17 | Long orderId, 18 | @Positive(message = "CustomerId should be positive") Long customerId, 19 | String status, 20 | String source, 21 | @NotEmpty(message = "Order without items not valid") List items) 22 | implements Serializable { 23 | 24 | @Serial private static final long serialVersionUID = 1L; 25 | 26 | public OrderDto withStatusAndSource(String status, String source) { 27 | if (Objects.equals(this.status(), status) && Objects.equals(this.source(), source)) { 28 | return this; 29 | } 30 | return new OrderDto(orderId(), customerId(), status, source, items()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/model/response/PagedResult.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.model.response; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import java.util.List; 11 | import org.springframework.data.domain.Page; 12 | 13 | public record PagedResult( 14 | List data, 15 | long totalElements, 16 | int pageNumber, 17 | int totalPages, 18 | @JsonProperty("isFirst") boolean isFirst, 19 | @JsonProperty("isLast") boolean isLast, 20 | @JsonProperty("hasNext") boolean hasNext, 21 | @JsonProperty("hasPrevious") boolean hasPrevious) { 22 | public PagedResult(Page page) { 23 | this( 24 | page.getContent(), 25 | page.getTotalElements(), 26 | page.getNumber() + 1, 27 | page.getTotalPages(), 28 | page.isFirst(), 29 | page.isLast(), 30 | page.hasNext(), 31 | page.hasPrevious()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/model/response/PagedResult.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.model.response; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import java.util.List; 11 | import org.springframework.data.domain.Page; 12 | 13 | public record PagedResult( 14 | List data, 15 | long totalElements, 16 | int pageNumber, 17 | int totalPages, 18 | @JsonProperty("isFirst") boolean isFirst, 19 | @JsonProperty("isLast") boolean isLast, 20 | @JsonProperty("hasNext") boolean hasNext, 21 | @JsonProperty("hasPrevious") boolean hasPrevious) { 22 | public PagedResult(Page page) { 23 | this( 24 | page.getContent(), 25 | page.getTotalElements(), 26 | page.getNumber() + 1, 27 | page.getTotalPages(), 28 | page.isFirst(), 29 | page.isLast(), 30 | page.hasNext(), 31 | page.hasPrevious()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.config; 2 | 3 | import org.jspecify.annotations.NonNull; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | @Configuration 10 | public class WebConfig { 11 | 12 | @Bean 13 | WebMvcConfigurer corsConfigurer() { 14 | return new WebMvcConfigurer() { 15 | @Override 16 | public void addCorsMappings(@NonNull CorsRegistry registry) { 17 | registry.addMapping("/api/**") 18 | .allowedOrigins("http://localhost:8080") 19 | .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") 20 | .allowedHeaders("*") 21 | .allowCredentials(true) 22 | .maxAge(3600); // Cache preflight response for 1 hour 23 | } 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/model/response/PagedResult.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.model.response; 8 | 9 | import com.fasterxml.jackson.annotation.JsonProperty; 10 | import java.util.List; 11 | import org.springframework.data.domain.Page; 12 | 13 | public record PagedResult( 14 | List data, 15 | long totalElements, 16 | int pageNumber, 17 | int totalPages, 18 | @JsonProperty("isFirst") boolean isFirst, 19 | @JsonProperty("isLast") boolean isLast, 20 | @JsonProperty("hasNext") boolean hasNext, 21 | @JsonProperty("hasPrevious") boolean hasPrevious) { 22 | public PagedResult(Page page) { 23 | this( 24 | page.getContent(), 25 | page.getTotalElements(), 26 | page.getNumber() + 1, 27 | page.getTotalPages(), 28 | page.isFirst(), 29 | page.isLast(), 30 | page.hasNext(), 31 | page.hasPrevious()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.example.microservices 8 | spring-boot-microservices-series-v2-parent 9 | 0.0.1-SNAPSHOT 10 | pom 11 | 12 | spring-boot-microservices-series-v2 13 | 14 | 15 | 21 16 | rajadilipkolli 17 | https://sonarcloud.io 18 | 19 | 20 | 21 | catalog-service 22 | inventory-service 23 | order-service 24 | payment-service 25 | config-server 26 | service-registry 27 | api-gateway 28 | retail-store-webapp 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/model/response/PagedResult.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2023 Raja Kolli. ***/ 2 | package com.example.paymentservice.model.response; 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import java.util.List; 6 | import org.springframework.data.domain.Page; 7 | 8 | public record PagedResult( 9 | List data, 10 | long totalElements, 11 | int pageNumber, 12 | int totalPages, 13 | @JsonProperty("isFirst") boolean isFirst, 14 | @JsonProperty("isLast") boolean isLast, 15 | @JsonProperty("hasNext") boolean hasNext, 16 | @JsonProperty("hasPrevious") boolean hasPrevious) { 17 | 18 | public PagedResult(List data, Page page) { 19 | this( 20 | data, 21 | page.getTotalElements(), 22 | page.getNumber() + 1, // for user page number starts from 1 23 | page.getTotalPages(), 24 | page.isFirst(), 25 | page.isLast(), 26 | page.hasNext(), 27 | page.hasPrevious()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/web/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.web.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.security.authentication.AnonymousAuthenticationToken; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | 10 | @Controller 11 | public class LoginController { 12 | 13 | private static final Logger logger = LoggerFactory.getLogger(LoginController.class); 14 | 15 | @GetMapping("/login") 16 | public String login(Authentication authentication) { 17 | logger.debug("Login page requested"); 18 | if (authentication != null 19 | && authentication.isAuthenticated() 20 | && !(authentication instanceof AnonymousAuthenticationToken)) { 21 | logger.debug("User {} already authenticated, redirecting to home", authentication.getName()); 22 | return "redirect:/"; 23 | } 24 | return "login"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /payment-service/src/test/java/com/example/paymentservice/common/NonSQLContainerConfig.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. ***/ 2 | package com.example.paymentservice.common; 3 | 4 | import java.time.Duration; 5 | import org.springframework.boot.test.context.TestConfiguration; 6 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 7 | import org.springframework.context.annotation.Bean; 8 | import org.testcontainers.grafana.LgtmStackContainer; 9 | import org.testcontainers.kafka.KafkaContainer; 10 | import org.testcontainers.utility.DockerImageName; 11 | 12 | @TestConfiguration(proxyBeanMethods = false) 13 | public class NonSQLContainerConfig { 14 | 15 | @Bean 16 | @ServiceConnection 17 | LgtmStackContainer lgtmContainer() { 18 | return new LgtmStackContainer(DockerImageName.parse("grafana/otel-lgtm:0.12.0")) 19 | .withStartupTimeout(Duration.ofMinutes(2)); 20 | } 21 | 22 | @Bean 23 | @ServiceConnection 24 | KafkaContainer kafkaContainer() { 25 | return new KafkaContainer(DockerImageName.parse("apache/kafka-native").withTag("4.1.1")); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /catalog-service/src/test/java/com/example/catalogservice/config/TestKafkaListenerConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.config; 8 | 9 | import java.util.concurrent.CountDownLatch; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.boot.test.context.TestConfiguration; 13 | import org.springframework.kafka.annotation.KafkaListener; 14 | import org.springframework.messaging.handler.annotation.Payload; 15 | 16 | @TestConfiguration(proxyBeanMethods = false) 17 | public class TestKafkaListenerConfig { 18 | 19 | private static final Logger log = LoggerFactory.getLogger(TestKafkaListenerConfig.class); 20 | 21 | private final CountDownLatch latch = new CountDownLatch(10); 22 | 23 | @KafkaListener(id = "products", topics = "productTopic", groupId = "product") 24 | public void onSaveProductEvent(@Payload String productDto) { 25 | log.info("Received Product: {}", productDto); 26 | latch.countDown(); 27 | } 28 | 29 | public CountDownLatch getLatch() { 30 | return latch; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /api-gateway/src/test/resources/com/example/api/gateway/config/CircuitBreakerConfigurationIntegrationTest/circuit-breaker.json: -------------------------------------------------------------------------------- 1 | { 2 | "mappings": [ 3 | { 4 | "request": { 5 | "urlPath": "/failing-service", 6 | "method": "GET" 7 | }, 8 | "response": { 9 | "status": 500, 10 | "headers": { 11 | "Content-Type": "application/json" 12 | }, 13 | "jsonBody": { 14 | "error": "Internal Server Error" 15 | } 16 | } 17 | }, 18 | { 19 | "request": { 20 | "urlPath": "/slow-service", 21 | "method": "GET" 22 | }, 23 | "response": { 24 | "status": 200, 25 | "headers": { 26 | "Content-Type": "application/json" 27 | }, 28 | "fixedDelayMilliseconds": 6000, 29 | "jsonBody": { 30 | "message": "Delayed Response" 31 | } 32 | } 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/config/Initializer.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.config; 8 | 9 | import com.example.orderservice.services.OrderService; 10 | import org.jobrunr.scheduling.BackgroundJob; 11 | import org.jobrunr.scheduling.cron.Cron; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.boot.CommandLineRunner; 15 | import org.springframework.stereotype.Component; 16 | 17 | @Component 18 | class Initializer implements CommandLineRunner { 19 | 20 | private final Logger log = LoggerFactory.getLogger(this.getClass()); 21 | 22 | private final OrderService orderService; 23 | 24 | public Initializer(OrderService orderService) { 25 | this.orderService = orderService; 26 | } 27 | 28 | @Override 29 | public void run(String... args) { 30 | log.info("Running Initializer....."); 31 | BackgroundJob.scheduleRecurrently(Cron.minutely(), orderService::retryNewOrders); 32 | log.info("Completed Scheduling Recurrently BackgroundJob with 2 retries"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /api-gateway/src/test/java/com/example/api/gateway/APIGatewayApplicationIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | import com.example.api.gateway.config.AbstractIntegrationTest; 12 | import org.junit.jupiter.api.Test; 13 | 14 | class APIGatewayApplicationIntegrationTest extends AbstractIntegrationTest { 15 | 16 | @Test 17 | void actuatorHealth() { 18 | webTestClient 19 | .get() 20 | .uri("/actuator/health") 21 | .exchange() 22 | .expectStatus() 23 | .isOk() 24 | .expectBody(String.class) 25 | .consumeWith( 26 | res -> 27 | assertThat(res.getResponseBody()) 28 | .isEqualToIgnoringWhitespace( 29 | """ 30 | {"groups":["liveness","readiness"],"status":"UP"} 31 | """)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/model/response/ProductResponse.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.model.response; 8 | 9 | import com.fasterxml.jackson.annotation.JsonFormat; 10 | import com.fasterxml.jackson.annotation.JsonIgnore; 11 | 12 | public record ProductResponse( 13 | Long id, 14 | String productCode, 15 | String productName, 16 | String description, 17 | String imageUrl, 18 | @JsonFormat(shape = JsonFormat.Shape.NUMBER_FLOAT, pattern = "0.00") double price, 19 | boolean inStock) { 20 | 21 | @JsonIgnore 22 | public ProductResponse withInStock(final boolean inStock) { 23 | return this.inStock == inStock 24 | ? this 25 | : new ProductResponse( 26 | this.id, 27 | this.productCode, 28 | this.productName, 29 | this.description, 30 | this.imageUrl, 31 | this.price, 32 | inStock); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/resources/templates/fragments/inventoryPagination.html: -------------------------------------------------------------------------------- 1 |
2 | 23 |
-------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/clients/order/OrderServiceClient.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.order; 2 | 3 | import com.example.retailstore.webapp.clients.PagedResult; 4 | import jakarta.validation.Valid; 5 | import java.util.Map; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RequestBody; 8 | import org.springframework.web.bind.annotation.RequestHeader; 9 | import org.springframework.web.service.annotation.GetExchange; 10 | import org.springframework.web.service.annotation.HttpExchange; 11 | import org.springframework.web.service.annotation.PostExchange; 12 | 13 | @HttpExchange("/order-service") 14 | public interface OrderServiceClient { 15 | 16 | @GetExchange("/api/orders") 17 | PagedResult getOrders(@RequestHeader Map headers); 18 | 19 | @GetExchange("/api/orders/{id}") 20 | OrderResponse getOrder(@RequestHeader Map headers, @PathVariable String id); 21 | 22 | @PostExchange("/api/orders") 23 | OrderConfirmationDTO createOrder( 24 | @RequestHeader Map headers, @Valid @RequestBody OrderRequestExternal orderRequestExternal); 25 | } 26 | -------------------------------------------------------------------------------- /api-gateway/src/test/java/com/example/api/gateway/config/ContainerConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.api.gateway.config; 8 | 9 | import com.redis.testcontainers.RedisContainer; 10 | import java.time.Duration; 11 | import org.springframework.boot.test.context.TestConfiguration; 12 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 13 | import org.springframework.context.annotation.Bean; 14 | import org.testcontainers.grafana.LgtmStackContainer; 15 | import org.testcontainers.utility.DockerImageName; 16 | 17 | @TestConfiguration(proxyBeanMethods = false) 18 | public class ContainerConfig { 19 | 20 | @Bean 21 | @ServiceConnection 22 | LgtmStackContainer lgtmContainer() { 23 | return new LgtmStackContainer(DockerImageName.parse("grafana/otel-lgtm:0.12.0")) 24 | .withStartupTimeout(Duration.ofMinutes(2)); 25 | } 26 | 27 | @Bean 28 | @ServiceConnection(name = "redis") 29 | RedisContainer redisContainer() { 30 | return new RedisContainer(DockerImageName.parse("redis").withTag("8.4.0-alpine")) 31 | .withReuse(true); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/mapper/ProductMapper.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.mapper; 8 | 9 | import com.example.catalogservice.entities.Product; 10 | import com.example.catalogservice.model.payload.ProductDto; 11 | import com.example.catalogservice.model.request.ProductRequest; 12 | import com.example.catalogservice.model.response.ProductResponse; 13 | import org.mapstruct.Mapper; 14 | import org.mapstruct.Mapping; 15 | import org.mapstruct.MappingTarget; 16 | 17 | @Mapper(componentModel = "spring") 18 | public interface ProductMapper { 19 | 20 | @Mapping(target = "inStock", ignore = true) 21 | @Mapping(target = "withInStock", ignore = true) 22 | ProductResponse toProductResponse(Product product); 23 | 24 | @Mapping(target = "id", ignore = true) 25 | Product toEntity(ProductRequest productRequest); 26 | 27 | @Mapping(target = "code", source = "productCode") 28 | ProductDto toProductDto(ProductRequest productRequest); 29 | 30 | @Mapping(target = "id", ignore = true) 31 | void mapProductWithRequest(ProductRequest productRequest, @MappingTarget Product product); 32 | } 33 | -------------------------------------------------------------------------------- /inventory-service/src/test/java/com/example/inventoryservice/common/NonSQLContainersConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.common; 8 | 9 | import java.time.Duration; 10 | import org.springframework.boot.test.context.TestConfiguration; 11 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 12 | import org.springframework.context.annotation.Bean; 13 | import org.testcontainers.grafana.LgtmStackContainer; 14 | import org.testcontainers.kafka.KafkaContainer; 15 | import org.testcontainers.utility.DockerImageName; 16 | 17 | @TestConfiguration(proxyBeanMethods = false) 18 | public class NonSQLContainersConfig { 19 | 20 | @Bean 21 | @ServiceConnection 22 | LgtmStackContainer lgtmContainer() { 23 | return new LgtmStackContainer(DockerImageName.parse("grafana/otel-lgtm:0.12.0")) 24 | .withStartupTimeout(Duration.ofMinutes(2)); 25 | } 26 | 27 | @Bean 28 | @ServiceConnection 29 | KafkaContainer kafkaContainer() { 30 | return new KafkaContainer(DockerImageName.parse("apache/kafka-native").withTag("4.1.1")) 31 | .withReuse(true); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /inventory-service/src/test/java/com/example/inventoryservice/config/TestStockOrderListenerConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.config; 8 | 9 | import com.example.common.dtos.OrderDto; 10 | import java.util.concurrent.CountDownLatch; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.boot.test.context.TestConfiguration; 14 | import org.springframework.kafka.annotation.KafkaListener; 15 | import org.springframework.messaging.handler.annotation.Payload; 16 | 17 | @TestConfiguration(proxyBeanMethods = false) 18 | public class TestStockOrderListenerConfig { 19 | 20 | private static final Logger log = LoggerFactory.getLogger(TestStockOrderListenerConfig.class); 21 | 22 | private final CountDownLatch countDownLatch = new CountDownLatch(1); 23 | 24 | public CountDownLatch getCountDownLatch() { 25 | return countDownLatch; 26 | } 27 | 28 | @KafkaListener(id = "stocks", topics = "stock-orders", groupId = "inventory") 29 | public void onOrderEvent(@Payload OrderDto orderDto) { 30 | log.info("Received Order: {}", orderDto); 31 | countDownLatch.countDown(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/java/com/example/retailstore/webapp/web/model/request/RegistrationRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.web.model.request; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.Pattern; 6 | import jakarta.validation.constraints.Size; 7 | 8 | public record RegistrationRequest( 9 | @NotBlank @Pattern( 10 | regexp = "^[a-zA-Z0-9._-]{3,20}$", 11 | message = 12 | "Username must be 3-20 characters and contain only letters, numbers, dots, underscores or hyphens") 13 | String username, 14 | @NotBlank @Email String email, 15 | @NotBlank @Size(min = 1, max = 50) String firstName, 16 | @NotBlank @Size(min = 1, max = 50) String lastName, 17 | @NotBlank @Pattern( 18 | regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,}$", 19 | message = 20 | "Password must be at least 8 characters and include uppercase, lowercase, numbers and special characters") 21 | String password, 22 | Long phone, 23 | String address) {} 24 | -------------------------------------------------------------------------------- /inventory-service/src/test/java/com/example/inventoryservice/repositories/InventoryRepositoryTest.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.repositories; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | import com.example.inventoryservice.common.SQLContainersConfig; 12 | import com.zaxxer.hikari.HikariDataSource; 13 | import javax.sql.DataSource; 14 | import org.junit.jupiter.api.Test; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest; 17 | import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; 18 | import org.springframework.context.annotation.Import; 19 | 20 | @DataJpaTest( 21 | properties = { 22 | "spring.jpa.hibernate.ddl-auto=validate", 23 | "spring.cloud.config.enabled=false" 24 | }) 25 | @Import(SQLContainersConfig.class) 26 | @AutoConfigureTestDatabase 27 | class InventoryRepositoryTest { 28 | 29 | @Autowired private DataSource datasource; 30 | 31 | @Test 32 | void dataSource() { 33 | assertThat(datasource).isNotNull().isInstanceOf(HikariDataSource.class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /payment-service/README.md: -------------------------------------------------------------------------------- 1 | # payment-service 2 | 3 | ### Run tests 4 | `$ ./mvnw clean verify` 5 | 6 | ### Run locally 7 | 8 | ```shell 9 | docker-compose -f docker/docker-compose.yml up -d 10 | ./mvnw spring-boot:run -Dspring-boot.run.profiles=local 11 | ``` 12 | 13 | ### Using Testcontainers at Development Time 14 | You can run `TestPaymentApplication.java` from your IDE directly. 15 | You can also run the application using Maven as follows: 16 | 17 | ```shell 18 | ./mvnw spotless:apply spring-boot:test-run 19 | ``` 20 | 21 | ### Running only this Service Locally - Tips 22 | 23 | To run only the Payment Service locally with clean logs, you can follow these steps: 24 | 25 | 26 | 1. start the Kafka and Postgres servers by using below command(You should be inside appropriate directory and docker setup should be done :- ) : 27 | ```shell 28 | docker compose up kafka postgres 29 | ``` 30 | 31 | 2. In IntelIj Open Modify Run Configuration from Main class: 32 | `com.example.paymentservice.TestPaymentApplication` 33 | Set the Environment variable value to 34 | ```text 35 | SPRING_PROFILES_ACTIVE=local 36 | ``` 37 | 38 | 39 | ### Useful Links 40 | * Swagger UI: http://localhost:18085/payment-service/swagger-ui.html 41 | * Actuator Endpoint: http://localhost:18085/payment-service/actuator 42 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/resources/templates/fragments/productsPagination.html: -------------------------------------------------------------------------------- 1 |
2 | 23 |
-------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/common/dtos/OrderDto.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. ***/ 2 | package com.example.common.dtos; 3 | 4 | import jakarta.validation.constraints.NotEmpty; 5 | import jakarta.validation.constraints.Positive; 6 | import java.io.Serial; 7 | import java.io.Serializable; 8 | import java.util.List; 9 | import java.util.Objects; 10 | 11 | public record OrderDto( 12 | Long orderId, 13 | @Positive(message = "CustomerId should be positive") Long customerId, 14 | String status, 15 | String source, 16 | @NotEmpty(message = "Order without items not valid") List items) 17 | implements Serializable { 18 | 19 | @Serial private static final long serialVersionUID = 1L; 20 | 21 | public OrderDto withSource(String source) { 22 | if (Objects.equals(this.source(), source)) { 23 | return this; 24 | } 25 | return new OrderDto(orderId(), customerId(), status(), source, items()); 26 | } 27 | 28 | public OrderDto withStatus(String status) { 29 | if (Objects.equals(this.status(), status)) { 30 | return this; 31 | } 32 | return new OrderDto(orderId(), customerId(), status, source(), items()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /inventory-service/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/config/HttpClientConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2022-2024 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.config; 8 | 9 | import com.example.orderservice.services.CatalogServiceProxy; 10 | import io.micrometer.observation.ObservationRegistry; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer; 14 | import org.springframework.web.service.registry.ImportHttpServices; 15 | 16 | @Configuration(proxyBeanMethods = false) 17 | @ImportHttpServices(CatalogServiceProxy.class) 18 | class HttpClientConfig { 19 | 20 | @Bean 21 | RestClientHttpServiceGroupConfigurer groupConfigurer( 22 | ObservationRegistry observationRegistry, ApplicationProperties applicationProperties) { 23 | return groups -> 24 | groups.forEachClient( 25 | (_, builder) -> 26 | builder.baseUrl(applicationProperties.catalogServiceUrl()) 27 | .observationRegistry(observationRegistry) 28 | .build()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /order-service/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /inventory-service/README.md: -------------------------------------------------------------------------------- 1 | # inventory-service 2 | 3 | ### Run tests 4 | `$ ./mvnw clean verify` 5 | 6 | ### Run locally 7 | 8 | ```shell 9 | docker-compose -f docker/docker-compose.yml up -d 10 | ./mvnw spring-boot:run -Dspring-boot.run.profiles=local 11 | ``` 12 | 13 | ### Using Testcontainers at Development Time 14 | You can run `TestInventoryApplication.java` from your IDE directly. 15 | You can also run the application using Maven as follows: 16 | 17 | ```shell 18 | ./mvnw spotless:apply spring-boot:test-run 19 | ``` 20 | 21 | ### Running only this Service Locally - Tips 22 | 23 | To run only the Inventory Service locally with clean logs, you can follow these steps: 24 | 25 | 26 | 1. start the Kafka and Postgres servers by using below command(You should be inside appropriate directory and docker setup should be done :- ) : 27 | ```shell 28 | docker compose up kafka postgres zipkin-server 29 | ``` 30 | 31 | 2. In IntelIj Open Modify Run Configuration from Main class: 32 | `com.example.inventoryservice.InventoryServiceApplication` 33 | Set the Environment variable value to 34 | ```text 35 | SPRING_PROFILES_ACTIVE=local 36 | ``` 37 | 38 | 39 | ### Useful Links 40 | * Swagger UI: http://localhost:18181/inventory-service/swagger-ui.html 41 | * Actuator Endpoint: http://localhost:18181/inventory-service/actuator 42 | -------------------------------------------------------------------------------- /order-service/src/test/java/com/example/orderservice/common/ContainersConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.common; 8 | 9 | import java.time.Duration; 10 | import org.springframework.boot.devtools.restart.RestartScope; 11 | import org.springframework.boot.test.context.TestConfiguration; 12 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 13 | import org.springframework.context.annotation.Bean; 14 | import org.testcontainers.grafana.LgtmStackContainer; 15 | import org.testcontainers.kafka.KafkaContainer; 16 | import org.testcontainers.utility.DockerImageName; 17 | 18 | @TestConfiguration(proxyBeanMethods = false) 19 | public class ContainersConfig { 20 | 21 | @Bean 22 | @ServiceConnection 23 | @RestartScope 24 | KafkaContainer kafkaContainer() { 25 | return new KafkaContainer(DockerImageName.parse("apache/kafka-native").withTag("4.1.1")) 26 | .withReuse(true); 27 | } 28 | 29 | @Bean 30 | @ServiceConnection 31 | LgtmStackContainer lgtmContainer() { 32 | return new LgtmStackContainer(DockerImageName.parse("grafana/otel-lgtm:0.12.0")) 33 | .withStartupTimeout(Duration.ofMinutes(2)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/config/WebFluxConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.config; 8 | 9 | import org.jspecify.annotations.NonNull; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.web.reactive.config.CorsRegistry; 12 | import org.springframework.web.reactive.config.WebFluxConfigurer; 13 | 14 | @Configuration(proxyBeanMethods = false) 15 | public class WebFluxConfig implements WebFluxConfigurer { 16 | 17 | private final ApplicationProperties properties; 18 | 19 | public WebFluxConfig(ApplicationProperties properties) { 20 | this.properties = properties; 21 | } 22 | 23 | @Override 24 | public void addCorsMappings(@NonNull CorsRegistry registry) { 25 | ApplicationProperties.Cors cors = properties.cors(); 26 | registry.addMapping(cors.getPathPattern()) 27 | .allowedMethods(cors.getAllowedMethods().split(",")) 28 | .allowedHeaders(cors.getAllowedHeaders().split(",")) 29 | .allowedOriginPatterns(cors.getAllowedOriginPatterns().split(",")) 30 | .allowCredentials(cors.isAllowCredentials()) 31 | .maxAge(3600); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /payment-service/src/main/java/com/example/paymentservice/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | /*** Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. ***/ 2 | package com.example.paymentservice.config; 3 | 4 | import org.jspecify.annotations.NonNull; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | @Configuration(proxyBeanMethods = false) 10 | class WebMvcConfig implements WebMvcConfigurer { 11 | 12 | private final ApplicationProperties properties; 13 | 14 | public WebMvcConfig(ApplicationProperties properties) { 15 | this.properties = properties; 16 | } 17 | 18 | @Override 19 | public void addCorsMappings(@NonNull CorsRegistry registry) { 20 | ApplicationProperties.Cors cors = properties.getCors(); 21 | registry.addMapping(cors.getPathPattern()) 22 | .allowedMethods(cors.getAllowedMethods().split(",")) 23 | .allowedHeaders(cors.getAllowedHeaders().split(",")) 24 | .allowedOriginPatterns(cors.getAllowedOriginPatterns().split(",")) 25 | .allowCredentials(cors.isAllowCredentials()) 26 | .maxAge(3600); // Cache preflight response for 1 hour 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /catalog-service/src/main/java/com/example/catalogservice/exception/ProductAlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2022-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.exception; 8 | 9 | import java.net.URI; 10 | import java.time.Instant; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ProblemDetail; 13 | import org.springframework.web.ErrorResponseException; 14 | 15 | public class ProductAlreadyExistsException extends ErrorResponseException { 16 | public ProductAlreadyExistsException(String productCode) { 17 | super( 18 | HttpStatus.CONFLICT, 19 | asProblemDetail("Product with code " + productCode + " already exists"), 20 | null); 21 | } 22 | 23 | private static ProblemDetail asProblemDetail(String message) { 24 | ProblemDetail problemDetail = 25 | ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, message); 26 | problemDetail.setTitle("Product Already Exists"); 27 | problemDetail.setType(URI.create("https://api.microservices.com/errors/already-exists")); 28 | problemDetail.setProperty("errorCategory", "Generic"); 29 | problemDetail.setProperty("timestamp", Instant.now()); 30 | return problemDetail; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /payment-service/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | Login - RetailStore 9 | 22 | 23 | 24 |
25 | 36 |
37 | 38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /config-server/src/main/resources/config-repository/inventory-service.properties: -------------------------------------------------------------------------------- 1 | 2 | resilience4j.retry.instances.inventory-api.maxAttempts=5 3 | resilience4j.retry.instances.inventory-api.waitDuration=1s 4 | resilience4j.retry.instances.inventory-api.enableExponentialBackoff=true 5 | resilience4j.ratelimiter.instances.default.limitForPeriod=2 6 | resilience4j.ratelimiter.instances.default.limitRefreshPeriod=10s 7 | resilience4j.bulkhead.instances.default.maxConcurrentCalls=10 8 | resilience4j.bulkhead.instances.inventory-api.maxConcurrentCalls=10 9 | 10 | # Expose circuitbreaker and ratelimiter health indicators via actuator 11 | management.health.circuitbreakers.enabled=true 12 | management.health.ratelimiters.enabled=true 13 | 14 | # Ensure circuitbreaker health indicator registration for default instance 15 | resilience4j.circuitbreaker.configs.default.registerHealthIndicator=true 16 | resilience4j.circuitbreaker.configs.default.slidingWindowSize=10 17 | resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=5 18 | resilience4j.circuitbreaker.configs.default.permittedNumberOfCallsInHalfOpenState=3 19 | resilience4j.circuitbreaker.configs.default.automaticTransitionFromOpenToHalfOpenEnabled=true 20 | resilience4j.circuitbreaker.configs.default.waitDurationInOpenState=5s 21 | resilience4j.circuitbreaker.configs.default.failureRateThreshold=50 22 | # resilience4j.circuitbreaker.instances.default.baseConfig=default 23 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/java 3 | { 4 | "name": "Java", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/java:25", 7 | 8 | "features": { 9 | "ghcr.io/devcontainers/features/java:1": { 10 | "version": "none", 11 | "installMaven": "true", 12 | "installGradle": "false" 13 | }, 14 | "ghcr.io/devcontainers/features/docker-in-docker:2": { 15 | "version": "latest", 16 | "enableNonRootDocker": "true", 17 | "moby": "false" 18 | } 19 | }, 20 | "customizations": { 21 | "vscode": { 22 | "extensions": [ 23 | "vscjava.vscode-java-pack", 24 | "vmware.vscode-boot-dev-pack", 25 | "ms-vscode-remote.remote-containers" 26 | ] 27 | } 28 | }, 29 | 30 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 31 | "forwardPorts": [9411,8765,8761], 32 | 33 | // Use 'postCreateCommand' to run commands after the container is created. 34 | "postCreateCommand": "./mvnw clean compile -DskipTests" 35 | 36 | // Configure tool-specific properties. 37 | // "customizations": {}, 38 | 39 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 40 | // "remoteUser": "root" 41 | } 42 | -------------------------------------------------------------------------------- /inventory-service/src/main/java/com/example/inventoryservice/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.inventoryservice.config; 8 | 9 | import com.example.inventoryservice.config.ApplicationProperties.Cors; 10 | import org.jspecify.annotations.NonNull; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 13 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 14 | 15 | @Configuration(proxyBeanMethods = false) 16 | class WebMvcConfig implements WebMvcConfigurer { 17 | 18 | private final ApplicationProperties properties; 19 | 20 | WebMvcConfig(ApplicationProperties properties) { 21 | this.properties = properties; 22 | } 23 | 24 | @Override 25 | public void addCorsMappings(@NonNull CorsRegistry registry) { 26 | Cors cors = properties.getCors(); 27 | registry.addMapping(cors.getPathPattern()) 28 | .allowedMethods(cors.getAllowedMethods().split(",")) 29 | .allowedHeaders(cors.getAllowedHeaders().split(",")) 30 | .allowedOriginPatterns(cors.getAllowedOriginPatterns().split(",")) 31 | .allowCredentials(cors.isAllowCredentials()) 32 | .maxAge(3600); // Cache preflight response for 1 hour 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /order-service/src/main/java/com/example/orderservice/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2021-2025 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.orderservice.config; 8 | 9 | import org.jspecify.annotations.NonNull; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.scheduling.annotation.EnableAsync; 12 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 13 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 14 | 15 | @Configuration(proxyBeanMethods = false) 16 | @EnableAsync 17 | class WebMvcConfig implements WebMvcConfigurer { 18 | 19 | private final ApplicationProperties properties; 20 | 21 | public WebMvcConfig(ApplicationProperties properties) { 22 | this.properties = properties; 23 | } 24 | 25 | @Override 26 | public void addCorsMappings(@NonNull CorsRegistry registry) { 27 | Cors cors = properties.cors(); 28 | registry.addMapping(cors.getPathPattern()) 29 | .allowedMethods(cors.getAllowedMethods().split(",")) 30 | .allowedHeaders(cors.getAllowedHeaders().split(",")) 31 | .allowedOriginPatterns(cors.getAllowedOriginPatterns().split(",")) 32 | .allowCredentials(cors.isAllowCredentials()) 33 | .maxAge(3600); // Cache preflight response for 1 hour 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /catalog-service/README.md: -------------------------------------------------------------------------------- 1 | # catalogservice 2 | 3 | ### Run locally 4 | 5 | ```shell 6 | docker-compose -f docker/docker-compose.yml up -d 7 | ./mvnw spring-boot:run -Dspring-boot.run.profiles=local 8 | ``` 9 | 10 | ### Using Testcontainers at Development Time 11 | You can run `TestCatalogServiceApplication.java` from your IDE directly. 12 | You can also run the application using Maven as follows: 13 | 14 | ```shell 15 | ./mvnw spotless:apply spring-boot:test-run 16 | ``` 17 | 18 | ### Run tests 19 | 20 | ```shell 21 | ./mvnw clean verify 22 | ``` 23 | 24 | ### Running only this Service Locally - Tips 25 | 26 | To run only the Catalog Service locally with clean logs, you can follow these steps: 27 | 28 | 29 | 1. start the Kafka and Postgres servers by using below command(You should be inside appropriate directory and docker setup should be done :-) 30 | ```shell 31 | docker compose up kafka postgres 32 | ``` 33 | 2. In IntelIj Open Modify Run Configuration from Main class: 34 | `com.example.catalogservice.CatalogServiceApplication` 35 | Set the Environment variable value to 36 | ```text 37 | SPRING_PROFILES_ACTIVE=local 38 | ``` 39 | 40 | 41 | ### Useful Links 42 | * Swagger UI: http://localhost:18080/catalog-service/swagger-ui.html 43 | * Actuator Endpoint: http://localhost:18080/catalog-service/actuator 44 | * Actuator Health Endpoint : http://localhost:18080/catalog-service/actuator/health 45 | * Zipkin Server: http://localhost:9411 46 | -------------------------------------------------------------------------------- /catalog-service/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/gatling-tests.yml: -------------------------------------------------------------------------------- 1 | name: gatling-tests 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - "gatling-tests/**" 8 | branches: [main] 9 | pull_request: 10 | paths: 11 | - "gatling-tests/**" 12 | types: 13 | - opened 14 | - synchronize 15 | - reopened 16 | 17 | jobs: 18 | build: 19 | name: Run Unit & Integration Tests with jdk ${{ matrix.java }} 20 | runs-on: ubuntu-latest 21 | defaults: 22 | run: 23 | working-directory: "gatling-tests" 24 | strategy: 25 | matrix: 26 | java: ["25"] 27 | steps: 28 | - uses: actions/checkout@v6 29 | with: 30 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 31 | 32 | - name: Set up JDK ${{ matrix.java }} 33 | uses: actions/setup-java@v5.1.0 34 | with: 35 | java-version: ${{ matrix.java }} 36 | distribution: "temurin" 37 | cache: "maven" 38 | 39 | - name: Start up softwares via Docker Compose 40 | run: | 41 | pwd 42 | cd .. 43 | cd deployment 44 | docker compose up -d postgresql redis zipkin-server kafka config-server naming-server api-gateway catalog-service inventory-service order-service payment-service 45 | sleep 60 46 | docker ps -a 47 | 48 | - name: Build and analyze 49 | run: ./mvnw clean gatling:test 50 | -------------------------------------------------------------------------------- /retail-store-webapp/src/test/java/com/example/retailstore/webapp/clients/order/CreateOrderRequestTest.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.clients.order; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.mockito.Mockito.mock; 5 | 6 | import com.example.retailstore.webapp.clients.customer.CustomerRequest; 7 | import java.math.BigDecimal; 8 | import java.util.List; 9 | import org.junit.jupiter.api.Test; 10 | 11 | class CreateOrderRequestTest { 12 | 13 | @Test 14 | void testWithCustomerId() { 15 | // Setup 16 | Address deliveryAddress = mock(Address.class); 17 | CustomerRequest customer = mock(CustomerRequest.class); 18 | List items = List.of(new OrderItemRequest("ABC123", 2, new BigDecimal("199.99"))); 19 | 20 | CreateOrderRequest request = new CreateOrderRequest(items, customer, deliveryAddress); 21 | 22 | // Execution 23 | OrderRequestExternal result = request.withCustomerId(123L); 24 | 25 | // Assertions 26 | assertEquals(123L, result.customerId()); 27 | assertEquals(1, result.items().size()); 28 | assertEquals("ABC123", result.items().getFirst().productCode()); 29 | assertEquals(2, result.items().getFirst().quantity()); 30 | assertEquals(new BigDecimal("199.99"), result.items().getFirst().productPrice()); 31 | assertEquals(deliveryAddress, result.deliveryAddress()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /retail-store-webapp/src/main/resources/templates/orders.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 |
10 |
11 |

All Orders

12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 |
Order IDStatusCreated Date
32 |
33 |
34 |
35 |
36 | 37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /retail-store-webapp/src/test/java/com/example/retailstore/webapp/common/AbstractIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.example.retailstore.webapp.common; 2 | 3 | import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; 4 | 5 | import com.github.tomakehurst.wiremock.WireMockServer; 6 | import dasniko.testcontainers.keycloak.KeycloakContainer; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; 10 | import org.springframework.test.web.servlet.assertj.MockMvcTester; 11 | import org.wiremock.spring.ConfigureWireMock; 12 | import org.wiremock.spring.EnableWireMock; 13 | import org.wiremock.spring.InjectWireMock; 14 | import tools.jackson.databind.json.JsonMapper; 15 | 16 | @SpringBootTest( 17 | webEnvironment = RANDOM_PORT, 18 | classes = {ContainerConfig.class}) 19 | @AutoConfigureMockMvc 20 | @EnableWireMock({@ConfigureWireMock(name = "gateway-service", baseUrlProperties = "retailstore.api-gateway-url")}) 21 | public abstract class AbstractIntegrationTest { 22 | 23 | @Autowired 24 | protected JsonMapper jsonMapper; 25 | 26 | @Autowired 27 | protected MockMvcTester mockMvcTester; 28 | 29 | @Autowired 30 | protected KeycloakContainer keycloakContainer; 31 | 32 | @InjectWireMock("gateway-service") 33 | protected WireMockServer gatewayServiceMock; 34 | } 35 | -------------------------------------------------------------------------------- /config-server/ReadMe.md: -------------------------------------------------------------------------------- 1 | # CONFIGURATION SERVER 2 | 3 | Configuration server in microservices is a centralized service that stores and manages the configuration data for all the microservices in an application. This configuration data includes settings such as database connections, service dependencies, and environment-specific variables. 4 | 5 | The configuration server is typically accessed via a REST API and can be accessed by all the microservices in the application. This allows for easy management and modification of the configuration data without the need to update and redeploy individual microservices. 6 | 7 | The configuration server also helps to decouple the microservices from the environment in which they are deployed. This means that the microservices can be easily moved between different environments (e.g. development, staging, production) without the need to change the configuration data within each microservice. 8 | 9 | How to access the values of particular project 10 | 11 | http://localhost:8888/{projectname}/{profile} 12 | 13 | Ex: http://localhost:8888/catalog-service/default 14 | 15 | 16 | ### Run tests 17 | 18 | ```shell 19 | ./mvnw clean verify 20 | ``` 21 | 22 | ### Run locally 23 | 24 | ```shell 25 | ./mvnw spring-boot:run 26 | ``` 27 | 28 | ## Actuator Endpoints 29 | - http://localhost:8888/actuator/health 30 | - http://localhost:8888/actuator/info (done using maven build plugin and git info plugin) 31 | - http://localhost:8888/actuator/health/configServer 32 | -------------------------------------------------------------------------------- /catalog-service/src/test/java/com/example/catalogservice/common/AbstractCircuitBreakerTest.java: -------------------------------------------------------------------------------- 1 | /*** 2 |

3 | Licensed under MIT License Copyright (c) 2023 Raja Kolli. 4 |

5 | ***/ 6 | 7 | package com.example.catalogservice.common; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | import io.github.resilience4j.circuitbreaker.CircuitBreaker; 12 | 13 | public abstract class AbstractCircuitBreakerTest extends AbstractIntegrationTest { 14 | 15 | protected void checkHealthStatus(String circuitBreakerName, CircuitBreaker.State state) { 16 | CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(circuitBreakerName); 17 | assertThat(circuitBreaker.getState()).isEqualTo(state); 18 | } 19 | 20 | protected void transitionToOpenState(String circuitBreakerName) { 21 | CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(circuitBreakerName); 22 | circuitBreaker.transitionToOpenState(); 23 | } 24 | 25 | protected void transitionToClosedState(String circuitBreakerName) { 26 | CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(circuitBreakerName); 27 | circuitBreaker.transitionToClosedState(); 28 | } 29 | 30 | protected void transitionToHalfOpenState(String circuitBreakerName) { 31 | CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(circuitBreakerName); 32 | circuitBreaker.transitionToHalfOpenState(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /api-gateway/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /deployment/config/tempo/tempo.yml: -------------------------------------------------------------------------------- 1 | server: 2 | http_listen_port: 3100 3 | http_listen_address: 0.0.0.0 4 | http_server_read_timeout: 30s 5 | http_server_write_timeout: 30s 6 | 7 | distributor: 8 | receivers: 9 | otlp: 10 | protocols: 11 | grpc: 12 | http: 13 | jaeger: 14 | protocols: 15 | thrift_http: 16 | grpc: 17 | thrift_binary: 18 | thrift_compact: 19 | zipkin: 20 | endpoint: 0.0.0.0:9411 21 | 22 | ingester: 23 | trace_idle_period: 10s 24 | max_block_bytes: 1_000_000 25 | max_block_duration: 5m 26 | 27 | querier: 28 | search: 29 | query_timeout: 30s 30 | 31 | metrics_generator: 32 | registry: 33 | external_labels: 34 | source: tempo 35 | storage: 36 | path: /tmp/tempo/generator/wal 37 | remote_write: 38 | - url: http://host.docker.internal:9090/api/v1/write 39 | send_exemplars: true 40 | traces_storage: 41 | path: /tmp/tempo/generator/traces 42 | processor: 43 | local_blocks: 44 | flush_to_storage: true 45 | filter_server_spans: false 46 | 47 | query_frontend: 48 | metrics: 49 | concurrent_jobs: 8 50 | target_bytes_per_job: 1.25e+09 51 | max_duration: 3h 52 | 53 | storage: 54 | trace: 55 | backend: local 56 | wal: 57 | path: /tmp/tempo/wal 58 | local: 59 | path: /tmp/tempo/blocks 60 | 61 | overrides: 62 | metrics_generator_processors: [service-graphs, span-metrics, local-blocks] 63 | 64 | usage_report: 65 | reporting_enabled: false 66 | --------------------------------------------------------------------------------