├── .DS_Store ├── .github ├── FUNDING.yml └── workflows │ └── jekyll-gh-pages.yml ├── .gitignore ├── LICENSE ├── README.md ├── diagrams └── micro-services.drawio ├── docker-compose.yml ├── resources ├── business needs.txt ├── curriculum.txt └── distributed patterns.txt └── services ├── config-server ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── alibou │ │ │ └── configserver │ │ │ └── ConfigServerApplication.java │ └── resources │ │ ├── application.yml │ │ └── configurations │ │ ├── application.yml │ │ ├── customer-service.yml │ │ ├── discovery-service.yml │ │ ├── gateway-service.yml │ │ ├── notification-service.yml │ │ ├── order-service.yml │ │ ├── payment-service.yml │ │ └── product-service.yml │ └── test │ └── java │ └── com │ └── alibou │ └── configserver │ └── ConfigServerApplicationTests.java ├── customer ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── alibou │ │ │ └── ecommerce │ │ │ ├── CustomerApplication.java │ │ │ ├── customer │ │ │ ├── Address.java │ │ │ ├── Customer.java │ │ │ ├── CustomerController.java │ │ │ ├── CustomerMapper.java │ │ │ ├── CustomerRepository.java │ │ │ ├── CustomerRequest.java │ │ │ ├── CustomerResponse.java │ │ │ └── CustomerService.java │ │ │ ├── exception │ │ │ └── CustomerNotFoundException.java │ │ │ └── handler │ │ │ ├── ErrorResponse.java │ │ │ └── GlobalExceptionHandler.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── alibou │ └── ecommerce │ └── CustomerApplicationTests.java ├── discovery ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── alibou │ │ │ └── discovery │ │ │ └── DiscoveryApplication.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── alibou │ └── discovery │ └── DiscoveryApplicationTests.java ├── gateway ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── alibou │ │ │ └── gateway │ │ │ └── GatewayApplication.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── alibou │ └── gateway │ └── GatewayApplicationTests.java ├── notification ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── alibou │ │ │ └── ecommerce │ │ │ ├── NotificationApplication.java │ │ │ ├── email │ │ │ ├── EmailService.java │ │ │ └── EmailTemplates.java │ │ │ ├── kafka │ │ │ ├── NotificationsConsumer.java │ │ │ ├── order │ │ │ │ ├── Customer.java │ │ │ │ ├── OrderConfirmation.java │ │ │ │ └── Product.java │ │ │ └── payment │ │ │ │ ├── PaymentConfirmation.java │ │ │ │ └── PaymentMethod.java │ │ │ └── notification │ │ │ ├── Notification.java │ │ │ ├── NotificationRepository.java │ │ │ └── NotificationType.java │ └── resources │ │ ├── application.yml │ │ └── templates │ │ ├── order-confirmation.html │ │ └── payment-confirmation.html │ └── test │ └── java │ └── com │ └── alibou │ └── ecommerce │ └── NotificationApplicationTests.java ├── order ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── alibou │ │ │ └── ecommerce │ │ │ ├── OrderApplication.java │ │ │ ├── config │ │ │ ├── KafkaOrderTopicConfig.java │ │ │ └── RestTemplateConfig.java │ │ │ ├── customer │ │ │ ├── CustomerClient.java │ │ │ └── CustomerResponse.java │ │ │ ├── exception │ │ │ └── BusinessException.java │ │ │ ├── handler │ │ │ ├── ErrorResponse.java │ │ │ └── GlobalExceptionHandler.java │ │ │ ├── kafka │ │ │ ├── OrderConfirmation.java │ │ │ └── OrderProducer.java │ │ │ ├── order │ │ │ ├── Order.java │ │ │ ├── OrderController.java │ │ │ ├── OrderMapper.java │ │ │ ├── OrderRepository.java │ │ │ ├── OrderRequest.java │ │ │ ├── OrderResponse.java │ │ │ ├── OrderService.java │ │ │ └── PaymentMethod.java │ │ │ ├── orderline │ │ │ ├── OrderLine.java │ │ │ ├── OrderLineController.java │ │ │ ├── OrderLineMapper.java │ │ │ ├── OrderLineRepository.java │ │ │ ├── OrderLineRequest.java │ │ │ ├── OrderLineResponse.java │ │ │ └── OrderLineService.java │ │ │ ├── payment │ │ │ ├── PaymentClient.java │ │ │ └── PaymentRequest.java │ │ │ └── product │ │ │ ├── ProductClient.java │ │ │ ├── PurchaseRequest.java │ │ │ └── PurchaseResponse.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── alibou │ └── ecommerce │ └── OrderApplicationTests.java ├── payment ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── alibou │ │ │ └── ecommerce │ │ │ ├── PaymentApplication.java │ │ │ ├── configuration │ │ │ └── KafkaPaymentTopicConfig.java │ │ │ ├── exception │ │ │ └── BusinessException.java │ │ │ ├── handler │ │ │ ├── ErrorResponse.java │ │ │ └── GlobalExceptionHandler.java │ │ │ ├── notification │ │ │ ├── NotificationProducer.java │ │ │ └── PaymentNotificationRequest.java │ │ │ └── payment │ │ │ ├── Customer.java │ │ │ ├── Payment.java │ │ │ ├── PaymentController.java │ │ │ ├── PaymentMapper.java │ │ │ ├── PaymentMethod.java │ │ │ ├── PaymentRepository.java │ │ │ ├── PaymentRequest.java │ │ │ └── PaymentService.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── com │ └── alibou │ └── ecommerce │ └── PaymentApplicationTests.java └── product ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── alibou │ │ └── ecommerce │ │ ├── ProductApplication.java │ │ ├── category │ │ └── Category.java │ │ ├── exception │ │ └── ProductPurchaseException.java │ │ ├── handler │ │ ├── ErrorResponse.java │ │ └── GlobalExceptionHandler.java │ │ └── product │ │ ├── Product.java │ │ ├── ProductController.java │ │ ├── ProductMapper.java │ │ ├── ProductPurchaseRequest.java │ │ ├── ProductPurchaseResponse.java │ │ ├── ProductRepository.java │ │ ├── ProductRequest.java │ │ ├── ProductResponse.java │ │ └── ProductService.java └── resources │ ├── application.yml │ └── db │ └── migration │ ├── V1__init_database.sql │ └── V2__insert_data.sql └── test └── java └── com └── alibou └── ecommerce └── ProductApplicationTests.java /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PramithaMJ/fully-completed-microservices-Java-Springboot/4a3d1d7c8a678c621371caafebe083e79f105244/.DS_Store -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # .github/FUNDING.yml 2 | github: [pramithamj] 3 | custom: 4 | - https://buymeacoffee.com/lpramithamm 5 | - https://pramithamj.me/ 6 | -------------------------------------------------------------------------------- /.github/workflows/jekyll-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Build job 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | - name: Setup Pages 32 | uses: actions/configure-pages@v5 33 | - name: Build with Jekyll 34 | uses: actions/jekyll-build-pages@v1 35 | with: 36 | source: ./ 37 | destination: ./_site 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v3 40 | 41 | # Deployment job 42 | deploy: 43 | environment: 44 | name: github-pages 45 | url: ${{ steps.deployment.outputs.page_url }} 46 | runs-on: ubuntu-latest 47 | needs: build 48 | steps: 49 | - name: Deploy to GitHub Pages 50 | id: deployment 51 | uses: actions/deploy-pages@v4 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fully Completed Microservices Project 2 | 3 | ## Overview 4 | 5 | This repository contains a collection of fully completed microservices built with Spring Boot version 3.2.5 and Java 17. The project utilizes Spring Cloud version 2023.0.1 for implementing various distributed system patterns and features. 6 | 7 | ## Microservices Description 8 | 9 | 1. **Config Server** 10 | - Provides centralized configuration for all microservices. 11 | - Uses Spring Cloud Config Server. 12 | 13 | 2. **Customer Service** 14 | - Manages customer data and operations. 15 | - Integrated with Eureka Discovery. 16 | 17 | 3. **Discovery Service** 18 | - Service registry using Netflix Eureka. 19 | - Enables service discovery for other microservices. 20 | 21 | 4. **Gateway Service** 22 | - API Gateway for routing requests to appropriate microservices. 23 | - Uses Spring Cloud Gateway. 24 | - Includes distributed tracing and circuit breaker. 25 | 26 | 5. **Notification Service** 27 | - Handles notifications and alerts. 28 | - Uses Kafka for messaging. 29 | 30 | 6. **Order Service** 31 | - Manages orders and their statuses. 32 | - Integrated with Eureka Discovery. 33 | 34 | 7. **Payment Service** 35 | - Processes payments. 36 | - Uses Eureka Discovery and Zipkin for tracing. 37 | 38 | 8. **Product Service** 39 | - Manages product information. 40 | - Integrated with Eureka Discovery. 41 | 42 | ## Features 43 | 44 | - **Service Discovery**: All microservices register with the Eureka server for easy discovery. 45 | - **Centralized Configuration**: Configurations are managed centrally using the Spring Cloud Config Server. 46 | - **API Gateway**: Spring Cloud Gateway is used for routing and handling cross-cutting concerns like security, monitoring, and resilience. 47 | - **Distributed Tracing**: Zipkin is used for tracing requests across microservices. 48 | - **Circuit Breaker**: Circuit breaking capabilities provided by Spring Cloud Circuit Breaker. 49 | - **Messaging**: Kafka is used for asynchronous communication between microservices. 50 | 51 | ## Prerequisites 52 | 53 | - Java 17 or later 54 | - Maven or Gradle 55 | - Docker (optional, for containerized deployment) 56 | 57 | ## Running the Microservices 58 | 59 | 1. **Clone the repository** 60 | ```sh 61 | git clone https://github.com/PramithaMJ/fully-completed-microservices.git 62 | cd fully-completed-microservices 63 | ``` 64 | 65 | 2. **Start Config Server** 66 | ```sh 67 | cd config-server 68 | mvn spring-boot:run 69 | ``` 70 | 71 | 3. **Start Discovery Service** 72 | ```sh 73 | cd discovery 74 | mvn spring-boot:run 75 | ``` 76 | 77 | 4. **Start Other Microservices** 78 | Start the remaining microservices in any order. Ensure they are configured to register with the Discovery Service. 79 | 80 | ```sh 81 | cd 82 | mvn spring-boot:run 83 | ``` 84 | 85 | ## Configuration 86 | 87 | Each microservice has its configuration properties defined in the `application.yml` or `application.properties` file. The Config Server properties should be specified in a central configuration repository. 88 | 89 | ## Deployment 90 | 91 | To deploy the microservices using Docker, use the Dockerfile available in each microservice directory. You can build and run the Docker images as follows: 92 | 93 | ```sh 94 | cd 95 | docker build -t :latest . 96 | docker run -d -p : :latest 97 | ``` 98 | 99 | ## Contributing 100 | 101 | 1. Fork the repository. 102 | 2. Create your feature branch (`git checkout -b feature/your-feature`). 103 | 3. Commit your changes (`git commit -m 'Add some feature'`). 104 | 4. Push to the branch (`git push origin feature/your-feature`). 105 | 5. Open a pull request. 106 | 107 | ## License 108 | 109 | This project is licensed under the MIT License. 110 | 111 | ## Contact 112 | 113 | For any questions or feedback, please open an issue in the repository. 114 | 115 | Thank you, 116 | Pramitha Jayasooriya 117 | https://pramithamj.live 118 | 119 | 122 | ## Donation 123 | 124 | ***If you like what I do, maybe consider buying me a coffee*** 125 | 126 | Buy Me A Coffee 127 | 128 | *** 129 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | postgresql: 3 | container_name: ms_pg_sql 4 | image: postgres 5 | environment: 6 | POSTGRES_USER: alibou 7 | POSTGRES_PASSWORD: alibou 8 | PGDATA: /data/postgres 9 | volumes: 10 | - postgres:/data/postgres 11 | ports: 12 | - "5432:5432" 13 | networks: 14 | - microservices-net 15 | restart: unless-stopped 16 | 17 | pgadmin: 18 | container_name: ms_pgadmin 19 | image: dpage/pgadmin4 20 | environment: 21 | PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org} 22 | PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} 23 | PGADMIN_CONFIG_SERVER_MODE: 'False' 24 | volumes: 25 | - pgadmin:/var/lib/pgadmin 26 | ports: 27 | - "5050:80" 28 | networks: 29 | - microservices-net 30 | restart: unless-stopped 31 | 32 | zipkin: 33 | container_name: zipkin 34 | image: openzipkin/zipkin 35 | ports: 36 | - "9411:9411" 37 | networks: 38 | - microservices-net 39 | 40 | mongodb: 41 | image: mongo 42 | container_name: mongo_db 43 | ports: 44 | - 27017:27017 45 | volumes: 46 | - mongo:/data 47 | environment: 48 | - MONGO_INITDB_ROOT_USERNAME=alibou 49 | - MONGO_INITDB_ROOT_PASSWORD=alibou 50 | 51 | mongo-express: 52 | image: mongo-express 53 | container_name: mongo_express 54 | restart: always 55 | ports: 56 | - 8081:8081 57 | environment: 58 | - ME_CONFIG_MONGODB_ADMINUSERNAME=alibou 59 | - ME_CONFIG_MONGODB_ADMINPASSWORD=alibou 60 | - ME_CONFIG_MONGODB_SERVER=mongodb 61 | 62 | zookeeper: 63 | image: confluentinc/cp-zookeeper:latest 64 | container_name: zookeeper 65 | environment: 66 | ZOOKEEPER_SERVER_ID: 1 67 | ZOOKEEPER_CLIENT_PORT: 2181 68 | ZOOKEEPER_TICK_TIME: 2000 69 | ports: 70 | - "22181:2181" 71 | networks: 72 | - microservices-net 73 | kafka: 74 | image: confluentinc/cp-kafka:latest 75 | container_name: ms_kafka 76 | ports: 77 | - "9092:9092" 78 | depends_on: 79 | - zookeeper 80 | environment: 81 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 82 | KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 83 | KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 84 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 85 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT 86 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 87 | networks: 88 | - microservices-net 89 | 90 | mail-dev: 91 | container_name: ms-mail-dev 92 | image: maildev/maildev 93 | ports: 94 | - 1080:1080 95 | - 1025:1025 96 | 97 | networks: 98 | microservices-net: 99 | driver: bridge 100 | 101 | volumes: 102 | postgres: 103 | pgadmin: 104 | mongo: 105 | -------------------------------------------------------------------------------- /resources/business needs.txt: -------------------------------------------------------------------------------- 1 | As a business owner in the e-commerce sector, I am currently operating without the aid of any digital solutions. 2 | My product line consists of a variety of items, each identified by a unique code and accompanied by a detailed description. 3 | 4 | Customers interact with my business by placing orders from this list of products. 5 | I identify my customers using their first name, last name, email and address. 6 | Each customer transaction involves a specific payment method. 7 | Upon the completion of a successful payment transaction, I take the responsibility to inform the customer via an email, confirming 8 | the success of their payment or conversely, notifying them of any payment failures. 9 | 10 | In a bid to streamline operations and promote growth within my business, I am looking to invest in the development of a dedicated application. 11 | This application will serve to simplify my business processes and contribute significantly to the overall efficiency and scalability of my online venture. 12 | -------------------------------------------------------------------------------- /resources/curriculum.txt: -------------------------------------------------------------------------------- 1 | Microservices course 2 | 3 | Section 1 - 4 | - What you will learn in this course 5 | 6 | Section 2 - Introduction to micro services architecture 7 | - What is micro services? 8 | - The fundamental principles of microservices architecture 9 | - The benefits of microservices architecture 10 | - Microservices architecture patterns 11 | 12 | Section 3 - Application overview 13 | - Presenting the future application 14 | 15 | Section 4 - Bootstrap the application 16 | - Create GitHub mono-repository 17 | - Create Customer ms 18 | - Create Order ms 19 | - Create Payment ms 20 | - Create Notification ms 21 | - Create docker-compose and run the containers 22 | - Git commit 23 | 24 | Section 5 - Config server 25 | - Why we need a config server? 26 | - Create config-server project 27 | - Develop the config server (maybe splitted) 28 | - Add Customer-service config 29 | - Git commit 30 | 31 | Section 6 - Implement Customer service 32 | - Read configs from config server 33 | - Create Customer entity 34 | - Create Customer repository 35 | - Create Customer registration dto 36 | - Create customer response dto 37 | - Create Customer service 38 | - Create Customer controller 39 | - Test 40 | - Git commit 41 | 42 | Section 7 - Implement Order service 43 | - Add Order config to config-server 44 | - Read configs from config server 45 | - Create Order entity 46 | - Create Order repository 47 | - Create Order registration dto 48 | - Create Order response dto 49 | - Create Order service 50 | - Create Order controller 51 | - Test 52 | - Git commit 53 | 54 | Section 8 - Implement Payment service 55 | - Add Payment config to config-server 56 | - Read configs from config server 57 | - Create Payment entity 58 | - Create Payment repository 59 | - Create Payment registration dto 60 | - Create Payment response dto 61 | - Create Payment service 62 | - Create Payment controller 63 | - Test 64 | - Git commit 65 | 66 | Section 9 - Microservices Communication via HTTP 67 | - 68 | - RestTemplates 69 | - Test the communication 70 | - OpenFeign 71 | - Test the communication 72 | - RestTemplate VS OpenFeign 73 | - Git commit 74 | 75 | Section 10 - Service Discovery with Eureka 76 | - What is service discovery? 77 | - Why we need a service discovery 78 | - Create discovery-service 79 | - Externalize configuration 80 | - Load externalized configuration 81 | - Update all the microservices to register in the DS 82 | - Run the discovery service 83 | - Observe the changes 84 | - Git commit 85 | 86 | Section 11 - API Gateway 87 | - What is a Gateway? 88 | - Why a Gateway? 89 | - Create api-gateway 90 | - Add routes for Customer-service 91 | - Run and test the changes 92 | - Exercice: Add the route for Order-service 93 | - Solution 94 | - Exercice: Externalize configuration 95 | - Solution 96 | - Load externalized configuration 97 | - Git commit 98 | 99 | 100 | Section 12 - Exercice 101 | - Establish a communication with all the microservices 102 | --> should be devided and detailed 103 | - Solution 104 | 105 | Section 13 - Asynchronous communication 106 | - Setup RabbitMQ (docker-compose) 107 | - Run RabbitMQ 108 | - What is async communication? 109 | - Why async? 110 | - more details about rabbit mq and the diff exchange type, queue, ... 111 | --> diagram 112 | - Publish message to order-queue 113 | - Test the changes 114 | - Exercice: Follow the previous steps and publish a message on payment-queue 115 | - Solution 116 | 117 | Section 14 - Play and Observe 118 | - Playing with the application 119 | 120 | Section 15 - Notification service & Java mail 121 | - Configure and install maildev (docker-compose) 122 | - Create Notification service 123 | - Configure Java-mail 124 | - Add RabbitMQ consumer 125 | - Consume order-queue and send email 126 | - Save to the database 127 | - Exercice: consume payment-queue and send email 128 | - Solution 129 | 130 | Section 16 - Distributed Tracing 131 | - Distributed Tracing 132 | - Adding Sleuth 133 | - Zipkin 134 | - Zipkin Container 135 | - Spring Cloud Sleuth Zipkin 136 | - Zipkin Dashboard 137 | - Section End Git Commit 138 | 139 | Section 17 - Distributed documentation 140 | - Expose services documentation through API Gateway 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /resources/distributed patterns.txt: -------------------------------------------------------------------------------- 1 | 2 | - Service Registry and Discovery (/) 3 | - API Gateway (/) 4 | - CQRS (Command Query Responsibility Segregation) (/) 5 | - Database per Service (/) 6 | - Event-driven Architecture (RabbitMQ) (/) 7 | - Saga Pattern (x) 8 | - Circuit breaker (x) 9 | 10 | 11 | ------------------------------------------ 12 | 13 | First part: 14 | - Spring boot in depth 15 | - Theoretic introduction to the spring framework 16 | - How to create a project 17 | - How to connect to a database 18 | - Class diagram 19 | - Hibernate relationships 20 | - Spring data JPA repositories 21 | - Services 22 | - Controllers 23 | - Test the API 24 | - Objects validation 25 | - Test the changes 26 | - Exception Handling 27 | - Test the changes 28 | - Unit testing (Validator, Service) 29 | - E2E testing (Controller) 30 | - E2E testing (postman collection, newman) 31 | 32 | 33 | -------------------------------------------------------------------------------- /services/config-server/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /services/config-server/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PramithaMJ/fully-completed-microservices-Java-Springboot/4a3d1d7c8a678c621371caafebe083e79f105244/services/config-server/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /services/config-server/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /services/config-server/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/#build-image) 9 | * [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/3.0.2/reference/htmlsingle/#actuator) 10 | * [Config Server](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_spring_cloud_config_server) 11 | 12 | ### Guides 13 | The following guides illustrate how to use some features concretely: 14 | 15 | * [Building a RESTful Web Service with Spring Boot Actuator](https://spring.io/guides/gs/actuator-service/) 16 | * [Centralized Configuration](https://spring.io/guides/gs/centralized-configuration/) 17 | 18 | -------------------------------------------------------------------------------- /services/config-server/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /services/config-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.5 9 | 10 | 11 | com.alibou 12 | config-server 13 | 0.0.1-SNAPSHOT 14 | config-server 15 | config-server 16 | 17 | 17 18 | 2023.0.1 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-actuator 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-config-server 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-test 33 | test 34 | 35 | 36 | 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-dependencies 41 | ${spring-cloud.version} 42 | pom 43 | import 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-maven-plugin 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /services/config-server/src/main/java/com/alibou/configserver/ConfigServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.alibou.configserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.config.server.EnableConfigServer; 6 | 7 | @EnableConfigServer 8 | @SpringBootApplication 9 | public class ConfigServerApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ConfigServerApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /services/config-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8888 3 | spring: 4 | profiles: 5 | active: native 6 | application: 7 | name: config-server 8 | cloud: 9 | config: 10 | server: 11 | native: 12 | search-locations: classpath:/configurations 13 | -------------------------------------------------------------------------------- /services/config-server/src/main/resources/configurations/application.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | instance: 3 | hostname: localhost 4 | client: 5 | service-url: 6 | defaultZone: http://localhost:8761/eureka 7 | name: 8 | value: alibou 9 | spring: 10 | cloud: 11 | config: 12 | override-system-properties: false 13 | 14 | management: 15 | tracing: 16 | sampling: 17 | probability: 1.0 18 | -------------------------------------------------------------------------------- /services/config-server/src/main/resources/configurations/customer-service.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8090 3 | spring: 4 | data: 5 | mongodb: 6 | username: alibou 7 | password: alibou 8 | host: localhost 9 | port: 27017 10 | database: customer 11 | authentication-database: admin -------------------------------------------------------------------------------- /services/config-server/src/main/resources/configurations/discovery-service.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | instance: 3 | hostname: localhost 4 | client: 5 | registerWithEureka: false 6 | fetchRegistry: false 7 | serviceUrl: 8 | defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 9 | 10 | server: 11 | port: 8761 12 | -------------------------------------------------------------------------------- /services/config-server/src/main/resources/configurations/gateway-service.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8222 3 | spring: 4 | cloud: 5 | gateway: 6 | discovery: 7 | locator: 8 | enabled: true 9 | routes: 10 | - id: customer-service 11 | uri: lb:http://CUSTOMER-SERVICE 12 | predicates: 13 | - Path=/api/v1/customers/** 14 | - id: order-service 15 | uri: lb:http://ORDER-SERVICE 16 | predicates: 17 | - Path=/api/v1/orders/** 18 | - id: order-lines-service 19 | uri: lb:http://ORDER-SERVICE 20 | predicates: 21 | - Path=/api/v1/order-lines/** 22 | - id: product-service 23 | uri: lb:http://PRODUCT-SERVICE 24 | predicates: 25 | - Path=/api/v1/products/** 26 | - id: payment-service 27 | uri: lb:http://PAYMENT-SERVICE 28 | predicates: 29 | - Path=/api/v1/payments/** 30 | -------------------------------------------------------------------------------- /services/config-server/src/main/resources/configurations/notification-service.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8040 3 | spring: 4 | data: 5 | mongodb: 6 | username: alibou 7 | password: alibou 8 | host: localhost 9 | port: 27017 10 | database: notification 11 | authentication-database: admin 12 | kafka: 13 | consumer: 14 | bootstrap-servers: localhost:9092 15 | group-id: paymentGroup,orderGroup 16 | auto-offset-reset: earliest 17 | key-deserializer: org.apache.kafka.common.serialization.StringDeserializer 18 | value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer 19 | properties: 20 | spring.json.trusted.packages: '*' 21 | spring.json.type.mapping: orderConfirmation:com.alibou.ecommerce.kafka.order.OrderConfirmation,paymentConfirmation:com.alibou.ecommerce.kafka.payment.PaymentConfirmation 22 | mail: 23 | host: localhost 24 | port: 1025 25 | username: alibou 26 | password: alibou 27 | properties: 28 | mail: 29 | smtp: 30 | trust: "*" 31 | auth: true 32 | starttls: 33 | enabled: true 34 | connectiontimeout: 5000 35 | timeout: 3000 36 | writetimeout: 5000 37 | -------------------------------------------------------------------------------- /services/config-server/src/main/resources/configurations/order-service.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8070 3 | spring: 4 | datasource: 5 | driver-class-name: org.postgresql.Driver 6 | url: jdbc:postgresql://localhost:5432/order 7 | username: alibou 8 | password: alibou 9 | jpa: 10 | hibernate: 11 | ddl-auto: create 12 | database: postgresql 13 | database-platform: org.hibernate.dialect.PostgreSQLDialect 14 | kafka: 15 | producer: 16 | bootstrap-servers: localhost:9092 17 | key-serializer: org.apache.kafka.common.serialization.StringSerializer 18 | value-serializer: org.springframework.kafka.support.serializer.JsonSerializer 19 | properties: 20 | spring.json.type.mapping: orderConfirmation:com.alibou.ecommerce.kafka.OrderConfirmation 21 | 22 | application: 23 | config: 24 | customer-url: http://localhost:8222/api/v1/customers 25 | payment-url: http://localhost:8222/api/v1/payments 26 | product-url: http://localhost:8222/api/v1/products 27 | -------------------------------------------------------------------------------- /services/config-server/src/main/resources/configurations/payment-service.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8060 3 | spring: 4 | datasource: 5 | driver-class-name: org.postgresql.Driver 6 | url: jdbc:postgresql://localhost:5432/payment 7 | username: alibou 8 | password: alibou 9 | jpa: 10 | hibernate: 11 | ddl-auto: create 12 | database: postgresql 13 | database-platform: org.hibernate.dialect.PostgreSQLDialect 14 | 15 | kafka: 16 | producer: 17 | bootstrap-servers: localhost:9092 18 | key-serializer: org.apache.kafka.common.serialization.StringSerializer 19 | value-serializer: org.springframework.kafka.support.serializer.JsonSerializer 20 | properties: 21 | spring.json.type.mapping: paymentConfirmation:com.alibou.ecommerce.notification.PaymentNotificationRequest 22 | application: 23 | config: 24 | product-url: http://localhost:8222/api/v1/products 25 | -------------------------------------------------------------------------------- /services/config-server/src/main/resources/configurations/product-service.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8050 3 | spring: 4 | datasource: 5 | driver-class-name: org.postgresql.Driver 6 | url: jdbc:postgresql://localhost:5432/product 7 | username: alibou 8 | password: alibou 9 | jpa: 10 | hibernate: 11 | ddl-auto: validate 12 | database: postgresql 13 | database-platform: org.hibernate.dialect.PostgreSQLDialect 14 | flyway: 15 | baseline-on-migrate: true 16 | enabled: true 17 | baseline-description: "init" 18 | baseline-version: 0 19 | user: ${spring.datasource.username} 20 | password: ${spring.datasource.password} 21 | 22 | -------------------------------------------------------------------------------- /services/config-server/src/test/java/com/alibou/configserver/ConfigServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.alibou.configserver; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ConfigServerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /services/customer/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /services/customer/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PramithaMJ/fully-completed-microservices-Java-Springboot/4a3d1d7c8a678c621371caafebe083e79f105244/services/customer/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /services/customer/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /services/customer/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/#build-image) 9 | * [Spring Data MongoDB](https://docs.spring.io/spring-boot/docs/3.0.2/reference/htmlsingle/#data.nosql.mongodb) 10 | * [Eureka Discovery Client](https://docs.spring.io/spring-cloud-netflix/docs/current/reference/html/#service-discovery-eureka-clients) 11 | * [Spring Web](https://docs.spring.io/spring-boot/docs/3.0.2/reference/htmlsingle/#web) 12 | * [Config Client Quick Start](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_client_side_usage) 13 | 14 | ### Guides 15 | The following guides illustrate how to use some features concretely: 16 | 17 | * [Accessing Data with MongoDB](https://spring.io/guides/gs/accessing-data-mongodb/) 18 | * [Service Registration and Discovery with Eureka and Spring Cloud](https://spring.io/guides/gs/service-registration-and-discovery/) 19 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 20 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 21 | * [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) 22 | 23 | -------------------------------------------------------------------------------- /services/customer/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /services/customer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.5 9 | 10 | 11 | com.alibou 12 | customer 13 | 0.0.1-SNAPSHOT 14 | customer 15 | customer 16 | 17 | 17 18 | 2023.0.1 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-data-mongodb 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-validation 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | org.springframework.cloud 35 | spring-cloud-starter-config 36 | 37 | 38 | org.springframework.cloud 39 | spring-cloud-starter-netflix-eureka-client 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-actuator 45 | 46 | 47 | 48 | io.micrometer 49 | micrometer-tracing-bridge-brave 50 | 51 | 52 | io.zipkin.reporter2 53 | zipkin-reporter-brave 54 | 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | true 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-test 64 | test 65 | 66 | 67 | 68 | 69 | 70 | org.springframework.cloud 71 | spring-cloud-dependencies 72 | ${spring-cloud.version} 73 | pom 74 | import 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-maven-plugin 84 | 85 | 86 | 87 | org.projectlombok 88 | lombok 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/CustomerApplication.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class CustomerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(CustomerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/customer/Address.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.customer; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.Setter; 10 | import org.springframework.validation.annotation.Validated; 11 | 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Builder 15 | @Getter 16 | @Setter 17 | @Validated 18 | public class Address { 19 | 20 | private String street; 21 | private String houseNumber; 22 | private String zipCode; 23 | } 24 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/customer/Customer.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.customer; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | import org.springframework.data.annotation.Id; 10 | import org.springframework.data.mongodb.core.mapping.Document; 11 | 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Builder 15 | @Getter 16 | @Setter 17 | @Document 18 | public class Customer { 19 | 20 | @Id 21 | private String id; 22 | private String firstname; 23 | private String lastname; 24 | private String email; 25 | private Address address; 26 | } 27 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/customer/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.customer; 2 | 3 | import java.util.List; 4 | 5 | import jakarta.validation.Valid; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.DeleteMapping; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.PutMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | @RestController 18 | @RequestMapping("/api/v1/customers") 19 | @RequiredArgsConstructor 20 | public class CustomerController { 21 | 22 | private final CustomerService service; 23 | 24 | @PostMapping 25 | public ResponseEntity createCustomer( 26 | @RequestBody @Valid CustomerRequest request 27 | ) { 28 | return ResponseEntity.ok(this.service.createCustomer(request)); 29 | } 30 | 31 | @PutMapping 32 | public ResponseEntity updateCustomer( 33 | @RequestBody @Valid CustomerRequest request 34 | ) { 35 | this.service.updateCustomer(request); 36 | return ResponseEntity.accepted().build(); 37 | } 38 | 39 | @GetMapping 40 | public ResponseEntity> findAll() { 41 | return ResponseEntity.ok(this.service.findAllCustomers()); 42 | } 43 | 44 | @GetMapping("/exists/{customer-id}") 45 | public ResponseEntity existsById( 46 | @PathVariable("customer-id") String customerId 47 | ) { 48 | return ResponseEntity.ok(this.service.existsById(customerId)); 49 | } 50 | 51 | @GetMapping("/{customer-id}") 52 | public ResponseEntity findById( 53 | @PathVariable("customer-id") String customerId 54 | ) { 55 | return ResponseEntity.ok(this.service.findById(customerId)); 56 | } 57 | 58 | @DeleteMapping("/{customer-id}") 59 | public ResponseEntity delete( 60 | @PathVariable("customer-id") String customerId 61 | ) { 62 | this.service.deleteCustomer(customerId); 63 | return ResponseEntity.accepted().build(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/customer/CustomerMapper.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.customer; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class CustomerMapper { 7 | 8 | public Customer toCustomer(CustomerRequest request) { 9 | if (request == null) { 10 | return null; 11 | } 12 | return Customer.builder() 13 | .id(request.id()) 14 | .firstname(request.firstname()) 15 | .lastname(request.lastname()) 16 | .email(request.email()) 17 | .address(request.address()) 18 | .build(); 19 | } 20 | 21 | public CustomerResponse fromCustomer(Customer customer) { 22 | if (customer == null) { 23 | return null; 24 | } 25 | return new CustomerResponse( 26 | customer.getId(), 27 | customer.getFirstname(), 28 | customer.getLastname(), 29 | customer.getEmail(), 30 | customer.getAddress() 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/customer/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.customer; 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository; 4 | 5 | public interface CustomerRepository extends MongoRepository { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/customer/CustomerRequest.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.customer; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotNull; 5 | 6 | public record CustomerRequest( 7 | String id, 8 | @NotNull(message = "Customer firstname is required") 9 | String firstname, 10 | @NotNull(message = "Customer firstname is required") 11 | String lastname, 12 | @NotNull(message = "Customer Email is required") 13 | @Email(message = "Customer Email is not a valid email address") 14 | String email, 15 | Address address 16 | ) { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/customer/CustomerResponse.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.customer; 2 | 3 | public record CustomerResponse( 4 | String id, 5 | String firstname, 6 | String lastname, 7 | String email, 8 | Address address 9 | ) { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/customer/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.customer; 2 | 3 | import com.alibou.ecommerce.exception.CustomerNotFoundException; 4 | import lombok.RequiredArgsConstructor; 5 | import org.apache.commons.lang.StringUtils; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | @Service 12 | @RequiredArgsConstructor 13 | public class CustomerService { 14 | 15 | private final CustomerRepository repository; 16 | private final CustomerMapper mapper; 17 | 18 | public String createCustomer(CustomerRequest request) { 19 | var customer = this.repository.save(mapper.toCustomer(request)); 20 | return customer.getId(); 21 | } 22 | 23 | public void updateCustomer(CustomerRequest request) { 24 | var customer = this.repository.findById(request.id()) 25 | .orElseThrow(() -> new CustomerNotFoundException( 26 | String.format("Cannot update customer:: No customer found with the provided ID: %s", request.id()) 27 | )); 28 | mergeCustomer(customer, request); 29 | this.repository.save(customer); 30 | } 31 | 32 | private void mergeCustomer(Customer customer, CustomerRequest request) { 33 | if (StringUtils.isNotBlank(request.firstname())) { 34 | customer.setFirstname(request.firstname()); 35 | } 36 | if (StringUtils.isNotBlank(request.email())) { 37 | customer.setEmail(request.email()); 38 | } 39 | if (request.address() != null) { 40 | customer.setAddress(request.address()); 41 | } 42 | } 43 | 44 | public List findAllCustomers() { 45 | return this.repository.findAll() 46 | .stream() 47 | .map(this.mapper::fromCustomer) 48 | .collect(Collectors.toList()); 49 | } 50 | 51 | public CustomerResponse findById(String id) { 52 | return this.repository.findById(id) 53 | .map(mapper::fromCustomer) 54 | .orElseThrow(() -> new CustomerNotFoundException(String.format("No customer found with the provided ID: %s", id))); 55 | } 56 | 57 | public boolean existsById(String id) { 58 | return this.repository.findById(id) 59 | .isPresent(); 60 | } 61 | 62 | public void deleteCustomer(String id) { 63 | this.repository.deleteById(id); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/exception/CustomerNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.exception; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | @EqualsAndHashCode(callSuper = true) 7 | @Data 8 | public class CustomerNotFoundException extends RuntimeException { 9 | 10 | private final String msg; 11 | } 12 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/handler/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.handler; 2 | 3 | import java.util.Map; 4 | import java.util.Set; 5 | 6 | public record ErrorResponse( 7 | Map errors 8 | ) { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /services/customer/src/main/java/com/alibou/ecommerce/handler/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.handler; 2 | 3 | import com.alibou.ecommerce.exception.CustomerNotFoundException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.validation.FieldError; 7 | import org.springframework.web.bind.MethodArgumentNotValidException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | 11 | import java.util.HashMap; 12 | 13 | import static org.springframework.http.HttpStatus.BAD_REQUEST; 14 | 15 | @RestControllerAdvice 16 | public class GlobalExceptionHandler { 17 | 18 | @ExceptionHandler(CustomerNotFoundException.class) 19 | public ResponseEntity handle(CustomerNotFoundException exp) { 20 | return ResponseEntity 21 | .status(HttpStatus.NOT_FOUND) 22 | .body(exp.getMsg()); 23 | } 24 | 25 | @ExceptionHandler(MethodArgumentNotValidException.class) 26 | public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException exp) { 27 | var errors = new HashMap(); 28 | exp.getBindingResult().getAllErrors() 29 | .forEach(error -> { 30 | var fieldName = ((FieldError) error).getField(); 31 | var errorMessage = error.getDefaultMessage(); 32 | errors.put(fieldName, errorMessage); 33 | }); 34 | 35 | return ResponseEntity 36 | .status(BAD_REQUEST) 37 | .body(new ErrorResponse(errors)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /services/customer/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | config: 3 | import: optional:configserver:http://localhost:8888 4 | application: 5 | name: customer-service 6 | 7 | -------------------------------------------------------------------------------- /services/customer/src/test/java/com/alibou/ecommerce/CustomerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class CustomerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /services/discovery/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /services/discovery/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PramithaMJ/fully-completed-microservices-Java-Springboot/4a3d1d7c8a678c621371caafebe083e79f105244/services/discovery/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /services/discovery/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /services/discovery/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/#build-image) 9 | * [Eureka Server](https://docs.spring.io/spring-cloud-netflix/docs/current/reference/html/#spring-cloud-eureka-server) 10 | * [Config Client Quick Start](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_client_side_usage) 11 | 12 | ### Guides 13 | The following guides illustrate how to use some features concretely: 14 | 15 | * [Service Registration and Discovery with Eureka and Spring Cloud](https://spring.io/guides/gs/service-registration-and-discovery/) 16 | 17 | -------------------------------------------------------------------------------- /services/discovery/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /services/discovery/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.5 9 | 10 | 11 | com.alibou 12 | discovery 13 | 0.0.1-SNAPSHOT 14 | discovery 15 | discovery 16 | 17 | 17 18 | 2023.0.1 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-config 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-starter-netflix-eureka-server 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-actuator 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-test 38 | test 39 | 40 | 41 | 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-dependencies 46 | ${spring-cloud.version} 47 | pom 48 | import 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-maven-plugin 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /services/discovery/src/main/java/com/alibou/discovery/DiscoveryApplication.java: -------------------------------------------------------------------------------- 1 | package com.alibou.discovery; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @EnableEurekaServer 8 | @SpringBootApplication 9 | public class DiscoveryApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DiscoveryApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /services/discovery/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | config: 3 | import: optional:configserver:http://localhost:8888 4 | application: 5 | name: discovery-service 6 | -------------------------------------------------------------------------------- /services/discovery/src/test/java/com/alibou/discovery/DiscoveryApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.alibou.discovery; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class DiscoveryApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /services/gateway/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /services/gateway/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PramithaMJ/fully-completed-microservices-Java-Springboot/4a3d1d7c8a678c621371caafebe083e79f105244/services/gateway/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /services/gateway/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /services/gateway/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/#build-image) 9 | * [Config Client Quick Start](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_client_side_usage) 10 | * [Gateway](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/) 11 | * [Eureka Discovery Client](https://docs.spring.io/spring-cloud-netflix/docs/current/reference/html/#service-discovery-eureka-clients) 12 | 13 | ### Guides 14 | The following guides illustrate how to use some features concretely: 15 | 16 | * [Using Spring Cloud Gateway](https://github.com/spring-cloud-samples/spring-cloud-gateway-sample) 17 | * [Service Registration and Discovery with Eureka and Spring Cloud](https://spring.io/guides/gs/service-registration-and-discovery/) 18 | 19 | -------------------------------------------------------------------------------- /services/gateway/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /services/gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.5 9 | 10 | 11 | com.alibou 12 | gateway 13 | 0.0.1-SNAPSHOT 14 | gateway 15 | gateway 16 | 17 | 17 18 | 2023.0.1 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-config 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-starter-gateway 28 | 29 | 30 | org.springframework.cloud 31 | spring-cloud-starter-netflix-eureka-client 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-actuator 37 | 38 | 39 | 40 | io.micrometer 41 | micrometer-tracing-bridge-brave 42 | 43 | 44 | io.zipkin.reporter2 45 | zipkin-reporter-brave 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-test 51 | test 52 | 53 | 54 | 55 | 56 | 57 | org.springframework.cloud 58 | spring-cloud-dependencies 59 | ${spring-cloud.version} 60 | pom 61 | import 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-maven-plugin 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /services/gateway/src/main/java/com/alibou/gateway/GatewayApplication.java: -------------------------------------------------------------------------------- 1 | package com.alibou.gateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class GatewayApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(GatewayApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /services/gateway/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | config: 3 | import: optional:configserver:http://localhost:8888 4 | application: 5 | name: gateway-service 6 | -------------------------------------------------------------------------------- /services/gateway/src/test/java/com/alibou/gateway/GatewayApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.alibou.gateway; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class GatewayApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /services/notification/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /services/notification/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PramithaMJ/fully-completed-microservices-Java-Springboot/4a3d1d7c8a678c621371caafebe083e79f105244/services/notification/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /services/notification/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /services/notification/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.2.0/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.2.0/maven-plugin/reference/html/#build-image) 9 | * [Spring Boot Actuator](https://docs.spring.io/spring-boot/docs/3.2.0/reference/htmlsingle/index.html#actuator) 10 | * [Eureka Discovery Client](https://docs.spring.io/spring-cloud-netflix/docs/current/reference/html/#service-discovery-eureka-clients) 11 | * [Config Client Quick Start](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_client_side_usage) 12 | * [Spring Web](https://docs.spring.io/spring-boot/docs/3.2.0/reference/htmlsingle/index.html#web) 13 | * [Spring for Apache Kafka](https://docs.spring.io/spring-boot/docs/3.2.0/reference/htmlsingle/index.html#messaging.kafka) 14 | * [Java Mail Sender](https://docs.spring.io/spring-boot/docs/3.2.0/reference/htmlsingle/index.html#io.email) 15 | * [Thymeleaf](https://docs.spring.io/spring-boot/docs/3.2.0/reference/htmlsingle/index.html#web.servlet.spring-mvc.template-engines) 16 | * [Spring Data MongoDB](https://docs.spring.io/spring-boot/docs/3.2.0/reference/htmlsingle/index.html#data.nosql.mongodb) 17 | 18 | ### Guides 19 | The following guides illustrate how to use some features concretely: 20 | 21 | * [Building a RESTful Web Service with Spring Boot Actuator](https://spring.io/guides/gs/actuator-service/) 22 | * [Service Registration and Discovery with Eureka and Spring Cloud](https://spring.io/guides/gs/service-registration-and-discovery/) 23 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 24 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 25 | * [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) 26 | * [Handling Form Submission](https://spring.io/guides/gs/handling-form-submission/) 27 | * [Accessing Data with MongoDB](https://spring.io/guides/gs/accessing-data-mongodb/) 28 | 29 | -------------------------------------------------------------------------------- /services/notification/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.5 9 | 10 | 11 | com.alibou 12 | notification 13 | 0.0.1-SNAPSHOT 14 | notification 15 | Notification project 16 | 17 | 17 18 | 2023.0.1 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-actuator 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-data-mongodb 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-mail 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-thymeleaf 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-web 40 | 41 | 42 | org.springframework.cloud 43 | spring-cloud-starter-config 44 | 45 | 46 | org.springframework.cloud 47 | spring-cloud-starter-netflix-eureka-client 48 | 49 | 50 | org.springframework.kafka 51 | spring-kafka 52 | 53 | 54 | org.projectlombok 55 | lombok 56 | 57 | 58 | 59 | io.micrometer 60 | micrometer-tracing-bridge-brave 61 | 62 | 63 | io.zipkin.reporter2 64 | zipkin-reporter-brave 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-test 70 | test 71 | 72 | 73 | 74 | 75 | 76 | org.springframework.cloud 77 | spring-cloud-dependencies 78 | ${spring-cloud.version} 79 | pom 80 | import 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | org.springframework.boot 89 | spring-boot-maven-plugin 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/NotificationApplication.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class NotificationApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(NotificationApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/email/EmailService.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.email; 2 | 3 | import com.alibou.ecommerce.kafka.order.Product; 4 | import jakarta.mail.MessagingException; 5 | import jakarta.mail.internet.MimeMessage; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.mail.javamail.JavaMailSender; 9 | import org.springframework.mail.javamail.MimeMessageHelper; 10 | import org.springframework.scheduling.annotation.Async; 11 | import org.springframework.stereotype.Service; 12 | import org.thymeleaf.context.Context; 13 | import org.thymeleaf.spring6.SpringTemplateEngine; 14 | 15 | import java.math.BigDecimal; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | import static com.alibou.ecommerce.email.EmailTemplates.ORDER_CONFIRMATION; 21 | import static com.alibou.ecommerce.email.EmailTemplates.PAYMENT_CONFIRMATION; 22 | import static java.nio.charset.StandardCharsets.UTF_8; 23 | 24 | @Service 25 | @Slf4j 26 | @RequiredArgsConstructor 27 | public class EmailService { 28 | 29 | private final JavaMailSender mailSender; 30 | private final SpringTemplateEngine templateEngine; 31 | 32 | @Async 33 | public void sendPaymentSuccessEmail( 34 | String destinationEmail, 35 | String customerName, 36 | BigDecimal amount, 37 | String orderReference 38 | ) throws MessagingException { 39 | 40 | MimeMessage mimeMessage = mailSender.createMimeMessage(); 41 | MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED, UTF_8.name()); 42 | messageHelper.setFrom("contact@aliboucoding.com"); 43 | 44 | final String templateName = PAYMENT_CONFIRMATION.getTemplate(); 45 | 46 | Map variables = new HashMap<>(); 47 | variables.put("customerName", customerName); 48 | variables.put("amount", amount); 49 | variables.put("orderReference", orderReference); 50 | 51 | Context context = new Context(); 52 | context.setVariables(variables); 53 | messageHelper.setSubject(PAYMENT_CONFIRMATION.getSubject()); 54 | 55 | try { 56 | String htmlTemplate = templateEngine.process(templateName, context); 57 | messageHelper.setText(htmlTemplate, true); 58 | 59 | messageHelper.setTo(destinationEmail); 60 | mailSender.send(mimeMessage); 61 | log.info(String.format("INFO - Email successfully sent to %s with template %s ", destinationEmail, templateName)); 62 | } catch (MessagingException e) { 63 | log.warn("WARNING - Cannot send Email to {} ", destinationEmail); 64 | } 65 | 66 | } 67 | 68 | @Async 69 | public void sendOrderConfirmationEmail( 70 | String destinationEmail, 71 | String customerName, 72 | BigDecimal amount, 73 | String orderReference, 74 | List products 75 | ) throws MessagingException { 76 | 77 | MimeMessage mimeMessage = mailSender.createMimeMessage(); 78 | MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED, UTF_8.name()); 79 | messageHelper.setFrom("contact@aliboucoding.com"); 80 | 81 | final String templateName = ORDER_CONFIRMATION.getTemplate(); 82 | 83 | Map variables = new HashMap<>(); 84 | variables.put("customerName", customerName); 85 | variables.put("totalAmount", amount); 86 | variables.put("orderReference", orderReference); 87 | variables.put("products", products); 88 | 89 | Context context = new Context(); 90 | context.setVariables(variables); 91 | messageHelper.setSubject(ORDER_CONFIRMATION.getSubject()); 92 | 93 | try { 94 | String htmlTemplate = templateEngine.process(templateName, context); 95 | messageHelper.setText(htmlTemplate, true); 96 | 97 | messageHelper.setTo(destinationEmail); 98 | mailSender.send(mimeMessage); 99 | log.info(String.format("INFO - Email successfully sent to %s with template %s ", destinationEmail, templateName)); 100 | } catch (MessagingException e) { 101 | log.warn("WARNING - Cannot send Email to {} ", destinationEmail); 102 | } 103 | 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/email/EmailTemplates.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.email; 2 | 3 | import lombok.Getter; 4 | 5 | public enum EmailTemplates { 6 | 7 | PAYMENT_CONFIRMATION("payment-confirmation.html", "Payment successfully processed"), 8 | ORDER_CONFIRMATION("order-confirmation.html", "Order confirmation") 9 | ; 10 | 11 | @Getter 12 | private final String template; 13 | @Getter 14 | private final String subject; 15 | 16 | 17 | EmailTemplates(String template, String subject) { 18 | this.template = template; 19 | this.subject = subject; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/kafka/NotificationsConsumer.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.kafka; 2 | 3 | import com.alibou.ecommerce.email.EmailService; 4 | import com.alibou.ecommerce.kafka.order.OrderConfirmation; 5 | import com.alibou.ecommerce.kafka.payment.PaymentConfirmation; 6 | import com.alibou.ecommerce.notification.Notification; 7 | import com.alibou.ecommerce.notification.NotificationRepository; 8 | import jakarta.mail.MessagingException; 9 | import lombok.RequiredArgsConstructor; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.kafka.annotation.KafkaListener; 12 | import org.springframework.stereotype.Service; 13 | 14 | import java.time.LocalDateTime; 15 | 16 | import static com.alibou.ecommerce.notification.NotificationType.ORDER_CONFIRMATION; 17 | import static com.alibou.ecommerce.notification.NotificationType.PAYMENT_CONFIRMATION; 18 | import static java.lang.String.format; 19 | 20 | @Service 21 | @Slf4j 22 | @RequiredArgsConstructor 23 | public class NotificationsConsumer { 24 | 25 | private final NotificationRepository repository; 26 | private final EmailService emailService; 27 | @KafkaListener(topics = "payment-topic") 28 | public void consumePaymentSuccessNotifications(PaymentConfirmation paymentConfirmation) throws MessagingException { 29 | log.info(format("Consuming the message from payment-topic Topic:: %s", paymentConfirmation)); 30 | repository.save( 31 | Notification.builder() 32 | .type(PAYMENT_CONFIRMATION) 33 | .notificationDate(LocalDateTime.now()) 34 | .paymentConfirmation(paymentConfirmation) 35 | .build() 36 | ); 37 | var customerName = paymentConfirmation.customerFirstname() + " " + paymentConfirmation.customerLastname(); 38 | emailService.sendPaymentSuccessEmail( 39 | paymentConfirmation.customerEmail(), 40 | customerName, 41 | paymentConfirmation.amount(), 42 | paymentConfirmation.orderReference() 43 | ); 44 | } 45 | 46 | @KafkaListener(topics = "order-topic") 47 | public void consumeOrderConfirmationNotifications(OrderConfirmation orderConfirmation) throws MessagingException { 48 | log.info(format("Consuming the message from order-topic Topic:: %s", orderConfirmation)); 49 | repository.save( 50 | Notification.builder() 51 | .type(ORDER_CONFIRMATION) 52 | .notificationDate(LocalDateTime.now()) 53 | .orderConfirmation(orderConfirmation) 54 | .build() 55 | ); 56 | var customerName = orderConfirmation.customer().firstname() + " " + orderConfirmation.customer().lastname(); 57 | emailService.sendOrderConfirmationEmail( 58 | orderConfirmation.customer().email(), 59 | customerName, 60 | orderConfirmation.totalAmount(), 61 | orderConfirmation.orderReference(), 62 | orderConfirmation.products() 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/kafka/order/Customer.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.kafka.order; 2 | 3 | public record Customer( 4 | String id, 5 | String firstname, 6 | String lastname, 7 | String email 8 | ) { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/kafka/order/OrderConfirmation.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.kafka.order; 2 | 3 | import com.alibou.ecommerce.kafka.payment.PaymentMethod; 4 | 5 | import java.math.BigDecimal; 6 | import java.util.List; 7 | 8 | public record OrderConfirmation( 9 | String orderReference, 10 | BigDecimal totalAmount, 11 | PaymentMethod paymentMethod, 12 | Customer customer, 13 | List products 14 | 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/kafka/order/Product.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.kafka.order; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record Product( 6 | Integer productId, 7 | String name, 8 | String description, 9 | BigDecimal price, 10 | double quantity 11 | ) { 12 | } 13 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/kafka/payment/PaymentConfirmation.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.kafka.payment; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record PaymentConfirmation( 6 | String orderReference, 7 | BigDecimal amount, 8 | PaymentMethod paymentMethod, 9 | String customerFirstname, 10 | String customerLastname, 11 | String customerEmail 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/kafka/payment/PaymentMethod.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.kafka.payment; 2 | 3 | public enum PaymentMethod { 4 | 5 | PAYPAL, 6 | CREDIT_CARD, 7 | VISA, 8 | MASTER_CARD, 9 | BITCOIN 10 | } 11 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/notification/Notification.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.notification; 2 | 3 | import com.alibou.ecommerce.kafka.order.OrderConfirmation; 4 | import com.alibou.ecommerce.kafka.payment.PaymentConfirmation; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | import lombok.Setter; 11 | import org.springframework.data.annotation.Id; 12 | import org.springframework.data.mongodb.core.mapping.Document; 13 | 14 | import java.time.LocalDateTime; 15 | 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @Builder 19 | @Getter 20 | @Setter 21 | @Document 22 | public class Notification { 23 | 24 | @Id 25 | private String id; 26 | private NotificationType type; 27 | private LocalDateTime notificationDate; 28 | private OrderConfirmation orderConfirmation; 29 | private PaymentConfirmation paymentConfirmation; 30 | } 31 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/notification/NotificationRepository.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.notification; 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository; 4 | 5 | public interface NotificationRepository extends MongoRepository { 6 | } 7 | -------------------------------------------------------------------------------- /services/notification/src/main/java/com/alibou/ecommerce/notification/NotificationType.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.notification; 2 | 3 | public enum NotificationType { 4 | ORDER_CONFIRMATION, 5 | PAYMENT_CONFIRMATION 6 | } 7 | -------------------------------------------------------------------------------- /services/notification/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | config: 3 | import: optional:configserver:http://localhost:8888 4 | application: 5 | name: notification-service 6 | -------------------------------------------------------------------------------- /services/notification/src/main/resources/templates/order-confirmation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Order Details 8 | 9 | 55 | 56 | 57 | 58 |
59 |

Order Details

60 |

Customer:

61 |

Order ID:

62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
Product NameQuantityPrice
79 | 80 | 85 |
86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /services/notification/src/main/resources/templates/payment-confirmation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Payment Confirmation 8 | 9 | 52 | 53 | 54 | 55 |
56 |

Payment Confirmation

57 |

Dear ,

58 |

Your payment of $ has been successfully processed.

59 |

Order reference:

60 |

Thank you for choosing our service. If you have any questions, feel free to contact us.

61 | 62 | 66 |
67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /services/notification/src/test/java/com/alibou/ecommerce/NotificationApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class NotificationApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /services/order/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /services/order/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PramithaMJ/fully-completed-microservices-Java-Springboot/4a3d1d7c8a678c621371caafebe083e79f105244/services/order/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /services/order/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /services/order/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/#build-image) 9 | * [Spring Web](https://docs.spring.io/spring-boot/docs/3.0.2/reference/htmlsingle/#web) 10 | * [OpenFeign](https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/) 11 | * [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.0.2/reference/htmlsingle/#data.sql.jpa-and-spring-data) 12 | 13 | ### Guides 14 | The following guides illustrate how to use some features concretely: 15 | 16 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 17 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 18 | * [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) 19 | * [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) 20 | 21 | ### Additional Links 22 | These additional references should also help you: 23 | 24 | * [Declarative REST calls with Spring Cloud OpenFeign sample](https://github.com/spring-cloud-samples/feign-eureka) 25 | 26 | -------------------------------------------------------------------------------- /services/order/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /services/order/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.5 9 | 10 | 11 | com.alibou 12 | order 13 | 0.0.1-SNAPSHOT 14 | order 15 | order 16 | 17 | 17 18 | 2023.0.1 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-data-jpa 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-validation 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | org.springframework.kafka 35 | spring-kafka 36 | 37 | 38 | org.springframework.cloud 39 | spring-cloud-starter-config 40 | 41 | 42 | org.springframework.cloud 43 | spring-cloud-starter-openfeign 44 | 45 | 46 | org.springframework.cloud 47 | spring-cloud-starter-netflix-eureka-client 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-actuator 53 | 54 | 55 | 56 | io.micrometer 57 | micrometer-tracing-bridge-brave 58 | 59 | 60 | io.zipkin.reporter2 61 | zipkin-reporter-brave 62 | 63 | 64 | 65 | org.postgresql 66 | postgresql 67 | runtime 68 | 69 | 70 | org.projectlombok 71 | lombok 72 | true 73 | 74 | 75 | org.springframework.boot 76 | spring-boot-starter-test 77 | test 78 | 79 | 80 | 81 | 82 | 83 | org.springframework.cloud 84 | spring-cloud-dependencies 85 | ${spring-cloud.version} 86 | pom 87 | import 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-maven-plugin 97 | 98 | 99 | 100 | org.projectlombok 101 | lombok 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/OrderApplication.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.openfeign.EnableFeignClients; 6 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 7 | 8 | @EnableJpaAuditing 9 | @EnableFeignClients 10 | @SpringBootApplication 11 | public class OrderApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(OrderApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/config/KafkaOrderTopicConfig.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.config; 2 | 3 | import org.apache.kafka.clients.admin.NewTopic; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.kafka.config.TopicBuilder; 7 | 8 | @Configuration 9 | public class KafkaOrderTopicConfig { 10 | 11 | @Bean 12 | public NewTopic orderTopic() { 13 | return TopicBuilder 14 | .name("order-topic") 15 | .build(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/config/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.client.RestTemplate; 6 | 7 | @Configuration 8 | public class RestTemplateConfig { 9 | 10 | @Bean 11 | public RestTemplate restTemplate() { 12 | return new RestTemplate(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/customer/CustomerClient.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.customer; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.PathVariable; 6 | 7 | import java.util.Optional; 8 | 9 | @FeignClient( 10 | name = "customer-service", 11 | url = "${application.config.customer-url}" 12 | ) 13 | public interface CustomerClient { 14 | 15 | @GetMapping("/{customer-id}") 16 | Optional findCustomerById(@PathVariable("customer-id") String customerId); 17 | } 18 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/customer/CustomerResponse.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.customer; 2 | 3 | public record CustomerResponse( 4 | String id, 5 | String firstname, 6 | String lastname, 7 | String email 8 | ) { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.exception; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | @EqualsAndHashCode(callSuper = true) 7 | @Data 8 | public class BusinessException extends RuntimeException { 9 | 10 | private final String msg; 11 | } 12 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/handler/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.handler; 2 | 3 | import java.util.Map; 4 | 5 | public record ErrorResponse( 6 | Map errors 7 | ) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/handler/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.handler; 2 | 3 | import com.alibou.ecommerce.exception.BusinessException; 4 | import jakarta.persistence.EntityNotFoundException; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.validation.FieldError; 8 | import org.springframework.web.bind.MethodArgumentNotValidException; 9 | import org.springframework.web.bind.annotation.ExceptionHandler; 10 | import org.springframework.web.bind.annotation.RestControllerAdvice; 11 | 12 | import java.util.HashMap; 13 | 14 | import static org.springframework.http.HttpStatus.BAD_REQUEST; 15 | 16 | @RestControllerAdvice 17 | public class GlobalExceptionHandler { 18 | 19 | @ExceptionHandler(EntityNotFoundException.class) 20 | public ResponseEntity handle(EntityNotFoundException exp) { 21 | return ResponseEntity 22 | .status(HttpStatus.NOT_FOUND) 23 | .body(exp.getMessage()); 24 | } 25 | 26 | @ExceptionHandler(MethodArgumentNotValidException.class) 27 | public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException exp) { 28 | var errors = new HashMap(); 29 | exp.getBindingResult().getAllErrors() 30 | .forEach(error -> { 31 | var fieldName = ((FieldError) error).getField(); 32 | var errorMessage = error.getDefaultMessage(); 33 | errors.put(fieldName, errorMessage); 34 | }); 35 | 36 | return ResponseEntity 37 | .status(BAD_REQUEST) 38 | .body(new ErrorResponse(errors)); 39 | } 40 | 41 | @ExceptionHandler(BusinessException.class) 42 | public ResponseEntity handle(BusinessException exp) { 43 | return ResponseEntity 44 | .status(HttpStatus.BAD_REQUEST) 45 | .body(exp.getMsg()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/kafka/OrderConfirmation.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.kafka; 2 | 3 | import com.alibou.ecommerce.customer.CustomerResponse; 4 | import com.alibou.ecommerce.order.PaymentMethod; 5 | import com.alibou.ecommerce.product.PurchaseResponse; 6 | 7 | import java.math.BigDecimal; 8 | import java.util.List; 9 | 10 | public record OrderConfirmation ( 11 | String orderReference, 12 | BigDecimal totalAmount, 13 | PaymentMethod paymentMethod, 14 | CustomerResponse customer, 15 | List products 16 | 17 | ) { 18 | } 19 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/kafka/OrderProducer.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.kafka; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.kafka.core.KafkaTemplate; 6 | import org.springframework.messaging.Message; 7 | import org.springframework.messaging.support.MessageBuilder; 8 | import org.springframework.stereotype.Service; 9 | 10 | import static org.springframework.kafka.support.KafkaHeaders.TOPIC; 11 | 12 | @Service 13 | @RequiredArgsConstructor 14 | @Slf4j 15 | public class OrderProducer { 16 | 17 | private final KafkaTemplate kafkaTemplate; 18 | 19 | public void sendOrderConfirmation(OrderConfirmation orderConfirmation) { 20 | log.info("Sending order confirmation"); 21 | Message message = MessageBuilder 22 | .withPayload(orderConfirmation) 23 | .setHeader(TOPIC, "order-topic") 24 | .build(); 25 | 26 | kafkaTemplate.send(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/order/Order.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.order; 2 | 3 | import com.alibou.ecommerce.orderline.OrderLine; 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.EntityListeners; 7 | import jakarta.persistence.EnumType; 8 | import jakarta.persistence.Enumerated; 9 | import jakarta.persistence.GeneratedValue; 10 | import jakarta.persistence.Id; 11 | import jakarta.persistence.OneToMany; 12 | import jakarta.persistence.Table; 13 | import java.math.BigDecimal; 14 | import java.time.LocalDateTime; 15 | import java.util.List; 16 | 17 | import lombok.AllArgsConstructor; 18 | import lombok.Builder; 19 | import lombok.Getter; 20 | import lombok.NoArgsConstructor; 21 | import lombok.Setter; 22 | import org.springframework.data.annotation.CreatedDate; 23 | import org.springframework.data.annotation.LastModifiedDate; 24 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 25 | 26 | @AllArgsConstructor 27 | @Builder 28 | @Getter 29 | @Setter 30 | @Entity 31 | @EntityListeners(AuditingEntityListener.class) 32 | @NoArgsConstructor 33 | @Table(name = "customer_order") 34 | public class Order { 35 | 36 | @Id 37 | @GeneratedValue 38 | private Integer id; 39 | 40 | @Column(unique = true, nullable = false) 41 | private String reference; 42 | 43 | private BigDecimal totalAmount; 44 | 45 | @Enumerated(EnumType.STRING) 46 | private PaymentMethod paymentMethod; 47 | 48 | private String customerId; 49 | 50 | @OneToMany(mappedBy = "order") 51 | private List orderLines; 52 | 53 | @CreatedDate 54 | @Column(updatable = false, nullable = false) 55 | private LocalDateTime createdDate; 56 | 57 | @LastModifiedDate 58 | @Column(insertable = false) 59 | private LocalDateTime lastModifiedDate; 60 | } 61 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/order/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.order; 2 | 3 | import java.util.List; 4 | 5 | import jakarta.validation.Valid; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | @RestController 16 | @RequestMapping("/api/v1/orders") 17 | @RequiredArgsConstructor 18 | public class OrderController { 19 | 20 | private final OrderService service; 21 | 22 | @PostMapping 23 | public ResponseEntity createOrder( 24 | @RequestBody @Valid OrderRequest request 25 | ) { 26 | return ResponseEntity.ok(this.service.createOrder(request)); 27 | } 28 | 29 | @GetMapping 30 | public ResponseEntity> findAll() { 31 | return ResponseEntity.ok(this.service.findAllOrders()); 32 | } 33 | 34 | @GetMapping("/{order-id}") 35 | public ResponseEntity findById( 36 | @PathVariable("order-id") Integer orderId 37 | ) { 38 | return ResponseEntity.ok(this.service.findById(orderId)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/order/OrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.order; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | @Service 6 | public class OrderMapper { 7 | 8 | 9 | public Order toOrder(OrderRequest request) { 10 | if (request == null) { 11 | return null; 12 | } 13 | return Order.builder() 14 | .id(request.id()) 15 | .reference(request.reference()) 16 | .paymentMethod(request.paymentMethod()) 17 | .customerId(request.customerId()) 18 | .build(); 19 | } 20 | 21 | public OrderResponse fromOrder(Order order) { 22 | return new OrderResponse( 23 | order.getId(), 24 | order.getReference(), 25 | order.getTotalAmount(), 26 | order.getPaymentMethod(), 27 | order.getCustomerId() 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/order/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.order; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface OrderRepository extends JpaRepository { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/order/OrderRequest.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.order; 2 | 3 | import com.alibou.ecommerce.product.PurchaseRequest; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 6 | import jakarta.validation.constraints.NotBlank; 7 | import jakarta.validation.constraints.NotEmpty; 8 | import jakarta.validation.constraints.NotNull; 9 | import jakarta.validation.constraints.Positive; 10 | import java.math.BigDecimal; 11 | import java.util.List; 12 | 13 | @JsonInclude(Include.NON_EMPTY) 14 | public record OrderRequest( 15 | Integer id, 16 | String reference, 17 | @Positive(message = "Order amount should be positive") 18 | BigDecimal amount, 19 | @NotNull(message = "Payment method should be precised") 20 | PaymentMethod paymentMethod, 21 | @NotNull(message = "Customer should be present") 22 | @NotEmpty(message = "Customer should be present") 23 | @NotBlank(message = "Customer should be present") 24 | String customerId, 25 | @NotEmpty(message = "You should at least purchase one product") 26 | List products 27 | ) { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/order/OrderResponse.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.order; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 5 | import java.math.BigDecimal; 6 | 7 | @JsonInclude(Include.NON_EMPTY) 8 | public record OrderResponse( 9 | Integer id, 10 | String reference, 11 | BigDecimal amount, 12 | PaymentMethod paymentMethod, 13 | String customerId 14 | ) { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/order/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.order; 2 | 3 | import com.alibou.ecommerce.kafka.OrderConfirmation; 4 | import com.alibou.ecommerce.customer.CustomerClient; 5 | import com.alibou.ecommerce.exception.BusinessException; 6 | import com.alibou.ecommerce.kafka.OrderProducer; 7 | import com.alibou.ecommerce.orderline.OrderLineRequest; 8 | import com.alibou.ecommerce.orderline.OrderLineService; 9 | import com.alibou.ecommerce.payment.PaymentClient; 10 | import com.alibou.ecommerce.payment.PaymentRequest; 11 | import com.alibou.ecommerce.product.ProductClient; 12 | import com.alibou.ecommerce.product.PurchaseRequest; 13 | import jakarta.persistence.EntityNotFoundException; 14 | import lombok.RequiredArgsConstructor; 15 | import org.springframework.stereotype.Service; 16 | import org.springframework.transaction.annotation.Transactional; 17 | 18 | import java.util.List; 19 | import java.util.stream.Collectors; 20 | 21 | @Service 22 | @RequiredArgsConstructor 23 | public class OrderService { 24 | 25 | private final OrderRepository repository; 26 | private final OrderMapper mapper; 27 | private final CustomerClient customerClient; 28 | private final PaymentClient paymentClient; 29 | private final ProductClient productClient; 30 | private final OrderLineService orderLineService; 31 | private final OrderProducer orderProducer; 32 | 33 | @Transactional 34 | public Integer createOrder(OrderRequest request) { 35 | var customer = this.customerClient.findCustomerById(request.customerId()) 36 | .orElseThrow(() -> new BusinessException("Cannot create order:: No customer exists with the provided ID")); 37 | 38 | var purchasedProducts = productClient.purchaseProducts(request.products()); 39 | 40 | var order = this.repository.save(mapper.toOrder(request)); 41 | 42 | for (PurchaseRequest purchaseRequest : request.products()) { 43 | orderLineService.saveOrderLine( 44 | new OrderLineRequest( 45 | null, 46 | order.getId(), 47 | purchaseRequest.productId(), 48 | purchaseRequest.quantity() 49 | ) 50 | ); 51 | } 52 | var paymentRequest = new PaymentRequest( 53 | request.amount(), 54 | request.paymentMethod(), 55 | order.getId(), 56 | order.getReference(), 57 | customer 58 | ); 59 | paymentClient.requestOrderPayment(paymentRequest); 60 | 61 | orderProducer.sendOrderConfirmation( 62 | new OrderConfirmation( 63 | request.reference(), 64 | request.amount(), 65 | request.paymentMethod(), 66 | customer, 67 | purchasedProducts 68 | ) 69 | ); 70 | 71 | return order.getId(); 72 | } 73 | 74 | public List findAllOrders() { 75 | return this.repository.findAll() 76 | .stream() 77 | .map(this.mapper::fromOrder) 78 | .collect(Collectors.toList()); 79 | } 80 | 81 | public OrderResponse findById(Integer id) { 82 | return this.repository.findById(id) 83 | .map(this.mapper::fromOrder) 84 | .orElseThrow(() -> new EntityNotFoundException(String.format("No order found with the provided ID: %d", id))); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/order/PaymentMethod.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.order; 2 | 3 | public enum PaymentMethod { 4 | 5 | PAYPAL, 6 | CREDIT_CARD, 7 | VISA, 8 | MASTER_CARD, 9 | BITCOIN 10 | } 11 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/orderline/OrderLine.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.orderline; 2 | 3 | 4 | import com.alibou.ecommerce.order.Order; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.JoinColumn; 9 | import jakarta.persistence.ManyToOne; 10 | import jakarta.persistence.Table; 11 | import lombok.AllArgsConstructor; 12 | import lombok.Builder; 13 | import lombok.Getter; 14 | import lombok.NoArgsConstructor; 15 | import lombok.Setter; 16 | 17 | @AllArgsConstructor 18 | @Builder 19 | @Getter 20 | @Setter 21 | @Entity 22 | @NoArgsConstructor 23 | @Table(name = "customer_line") 24 | public class OrderLine { 25 | 26 | @Id 27 | @GeneratedValue 28 | private Integer id; 29 | @ManyToOne 30 | @JoinColumn(name = "order_id") 31 | private Order order; 32 | private Integer productId; 33 | private double quantity; 34 | } 35 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/orderline/OrderLineController.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.orderline; 2 | 3 | 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.List; 12 | 13 | @RestController 14 | @RequestMapping("/api/v1/order-lines") 15 | @RequiredArgsConstructor 16 | public class OrderLineController { 17 | 18 | private final OrderLineService service; 19 | 20 | @GetMapping("/order/{order-id}") 21 | public ResponseEntity> findByOrderId( 22 | @PathVariable("order-id") Integer orderId 23 | ) { 24 | return ResponseEntity.ok(service.findAllByOrderId(orderId)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/orderline/OrderLineMapper.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.orderline; 2 | 3 | import com.alibou.ecommerce.order.Order; 4 | import org.springframework.stereotype.Service; 5 | 6 | @Service 7 | public class OrderLineMapper { 8 | public OrderLine toOrderLine(OrderLineRequest request) { 9 | return OrderLine.builder() 10 | .id(request.orderId()) 11 | .productId(request.productId()) 12 | .order( 13 | Order.builder() 14 | .id(request.orderId()) 15 | .build() 16 | ) 17 | .quantity(request.quantity()) 18 | .build(); 19 | } 20 | 21 | public OrderLineResponse toOrderLineResponse(OrderLine orderLine) { 22 | return new OrderLineResponse( 23 | orderLine.getId(), 24 | orderLine.getQuantity() 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/orderline/OrderLineRepository.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.orderline; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.List; 6 | 7 | public interface OrderLineRepository extends JpaRepository { 8 | 9 | List findAllByOrderId(Integer orderId); 10 | } 11 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/orderline/OrderLineRequest.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.orderline; 2 | 3 | public record OrderLineRequest( 4 | Integer id, 5 | Integer orderId, 6 | Integer productId, 7 | double quantity 8 | ) { 9 | } 10 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/orderline/OrderLineResponse.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.orderline; 2 | 3 | public record OrderLineResponse( 4 | Integer id, 5 | double quantity 6 | ) { } 7 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/orderline/OrderLineService.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.orderline; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | @Service 10 | @RequiredArgsConstructor 11 | public class OrderLineService { 12 | 13 | private final OrderLineRepository repository; 14 | private final OrderLineMapper mapper; 15 | 16 | public Integer saveOrderLine(OrderLineRequest request) { 17 | var order = mapper.toOrderLine(request); 18 | return repository.save(order).getId(); 19 | } 20 | 21 | public List findAllByOrderId(Integer orderId) { 22 | return repository.findAllByOrderId(orderId) 23 | .stream() 24 | .map(mapper::toOrderLineResponse) 25 | .collect(Collectors.toList()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/payment/PaymentClient.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.payment; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.PostMapping; 5 | import org.springframework.web.bind.annotation.RequestBody; 6 | 7 | @FeignClient( 8 | name = "product-service", 9 | url = "${application.config.payment-url}" 10 | ) 11 | public interface PaymentClient { 12 | 13 | @PostMapping 14 | Integer requestOrderPayment(@RequestBody PaymentRequest request); 15 | } 16 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/payment/PaymentRequest.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.payment; 2 | 3 | import com.alibou.ecommerce.customer.CustomerResponse; 4 | import com.alibou.ecommerce.order.PaymentMethod; 5 | 6 | import java.math.BigDecimal; 7 | 8 | public record PaymentRequest( 9 | BigDecimal amount, 10 | PaymentMethod paymentMethod, 11 | Integer orderId, 12 | String orderReference, 13 | CustomerResponse customer 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/product/ProductClient.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import com.alibou.ecommerce.exception.BusinessException; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.core.ParameterizedTypeReference; 7 | import org.springframework.http.HttpEntity; 8 | import org.springframework.http.HttpHeaders; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.web.client.RestTemplate; 12 | 13 | import java.util.List; 14 | 15 | import static org.springframework.http.HttpHeaders.CONTENT_TYPE; 16 | import static org.springframework.http.HttpMethod.POST; 17 | import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; 18 | 19 | @Service 20 | @RequiredArgsConstructor 21 | public class ProductClient { 22 | 23 | @Value("${application.config.product-url}") 24 | private String productUrl; 25 | private final RestTemplate restTemplate; 26 | 27 | public List purchaseProducts(List requestBody) { 28 | HttpHeaders headers = new HttpHeaders(); 29 | headers.set(CONTENT_TYPE, APPLICATION_JSON_VALUE); 30 | 31 | HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); 32 | ParameterizedTypeReference> responseType = new ParameterizedTypeReference<>() { 33 | }; 34 | ResponseEntity> responseEntity = restTemplate.exchange( 35 | productUrl + "/purchase", 36 | POST, 37 | requestEntity, 38 | responseType 39 | ); 40 | 41 | if (responseEntity.getStatusCode().isError()) { 42 | throw new BusinessException("An error occurred while processing the products purchase: " + responseEntity.getStatusCode()); 43 | } 44 | return responseEntity.getBody(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/product/PurchaseRequest.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import jakarta.validation.constraints.Positive; 5 | import org.springframework.validation.annotation.Validated; 6 | 7 | @Validated 8 | public record PurchaseRequest( 9 | @NotNull(message = "Product is mandatory") 10 | Integer productId, 11 | @Positive(message = "Quantity is mandatory") 12 | double quantity 13 | ) { 14 | } 15 | -------------------------------------------------------------------------------- /services/order/src/main/java/com/alibou/ecommerce/product/PurchaseResponse.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record PurchaseResponse( 6 | Integer productId, 7 | String name, 8 | String description, 9 | BigDecimal price, 10 | double quantity 11 | ) { 12 | } 13 | -------------------------------------------------------------------------------- /services/order/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | config: 3 | import: optional:configserver:http://localhost:8888 4 | application: 5 | name: order-service 6 | -------------------------------------------------------------------------------- /services/order/src/test/java/com/alibou/ecommerce/OrderApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class OrderApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /services/payment/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /services/payment/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PramithaMJ/fully-completed-microservices-Java-Springboot/4a3d1d7c8a678c621371caafebe083e79f105244/services/payment/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /services/payment/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /services/payment/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/#build-image) 9 | * [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.0.2/reference/htmlsingle/#data.sql.jpa-and-spring-data) 10 | * [Spring Web](https://docs.spring.io/spring-boot/docs/3.0.2/reference/htmlsingle/#web) 11 | * [Config Client Quick Start](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_client_side_usage) 12 | 13 | ### Guides 14 | The following guides illustrate how to use some features concretely: 15 | 16 | * [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) 17 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 18 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 19 | * [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) 20 | 21 | -------------------------------------------------------------------------------- /services/payment/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /services/payment/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.5 9 | 10 | 11 | com.alibou 12 | payment 13 | 0.0.1-SNAPSHOT 14 | payment 15 | payment 16 | 17 | 17 18 | 2023.0.1 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-data-jpa 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-web 28 | 29 | 30 | org.springframework.cloud 31 | spring-cloud-starter-config 32 | 33 | 34 | org.springframework.cloud 35 | spring-cloud-starter-openfeign 36 | 37 | 38 | org.springframework.cloud 39 | spring-cloud-starter-netflix-eureka-client 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-validation 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-actuator 48 | 49 | 50 | org.springframework.kafka 51 | spring-kafka 52 | 53 | 54 | 55 | org.postgresql 56 | postgresql 57 | runtime 58 | 59 | 60 | org.projectlombok 61 | lombok 62 | true 63 | 64 | 65 | 66 | io.micrometer 67 | micrometer-tracing-bridge-brave 68 | 69 | 70 | io.zipkin.reporter2 71 | zipkin-reporter-brave 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-starter-test 76 | test 77 | 78 | 79 | 80 | 81 | 82 | org.springframework.cloud 83 | spring-cloud-dependencies 84 | ${spring-cloud.version} 85 | pom 86 | import 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | org.springframework.boot 95 | spring-boot-maven-plugin 96 | 97 | 98 | 99 | org.projectlombok 100 | lombok 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/PaymentApplication.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 6 | 7 | @EnableJpaAuditing 8 | @SpringBootApplication 9 | public class PaymentApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(PaymentApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/configuration/KafkaPaymentTopicConfig.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.configuration; 2 | 3 | import org.apache.kafka.clients.admin.NewTopic; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.kafka.config.TopicBuilder; 7 | 8 | @Configuration 9 | public class KafkaPaymentTopicConfig { 10 | 11 | @Bean 12 | public NewTopic paymentTopic() { 13 | return TopicBuilder 14 | .name("payment-topic") 15 | .build(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.exception; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | @EqualsAndHashCode(callSuper = true) 7 | @Data 8 | public class BusinessException extends RuntimeException { 9 | 10 | private final String msg; 11 | } 12 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/handler/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.handler; 2 | 3 | import java.util.Map; 4 | 5 | public record ErrorResponse( 6 | Map errors 7 | ) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/handler/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.handler; 2 | 3 | import com.alibou.ecommerce.exception.BusinessException; 4 | import jakarta.persistence.EntityNotFoundException; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.validation.FieldError; 8 | import org.springframework.web.bind.MethodArgumentNotValidException; 9 | import org.springframework.web.bind.annotation.ExceptionHandler; 10 | import org.springframework.web.bind.annotation.RestControllerAdvice; 11 | 12 | import java.util.HashMap; 13 | 14 | import static org.springframework.http.HttpStatus.BAD_REQUEST; 15 | 16 | @RestControllerAdvice 17 | public class GlobalExceptionHandler { 18 | 19 | @ExceptionHandler(EntityNotFoundException.class) 20 | public ResponseEntity handle(EntityNotFoundException exp) { 21 | return ResponseEntity 22 | .status(HttpStatus.NOT_FOUND) 23 | .body(exp.getMessage()); 24 | } 25 | 26 | @ExceptionHandler(MethodArgumentNotValidException.class) 27 | public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException exp) { 28 | var errors = new HashMap(); 29 | exp.getBindingResult().getAllErrors() 30 | .forEach(error -> { 31 | var fieldName = ((FieldError) error).getField(); 32 | var errorMessage = error.getDefaultMessage(); 33 | errors.put(fieldName, errorMessage); 34 | }); 35 | 36 | return ResponseEntity 37 | .status(BAD_REQUEST) 38 | .body(new ErrorResponse(errors)); 39 | } 40 | 41 | @ExceptionHandler(BusinessException.class) 42 | public ResponseEntity handle(BusinessException exp) { 43 | return ResponseEntity 44 | .status(HttpStatus.BAD_REQUEST) 45 | .body(exp.getMsg()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/notification/NotificationProducer.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.notification; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.kafka.core.KafkaTemplate; 6 | import org.springframework.messaging.Message; 7 | import org.springframework.messaging.support.MessageBuilder; 8 | import org.springframework.stereotype.Service; 9 | 10 | import static org.springframework.kafka.support.KafkaHeaders.TOPIC; 11 | 12 | @Service 13 | @RequiredArgsConstructor 14 | @Slf4j 15 | public class NotificationProducer { 16 | 17 | private final KafkaTemplate kafkaTemplate; 18 | 19 | public void sendNotification(PaymentNotificationRequest request) { 20 | log.info("Sending notification with body = < {} >", request); 21 | Message message = MessageBuilder 22 | .withPayload(request) 23 | .setHeader(TOPIC, "payment-topic") 24 | .build(); 25 | 26 | kafkaTemplate.send(message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/notification/PaymentNotificationRequest.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.notification; 2 | 3 | import com.alibou.ecommerce.payment.PaymentMethod; 4 | 5 | import java.math.BigDecimal; 6 | 7 | public record PaymentNotificationRequest( 8 | String orderReference, 9 | BigDecimal amount, 10 | PaymentMethod paymentMethod, 11 | String customerFirstname, 12 | String customerLastname, 13 | String customerEmail 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/payment/Customer.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.payment; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotNull; 5 | import org.springframework.validation.annotation.Validated; 6 | 7 | @Validated 8 | public record Customer( 9 | String id, 10 | @NotNull(message = "Firstname is required") 11 | String firstname, 12 | @NotNull(message = "Lastname is required") 13 | String lastname, 14 | @NotNull(message = "Email is required") 15 | @Email(message = "The customer email is not correctly formatted") 16 | String email 17 | ) { } 18 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/payment/Payment.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.payment; 2 | 3 | 4 | import jakarta.persistence.Column; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.EntityListeners; 7 | import jakarta.persistence.EnumType; 8 | import jakarta.persistence.Enumerated; 9 | import jakarta.persistence.GeneratedValue; 10 | import jakarta.persistence.Id; 11 | import jakarta.persistence.Table; 12 | import java.math.BigDecimal; 13 | import java.time.LocalDateTime; 14 | import lombok.AllArgsConstructor; 15 | import lombok.Builder; 16 | import lombok.Getter; 17 | import lombok.NoArgsConstructor; 18 | import lombok.Setter; 19 | import org.springframework.data.annotation.CreatedDate; 20 | import org.springframework.data.annotation.LastModifiedDate; 21 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 22 | 23 | @AllArgsConstructor 24 | @Builder 25 | @Getter 26 | @Setter 27 | @Entity 28 | @EntityListeners(AuditingEntityListener.class) 29 | @NoArgsConstructor 30 | @Table(name = "payment") 31 | public class Payment { 32 | 33 | @Id 34 | @GeneratedValue 35 | private Integer id; 36 | 37 | private BigDecimal amount; 38 | 39 | @Enumerated(EnumType.STRING) 40 | private PaymentMethod paymentMethod; 41 | 42 | private Integer orderId; 43 | 44 | @CreatedDate 45 | @Column(updatable = false, nullable = false) 46 | private LocalDateTime createdDate; 47 | 48 | @LastModifiedDate 49 | @Column(insertable = false) 50 | private LocalDateTime lastModifiedDate; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/payment/PaymentController.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.payment; 2 | 3 | import jakarta.validation.Valid; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestBody; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | @RequestMapping("/api/v1/payments") 13 | @RequiredArgsConstructor 14 | public class PaymentController { 15 | 16 | private final PaymentService service; 17 | 18 | @PostMapping 19 | public ResponseEntity createPayment( 20 | @RequestBody @Valid PaymentRequest request 21 | ) { 22 | return ResponseEntity.ok(this.service.createPayment(request)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/payment/PaymentMapper.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.payment; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | @Service 6 | public class PaymentMapper { 7 | 8 | public Payment toPayment(PaymentRequest request) { 9 | if (request == null) { 10 | return null; 11 | } 12 | return Payment.builder() 13 | .id(request.id()) 14 | .paymentMethod(request.paymentMethod()) 15 | .amount(request.amount()) 16 | .orderId(request.orderId()) 17 | .build(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/payment/PaymentMethod.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.payment; 2 | 3 | public enum PaymentMethod { 4 | 5 | PAYPAL, 6 | CREDIT_CARD, 7 | VISA, 8 | MASTER_CARD, 9 | BITCOIN 10 | } 11 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/payment/PaymentRepository.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.payment; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface PaymentRepository extends JpaRepository { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/payment/PaymentRequest.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.payment; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record PaymentRequest( 6 | Integer id, 7 | BigDecimal amount, 8 | PaymentMethod paymentMethod, 9 | Integer orderId, 10 | String orderReference, 11 | Customer customer 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /services/payment/src/main/java/com/alibou/ecommerce/payment/PaymentService.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.payment; 2 | 3 | import com.alibou.ecommerce.notification.NotificationProducer; 4 | import com.alibou.ecommerce.notification.PaymentNotificationRequest; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | @RequiredArgsConstructor 10 | public class PaymentService { 11 | 12 | private final PaymentRepository repository; 13 | private final PaymentMapper mapper; 14 | private final NotificationProducer notificationProducer; 15 | 16 | public Integer createPayment(PaymentRequest request) { 17 | var payment = this.repository.save(this.mapper.toPayment(request)); 18 | 19 | this.notificationProducer.sendNotification( 20 | new PaymentNotificationRequest( 21 | request.orderReference(), 22 | request.amount(), 23 | request.paymentMethod(), 24 | request.customer().firstname(), 25 | request.customer().lastname(), 26 | request.customer().email() 27 | ) 28 | ); 29 | return payment.getId(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /services/payment/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | config: 3 | import: optional:configserver:http://localhost:8888 4 | application: 5 | name: payment-service 6 | -------------------------------------------------------------------------------- /services/payment/src/test/java/com/alibou/ecommerce/PaymentApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class PaymentApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /services/product/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /services/product/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PramithaMJ/fully-completed-microservices-Java-Springboot/4a3d1d7c8a678c621371caafebe083e79f105244/services/product/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /services/product/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /services/product/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.0.2/maven-plugin/reference/html/#build-image) 9 | * [Spring Web](https://docs.spring.io/spring-boot/docs/3.0.2/reference/htmlsingle/#web) 10 | * [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.0.2/reference/htmlsingle/#data.sql.jpa-and-spring-data) 11 | * [Config Client Quick Start](https://docs.spring.io/spring-cloud-config/docs/current/reference/html/#_client_side_usage) 12 | 13 | ### Guides 14 | The following guides illustrate how to use some features concretely: 15 | 16 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 17 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 18 | * [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) 19 | * [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) 20 | 21 | -------------------------------------------------------------------------------- /services/product/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /services/product/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.5 9 | 10 | 11 | com.alibou 12 | product 13 | 0.0.1-SNAPSHOT 14 | product 15 | product 16 | 17 | 17 18 | 2023.0.1 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-data-jpa 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-web 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-validation 32 | 33 | 34 | org.springframework.cloud 35 | spring-cloud-starter-config 36 | 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-starter-netflix-eureka-client 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-actuator 46 | 47 | 48 | 49 | io.micrometer 50 | micrometer-tracing-bridge-brave 51 | 52 | 53 | io.zipkin.reporter2 54 | zipkin-reporter-brave 55 | 56 | 57 | org.flywaydb 58 | flyway-core 59 | 60 | 61 | 62 | org.projectlombok 63 | lombok 64 | true 65 | 66 | 67 | org.postgresql 68 | postgresql 69 | runtime 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-starter-test 74 | test 75 | 76 | 77 | 78 | 79 | 80 | org.springframework.cloud 81 | spring-cloud-dependencies 82 | ${spring-cloud.version} 83 | pom 84 | import 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | org.springframework.boot 93 | spring-boot-maven-plugin 94 | 95 | 96 | 97 | org.projectlombok 98 | lombok 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/ProductApplication.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ProductApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ProductApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/category/Category.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.category; 2 | 3 | import com.alibou.ecommerce.product.Product; 4 | import jakarta.persistence.CascadeType; 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.OneToMany; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Getter; 12 | import lombok.NoArgsConstructor; 13 | import lombok.Setter; 14 | 15 | import java.util.List; 16 | 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | @Builder 20 | @Getter 21 | @Setter 22 | @Entity 23 | public class Category { 24 | 25 | @Id 26 | @GeneratedValue 27 | private Integer id; 28 | private String name; 29 | private String description; 30 | @OneToMany(mappedBy = "category", cascade = CascadeType.REMOVE) 31 | private List products; 32 | } 33 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/exception/ProductPurchaseException.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.exception; 2 | 3 | public class ProductPurchaseException extends RuntimeException { 4 | public ProductPurchaseException(String s) { 5 | super(s); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/handler/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.handler; 2 | 3 | import java.util.Map; 4 | 5 | public record ErrorResponse( 6 | Map errors 7 | ) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/handler/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.handler; 2 | 3 | import com.alibou.ecommerce.exception.ProductPurchaseException; 4 | import jakarta.persistence.EntityNotFoundException; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.validation.FieldError; 7 | import org.springframework.web.bind.MethodArgumentNotValidException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | 11 | import java.util.HashMap; 12 | 13 | import static org.springframework.http.HttpStatus.BAD_REQUEST; 14 | 15 | @RestControllerAdvice 16 | public class GlobalExceptionHandler { 17 | 18 | @ExceptionHandler(ProductPurchaseException.class) 19 | public ResponseEntity handle(ProductPurchaseException exp) { 20 | return ResponseEntity 21 | .status(BAD_REQUEST) 22 | .body(exp.getMessage()); 23 | } 24 | 25 | @ExceptionHandler(EntityNotFoundException.class) 26 | public ResponseEntity handle(EntityNotFoundException exp) { 27 | return ResponseEntity 28 | .status(BAD_REQUEST) 29 | .body(exp.getMessage()); 30 | } 31 | 32 | @ExceptionHandler(MethodArgumentNotValidException.class) 33 | public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException exp) { 34 | var errors = new HashMap(); 35 | exp.getBindingResult().getAllErrors() 36 | .forEach(error -> { 37 | var fieldName = ((FieldError) error).getField(); 38 | var errorMessage = error.getDefaultMessage(); 39 | errors.put(fieldName, errorMessage); 40 | }); 41 | 42 | return ResponseEntity 43 | .status(BAD_REQUEST) 44 | .body(new ErrorResponse(errors)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/product/Product.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import com.alibou.ecommerce.category.Category; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.Id; 7 | import jakarta.persistence.JoinColumn; 8 | import jakarta.persistence.ManyToOne; 9 | import lombok.AllArgsConstructor; 10 | import lombok.Builder; 11 | import lombok.Getter; 12 | import lombok.NoArgsConstructor; 13 | import lombok.Setter; 14 | 15 | import java.math.BigDecimal; 16 | 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | @Builder 20 | @Getter 21 | @Setter 22 | @Entity 23 | public class Product { 24 | 25 | @Id 26 | @GeneratedValue 27 | private Integer id; 28 | private String name; 29 | private String description; 30 | private double availableQuantity; 31 | private BigDecimal price; 32 | @ManyToOne 33 | @JoinColumn(name = "category_id") 34 | private Category category; 35 | } 36 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/product/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import jakarta.validation.Valid; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import java.util.List; 14 | 15 | @RestController 16 | @RequestMapping("/api/v1/products") 17 | @RequiredArgsConstructor 18 | public class ProductController { 19 | 20 | private final ProductService service; 21 | 22 | @PostMapping 23 | public ResponseEntity createProduct( 24 | @RequestBody @Valid ProductRequest request 25 | ) { 26 | return ResponseEntity.ok(service.createProduct(request)); 27 | } 28 | 29 | @PostMapping("/purchase") 30 | public ResponseEntity> purchaseProducts( 31 | @RequestBody List request 32 | ) { 33 | return ResponseEntity.ok(service.purchaseProducts(request)); 34 | } 35 | 36 | @GetMapping("/{product-id}") 37 | public ResponseEntity findById( 38 | @PathVariable("product-id") Integer productId 39 | ) { 40 | return ResponseEntity.ok(service.findById(productId)); 41 | } 42 | 43 | @GetMapping 44 | public ResponseEntity> findAll() { 45 | return ResponseEntity.ok(service.findAll()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/product/ProductMapper.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import com.alibou.ecommerce.category.Category; 4 | import org.springframework.stereotype.Service; 5 | 6 | @Service 7 | public class ProductMapper { 8 | public Product toProduct(ProductRequest request) { 9 | return Product.builder() 10 | .id(request.id()) 11 | .name(request.name()) 12 | .description(request.description()) 13 | .availableQuantity(request.availableQuantity()) 14 | .price(request.price()) 15 | .category( 16 | Category.builder() 17 | .id(request.categoryId()) 18 | .build() 19 | ) 20 | .build(); 21 | } 22 | 23 | public ProductResponse toProductResponse(Product product) { 24 | return new ProductResponse( 25 | product.getId(), 26 | product.getName(), 27 | product.getDescription(), 28 | product.getAvailableQuantity(), 29 | product.getPrice(), 30 | product.getCategory().getId(), 31 | product.getCategory().getName(), 32 | product.getCategory().getDescription() 33 | ); 34 | } 35 | 36 | public ProductPurchaseResponse toproductPurchaseResponse(Product product, double quantity) { 37 | return new ProductPurchaseResponse( 38 | product.getId(), 39 | product.getName(), 40 | product.getDescription(), 41 | product.getPrice(), 42 | quantity 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/product/ProductPurchaseRequest.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import jakarta.validation.constraints.Positive; 5 | 6 | public record ProductPurchaseRequest( 7 | @NotNull(message = "Product is mandatory") 8 | Integer productId, 9 | @Positive(message = "Quantity is mandatory") 10 | double quantity 11 | ) { 12 | } 13 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/product/ProductPurchaseResponse.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record ProductPurchaseResponse( 6 | Integer productId, 7 | String name, 8 | String description, 9 | BigDecimal price, 10 | double quantity 11 | ) { 12 | } 13 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/product/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.List; 6 | 7 | public interface ProductRepository extends JpaRepository { 8 | 9 | List findAllByIdInOrderById(List ids); 10 | } 11 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/product/ProductRequest.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import jakarta.validation.constraints.Positive; 5 | 6 | import java.math.BigDecimal; 7 | 8 | public record ProductRequest( 9 | 10 | Integer id, 11 | @NotNull(message = "Product name is required") 12 | String name, 13 | @NotNull(message = "Product description is required") 14 | String description, 15 | @Positive(message = "Available quantity should be positive") 16 | double availableQuantity, 17 | @Positive(message = "Price should be positive") 18 | BigDecimal price, 19 | @NotNull(message = "Product category is required") 20 | Integer categoryId 21 | ) { 22 | } 23 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/product/ProductResponse.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public record ProductResponse( 6 | Integer id, 7 | String name, 8 | String description, 9 | double availableQuantity, 10 | BigDecimal price, 11 | Integer categoryId, 12 | String categoryName, 13 | String categoryDescription 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /services/product/src/main/java/com/alibou/ecommerce/product/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce.product; 2 | 3 | import com.alibou.ecommerce.exception.ProductPurchaseException; 4 | import jakarta.persistence.EntityNotFoundException; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Comparator; 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | @Service 15 | @RequiredArgsConstructor 16 | public class ProductService { 17 | 18 | private final ProductRepository repository; 19 | private final ProductMapper mapper; 20 | 21 | public Integer createProduct( 22 | ProductRequest request 23 | ) { 24 | var product = mapper.toProduct(request); 25 | return repository.save(product).getId(); 26 | } 27 | 28 | public ProductResponse findById(Integer id) { 29 | return repository.findById(id) 30 | .map(mapper::toProductResponse) 31 | .orElseThrow(() -> new EntityNotFoundException("Product not found with ID:: " + id)); 32 | } 33 | 34 | public List findAll() { 35 | return repository.findAll() 36 | .stream() 37 | .map(mapper::toProductResponse) 38 | .collect(Collectors.toList()); 39 | } 40 | 41 | @Transactional(rollbackFor = ProductPurchaseException.class) 42 | public List purchaseProducts( 43 | List request 44 | ) { 45 | var productIds = request 46 | .stream() 47 | .map(ProductPurchaseRequest::productId) 48 | .toList(); 49 | var storedProducts = repository.findAllByIdInOrderById(productIds); 50 | if (productIds.size() != storedProducts.size()) { 51 | throw new ProductPurchaseException("One or more products does not exist"); 52 | } 53 | var sortedRequest = request 54 | .stream() 55 | .sorted(Comparator.comparing(ProductPurchaseRequest::productId)) 56 | .toList(); 57 | var purchasedProducts = new ArrayList(); 58 | for (int i = 0; i < storedProducts.size(); i++) { 59 | var product = storedProducts.get(i); 60 | var productRequest = sortedRequest.get(i); 61 | if (product.getAvailableQuantity() < productRequest.quantity()) { 62 | throw new ProductPurchaseException("Insufficient stock quantity for product with ID:: " + productRequest.productId()); 63 | } 64 | var newAvailableQuantity = product.getAvailableQuantity() - productRequest.quantity(); 65 | product.setAvailableQuantity(newAvailableQuantity); 66 | repository.save(product); 67 | purchasedProducts.add(mapper.toproductPurchaseResponse(product, productRequest.quantity())); 68 | } 69 | return purchasedProducts; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /services/product/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | config: 3 | import: optional:configserver:http://localhost:8888 4 | application: 5 | name: product-service 6 | 7 | -------------------------------------------------------------------------------- /services/product/src/main/resources/db/migration/V1__init_database.sql: -------------------------------------------------------------------------------- 1 | create table if not exists category 2 | ( 3 | id integer not null 4 | primary key, 5 | description varchar(255), 6 | name varchar(255) 7 | ); 8 | 9 | create table if not exists product 10 | ( 11 | id integer not null 12 | primary key, 13 | available_quantity double precision not null, 14 | description varchar(255), 15 | name varchar(255), 16 | price numeric(38, 2), 17 | category_id integer 18 | constraint fk1mtsbur82frn64de7balymq9s 19 | references category 20 | ); 21 | 22 | create sequence if not exists category_seq increment by 50; 23 | create sequence if not exists product_seq increment by 50; 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /services/product/src/main/resources/db/migration/V2__insert_data.sql: -------------------------------------------------------------------------------- 1 | 2 | INSERT INTO category (id, description, name) VALUES (nextval('category_seq'), 'Computer Keyboards', 'Keyboards'); 3 | INSERT INTO category (id, description, name) VALUES (nextval('category_seq'), 'Computer Monitors', 'Monitors'); 4 | INSERT INTO category (id, description, name) VALUES (nextval('category_seq'), 'Display Screens', 'Screens'); 5 | INSERT INTO category (id, description, name) VALUES (nextval('category_seq'), 'Computer Mice', 'Mice'); 6 | INSERT INTO category (id, description, name) VALUES (nextval('category_seq'), 'Computer Accessories', 'Accessories'); 7 | 8 | 9 | -- Assuming you already have a sequence named 'product_seq' 10 | 11 | -- Insert products for the 'Keyboards' category 12 | INSERT INTO public.product (id, available_quantity, description, name, price, category_id) 13 | VALUES 14 | (nextval('product_seq'), 10, 'Mechanical keyboard with RGB lighting', 'Mechanical Keyboard 1', 99.99, (SELECT id FROM category WHERE name = 'Keyboards')), 15 | (nextval('product_seq'), 15, 'Wireless compact keyboard', 'Wireless Compact Keyboard 1', 79.99, (SELECT id FROM category WHERE name = 'Keyboards')), 16 | (nextval('product_seq'), 20, 'Backlit gaming keyboard with customizable keys', 'Gaming Keyboard 1', 129.99, (SELECT id FROM category WHERE name = 'Keyboards')), 17 | (nextval('product_seq'), 25, 'Mechanical keyboard with wrist rest', 'Ergonomic Keyboard 1', 109.99, (SELECT id FROM category WHERE name = 'Keyboards')), 18 | (nextval('product_seq'), 18, 'Wireless keyboard and mouse combo', 'Wireless Combo 1', 69.99, (SELECT id FROM category WHERE name = 'Keyboards')); 19 | 20 | -- Insert products for the 'Monitors' category 21 | INSERT INTO public.product (id, available_quantity, description, name, price, category_id) 22 | VALUES 23 | (nextval('product_seq'), 30, '27-inch IPS monitor with 4K resolution', '4K Monitor 1', 399.99, (SELECT id FROM category WHERE name = 'Monitors')), 24 | (nextval('product_seq'), 25, 'Ultra-wide gaming monitor with HDR support', 'Ultra-wide Gaming Monitor 1', 499.99, (SELECT id FROM category WHERE name = 'Monitors')), 25 | (nextval('product_seq'), 22, '24-inch LED monitor for office use', 'Office Monitor 1', 179.99, (SELECT id FROM category WHERE name = 'Monitors')), 26 | (nextval('product_seq'), 28, '32-inch curved monitor with AMD FreeSync', 'Curved Monitor 1', 329.99, (SELECT id FROM category WHERE name = 'Monitors')), 27 | (nextval('product_seq'), 35, 'Portable USB-C monitor for laptops', 'Portable Monitor 1', 249.99, (SELECT id FROM category WHERE name = 'Monitors')); 28 | 29 | -- Insert products for the 'Screens' category 30 | INSERT INTO public.product (id, available_quantity, description, name, price, category_id) 31 | VALUES 32 | (nextval('product_seq'), 15, 'Curved OLED gaming screen with 240Hz refresh rate', 'Curved OLED Gaming Screen 1', 799.99, (SELECT id FROM category WHERE name = 'Screens')), 33 | (nextval('product_seq'), 18, 'Flat QLED monitor with 1440p resolution', 'QLED Monitor 1', 599.99, (SELECT id FROM category WHERE name = 'Screens')), 34 | (nextval('product_seq'), 22, '27-inch touch screen display for creative work', 'Touch Screen Display 1', 699.99, (SELECT id FROM category WHERE name = 'Screens')), 35 | (nextval('product_seq'), 20, 'Ultra-slim 4K HDR display for multimedia', 'Ultra-slim 4K HDR Display 1', 449.99, (SELECT id FROM category WHERE name = 'Screens')), 36 | (nextval('product_seq'), 25, 'Gaming projector with low input lag', 'Gaming Projector 1', 899.99, (SELECT id FROM category WHERE name = 'Screens')); 37 | 38 | -- Insert products for the 'Mice' category 39 | INSERT INTO public.product (id, available_quantity, description, name, price, category_id) 40 | VALUES 41 | (nextval('product_seq'), 30, 'Wireless gaming mouse with customizable RGB lighting', 'RGB Gaming Mouse 1', 59.99, (SELECT id FROM category WHERE name = 'Mice')), 42 | (nextval('product_seq'), 28, 'Ergonomic wired mouse for productivity', 'Ergonomic Wired Mouse 1', 29.99, (SELECT id FROM category WHERE name = 'Mice')), 43 | (nextval('product_seq'), 32, 'Ambidextrous gaming mouse with high DPI', 'Ambidextrous Gaming Mouse 1', 69.99, (SELECT id FROM category WHERE name = 'Mice')), 44 | (nextval('product_seq'), 26, 'Travel-sized compact mouse for laptops', 'Travel Mouse 1', 19.99, (SELECT id FROM category WHERE name = 'Mice')), 45 | (nextval('product_seq'), 35, 'Vertical ergonomic mouse for reduced strain', 'Vertical Ergonomic Mouse 1', 39.99, (SELECT id FROM category WHERE name = 'Mice')); 46 | 47 | -- Insert products for the 'Accessories' category 48 | INSERT INTO public.product (id, available_quantity, description, name, price, category_id) 49 | VALUES 50 | (nextval('product_seq'), 25, 'Adjustable laptop stand with cooling fan', 'Adjustable Laptop Stand 1', 34.99, (SELECT id FROM category WHERE name = 'Accessories')), 51 | (nextval('product_seq'), 20, 'Wireless charging pad for smartphones', 'Wireless Charging Pad 1', 24.99, (SELECT id FROM category WHERE name = 'Accessories')), 52 | (nextval('product_seq'), 28, 'Gaming headset stand with RGB lighting', 'RGB Headset Stand 1', 49.99, (SELECT id FROM category WHERE name = 'Accessories')), 53 | (nextval('product_seq'), 22, 'Bluetooth mechanical keypad for tablets', 'Bluetooth Keypad 1', 39.99, (SELECT id FROM category WHERE name = 'Accessories')), 54 | (nextval('product_seq'), 30, 'External hard drive enclosure with USB-C', 'External Hard Drive Enclosure 1', 29.99, (SELECT id FROM category WHERE name = 'Accessories')); 55 | 56 | -------------------------------------------------------------------------------- /services/product/src/test/java/com/alibou/ecommerce/ProductApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.alibou.ecommerce; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ProductApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------