diff --git a/.github/workflows/order-service.yml b/.github/workflows/order-service.yml index eea3ea6..e3ea75c 100644 --- a/.github/workflows/order-service.yml +++ b/.github/workflows/order-service.yml @@ -1,51 +1,51 @@ -name: Order Service - -on: - push: - paths: - - order-service/** - branches: - - 'main' - pull_request: - branches: [main] - -jobs: - build: - name: Build - runs-on: ubuntu-latest - env: - working-directory: ./order-service - DOCKER_IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/bookstore-order-service - defaults: - run: - working-directory: ${{ env.working-directory }} - steps: - - uses: actions/checkout@v4 - - - name: Setup Java 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - cache: 'maven' - - - name: Make Maven wrapper executable - run: chmod +x mvnw - - - name: Check code formatting (Spotless) - run: ./mvnw spotless:check - - - name: Build with Maven - run: ./mvnw -ntp verify - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and Publish Docker Image - run: | - ./mvnw spring-boot:build-image -DskipTests - echo "Pushing the image $DOCKER_IMAGE_NAME to Docker Hub..." +name: Order Service + +on: + push: + paths: + - order-service/** + branches: + - 'main' + pull_request: + branches: [main] + +jobs: + build: + name: Build + runs-on: ubuntu-latest + env: + working-directory: ./order-service + DOCKER_IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/bookstore-order-service + defaults: + run: + working-directory: ${{ env.working-directory }} + steps: + - uses: actions/checkout@v4 + + - name: Setup Java 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Make Maven wrapper executable + run: chmod +x mvnw + + - name: Check code formatting (Spotless) + run: ./mvnw spotless:check + + - name: Build with Maven + run: ./mvnw -ntp verify + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and Publish Docker Image + run: | + ./mvnw spring-boot:build-image -DskipTests + echo "Pushing the image $DOCKER_IMAGE_NAME to Docker Hub..." docker push $DOCKER_IMAGE_NAME \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 4d24505..b969401 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,19 +1,19 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -wrapperVersion=3.3.2 -distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip diff --git a/Taskfile.yml b/Taskfile.yml index 38e7bce..7d79fa2 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,78 +1,78 @@ -version: '3' - -vars: - GOOS: "{{default OS .GOOS}}" - MVNW: '{{if eq .GOOS "windows"}}mvnw.cmd{{else}}./mvnw{{end}}' - DC_DIR: "deployment/docker-compose" - INFRA_DC_FILE: "{{.DC_DIR}}/infra.yml" - APPS_DC_FILE: "{{.DC_DIR}}/apps.yml" - PORTAINER_DC_FILE: "devtools/docker-compose/portainer.yml" - SLEEP_CMD: '{{if eq .GOOS "windows"}}timeout{{else}}sleep{{end}}' - -tasks: - default: - cmds: - - task: test - test: - deps: [format] - cmds: - - "{{.MVNW}} clean verify" - - format: - cmds: - - "{{.MVNW}} spotless:apply" - - build: - cmds: - - "{{.MVNW}} -pl catalog-service spring-boot:build-image -DskipTests" - - start_infra: - cmds: - - "docker compose -f {{.INFRA_DC_FILE}} up -d" - - stop_infra: - cmds: - - "docker compose -f {{.INFRA_DC_FILE}} stop" - - "docker compose -f {{.INFRA_DC_FILE}} rm -f" - - restart_infra: - cmds: - - task: stop_infra - - task: sleep - - task: start_infra - - start: - cmds: - - "docker compose -f {{.INFRA_DC_FILE}} -f {{.APPS_DC_FILE}} up -d" - - stop: - cmds: - - "docker compose -f {{.INFRA_DC_FILE}} -f {{.APPS_DC_FILE}} stop" - - "docker compose -f {{.INFRA_DC_FILE}} -f {{.APPS_DC_FILE}} rm -f" - - restart: - cmds: - - task: stop - - task: sleep - - task: start - - start_portainer: - cmds: - - "docker compose -f {{.PORTAINER_DC_FILE}} up -d" - - stop_portainer: - cmds: - - "docker compose -f {{.PORTAINER_DC_FILE}} stop" - - "docker compose -f {{.PORTAINER_DC_FILE}} rm -f" - - restart_portainer: - cmds: - - task: stop_portainer - - task: sleep - - task: start_portainer - - sleep: - vars: - DURATION: "{{default 5 .DURATION}}" - cmds: +version: '3' + +vars: + GOOS: "{{default OS .GOOS}}" + MVNW: '{{if eq .GOOS "windows"}}mvnw.cmd{{else}}./mvnw{{end}}' + DC_DIR: "deployment/docker-compose" + INFRA_DC_FILE: "{{.DC_DIR}}/infra.yml" + APPS_DC_FILE: "{{.DC_DIR}}/apps.yml" + PORTAINER_DC_FILE: "devtools/docker-compose/portainer.yml" + SLEEP_CMD: '{{if eq .GOOS "windows"}}timeout{{else}}sleep{{end}}' + +tasks: + default: + cmds: + - task: test + test: + deps: [format] + cmds: + - "{{.MVNW}} clean verify" + + format: + cmds: + - "{{.MVNW}} spotless:apply" + + build: + cmds: + - "{{.MVNW}} -pl catalog-service spring-boot:build-image -DskipTests" + + start_infra: + cmds: + - "docker compose -f {{.INFRA_DC_FILE}} up -d" + + stop_infra: + cmds: + - "docker compose -f {{.INFRA_DC_FILE}} stop" + - "docker compose -f {{.INFRA_DC_FILE}} rm -f" + + restart_infra: + cmds: + - task: stop_infra + - task: sleep + - task: start_infra + + start: + cmds: + - "docker compose -f {{.INFRA_DC_FILE}} -f {{.APPS_DC_FILE}} up -d" + + stop: + cmds: + - "docker compose -f {{.INFRA_DC_FILE}} -f {{.APPS_DC_FILE}} stop" + - "docker compose -f {{.INFRA_DC_FILE}} -f {{.APPS_DC_FILE}} rm -f" + + restart: + cmds: + - task: stop + - task: sleep + - task: start + + start_portainer: + cmds: + - "docker compose -f {{.PORTAINER_DC_FILE}} up -d" + + stop_portainer: + cmds: + - "docker compose -f {{.PORTAINER_DC_FILE}} stop" + - "docker compose -f {{.PORTAINER_DC_FILE}} rm -f" + + restart_portainer: + cmds: + - task: stop_portainer + - task: sleep + - task: start_portainer + + sleep: + vars: + DURATION: "{{default 5 .DURATION}}" + cmds: - "{{.SLEEP_CMD}} {{.DURATION}}" \ No newline at end of file diff --git a/catalog-service/.gitattributes b/catalog-service/.gitattributes index 3b41682..0ac0d33 100644 --- a/catalog-service/.gitattributes +++ b/catalog-service/.gitattributes @@ -1,2 +1,2 @@ -/mvnw text eol=lf -*.cmd text eol=crlf +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/catalog-service/.gitignore b/catalog-service/.gitignore index 667aaef..d3f8d6e 100644 --- a/catalog-service/.gitignore +++ b/catalog-service/.gitignore @@ -1,33 +1,33 @@ -HELP.md -target/ -.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/catalog-service/.mvn/wrapper/maven-wrapper.properties b/catalog-service/.mvn/wrapper/maven-wrapper.properties index d58dfb7..b2f4fc0 100644 --- a/catalog-service/.mvn/wrapper/maven-wrapper.properties +++ b/catalog-service/.mvn/wrapper/maven-wrapper.properties @@ -1,19 +1,19 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -wrapperVersion=3.3.2 -distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/catalog-service/pom.xml b/catalog-service/pom.xml index 8a38b5b..856c32b 100644 --- a/catalog-service/pom.xml +++ b/catalog-service/pom.xml @@ -1,195 +1,195 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.5.0 - - - com.supersection - catalog-service - 0.0.1-SNAPSHOT - catalog-service - Catalog Service using Spring Boot - - - 21 - 2.8.8 - 2.44.5 - supersection/bookstore-${project.artifactId} - - - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.boot - spring-boot-starter-web - - - org.flywaydb - flyway-core - - - org.flywaydb - flyway-database-postgresql - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - ${springdoc-openapi.version} - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - io.micrometer - micrometer-registry-prometheus - runtime - - - org.postgresql - postgresql - runtime - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.projectlombok - lombok - true - - - org.springframework.boot - spring-boot-starter-test - test - - - io.rest-assured - rest-assured - test - - - org.springframework.boot - spring-boot-testcontainers - test - - - org.testcontainers - junit-jupiter - test - - - org.testcontainers - postgresql - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.springframework.boot - spring-boot-configuration-processor - - - org.projectlombok - lombok - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - ${dockerImageName} - - - - org.projectlombok - lombok - - - - - - - build-info - - - - - - io.github.git-commit-id - git-commit-id-maven-plugin - - - - revision - - - - - false - false - true - - ^git.branch$ - ^git.commit.id.abbrev$ - ^git.commit.user.name$ - ^git.commit.message.full$ - - - - - com.diffplug.spotless - spotless-maven-plugin - ${spotless-maven-plugin.version} - - - - - - 2.35.0 - - - - - - - compile - - check - - - - - - - - + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.0 + + + com.supersection + catalog-service + 0.0.1-SNAPSHOT + catalog-service + Catalog Service using Spring Boot + + + 21 + 2.8.8 + 2.44.5 + supersection/bookstore-${project.artifactId} + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-web + + + org.flywaydb + flyway-core + + + org.flywaydb + flyway-database-postgresql + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc-openapi.version} + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + io.micrometer + micrometer-registry-prometheus + runtime + + + org.postgresql + postgresql + runtime + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + io.rest-assured + rest-assured + test + + + org.springframework.boot + spring-boot-testcontainers + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + postgresql + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.springframework.boot + spring-boot-configuration-processor + + + org.projectlombok + lombok + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + ${dockerImageName} + + + + org.projectlombok + lombok + + + + + + + build-info + + + + + + io.github.git-commit-id + git-commit-id-maven-plugin + + + + revision + + + + + false + false + true + + ^git.branch$ + ^git.commit.id.abbrev$ + ^git.commit.user.name$ + ^git.commit.message.full$ + + + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless-maven-plugin.version} + + + + + + 2.35.0 + + + + + + + compile + + check + + + + + + + + diff --git a/catalog-service/src/main/resources/application.properties b/catalog-service/src/main/resources/application.properties index 89c15a8..bc808dd 100644 --- a/catalog-service/src/main/resources/application.properties +++ b/catalog-service/src/main/resources/application.properties @@ -1,19 +1,19 @@ -spring.application.name=catalog-service -server.port=8081 -server.shutdown=graceful - -# Catalog Service Configuration -catalog.page-size=10 - -# Expose all actuator endpoints -management.endpoints.web.exposure.include=* - -# Enable full Git metadata display in /actuator/info -management.info.git.mode=full - - -# Database Configuration -spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:15432/postgres} -spring.datasource.username=${DB_USERNAME:postgres} -spring.datasource.password=${DB_PASSWORD:postgres} +spring.application.name=catalog-service +server.port=8081 +server.shutdown=graceful + +# Catalog Service Configuration +catalog.page-size=10 + +# Expose all actuator endpoints +management.endpoints.web.exposure.include=* + +# Enable full Git metadata display in /actuator/info +management.info.git.mode=full + + +# Database Configuration +spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:15432/postgres} +spring.datasource.username=${DB_USERNAME:postgres} +spring.datasource.password=${DB_PASSWORD:postgres} spring.jpa.open-in-view=false \ No newline at end of file diff --git a/catalog-service/src/main/resources/db/migration/V1__create_products_table.sql b/catalog-service/src/main/resources/db/migration/V1__create_products_table.sql index e8312f0..701ad53 100644 --- a/catalog-service/src/main/resources/db/migration/V1__create_products_table.sql +++ b/catalog-service/src/main/resources/db/migration/V1__create_products_table.sql @@ -1,12 +1,12 @@ -create sequence product_id_seq start with 1 increment by 50; - -create table products -( - id bigint default nextval('product_id_seq') not null, - code text not null unique, - name text not null, - description text, - image_url text, - price numeric not null, - primary key (id) +create sequence product_id_seq start with 1 increment by 50; + +create table products +( + id bigint default nextval('product_id_seq') not null, + code text not null unique, + name text not null, + description text, + image_url text, + price numeric not null, + primary key (id) ); \ No newline at end of file diff --git a/catalog-service/src/main/resources/db/migration/V2__add_books_data.sql b/catalog-service/src/main/resources/db/migration/V2__add_books_data.sql index 164ea28..dba7a74 100644 --- a/catalog-service/src/main/resources/db/migration/V2__add_books_data.sql +++ b/catalog-service/src/main/resources/db/migration/V2__add_books_data.sql @@ -1,17 +1,17 @@ -insert into products(code, name, description, image_url, price) values -('P100','The Hunger Games','Winning will make you famous. Losing means certain death...','https://images.gr-assets.com/books/1447303603l/2767052.jpg', 34.0), -('P101','To Kill a Mockingbird','The unforgettable novel of a childhood in a sleepy Southern town and the crisis of conscience that rocked it...','https://images.gr-assets.com/books/1361975680l/2657.jpg', 45.40), -('P102','The Chronicles of Narnia','Journeys to the end of the world, fantastic creatures, and epic battles between good and evil—what more could any reader ask for in one book?...','https://images.gr-assets.com/books/1449868701l/11127.jpg', 44.50), -('P103','Gone with the Wind', 'Gone with the Wind is a novel written by Margaret Mitchell, first published in 1936.', 'https://images.gr-assets.com/books/1328025229l/18405.jpg',44.50), -('P104','The Fault in Our Stars','Despite the tumor-shrinking medical miracle that has bought her a few years, Hazel has never been anything but terminal, her final chapter inscribed upon diagnosis.','https://images.gr-assets.com/books/1360206420l/11870085.jpg',14.50), -('P105','The Giving Tree','Once there was a tree...and she loved a little boy.','https://images.gr-assets.com/books/1174210942l/370493.jpg',32.0), -('P106','The Da Vinci Code','An ingenious code hidden in the works of Leonardo da Vinci.A desperate race through the cathedrals and castles of Europe','https://images.gr-assets.com/books/1303252999l/968.jpg',14.50), -('P107','The Alchemist','Paulo Coelho''s masterpiece tells the mystical story of Santiago, an Andalusian shepherd boy who yearns to travel in search of a worldly treasure','https://images.gr-assets.com/books/1483412266l/865.jpg',12.0), -('P108','Charlotte''s Web','This beloved book by E. B. White, author of Stuart Little and The Trumpet of the Swan, is a classic of children''s literature','https://images.gr-assets.com/books/1439632243l/24178.jpg',14.0), -('P109','The Little Prince','Moral allegory and spiritual autobiography, The Little Prince is the most translated book in the French language.','https://images.gr-assets.com/books/1367545443l/157993.jpg',16.50), -('P110','A Thousand Splendid Suns','A Thousand Splendid Suns is a breathtaking story set against the volatile events of Afghanistan''s last thirty years—from the Soviet invasion to the reign of the Taliban to post-Taliban rebuilding—that puts the violence, fear, hope, and faith of this country in intimate, human terms.','https://images.gr-assets.com/books/1345958969l/128029.jpg',15.50), -('P111','A Game of Thrones','Here is the first volume in George R. R. Martin’s magnificent cycle of novels that includes A Clash of Kings and A Storm of Swords.','https://images.gr-assets.com/books/1436732693l/13496.jpg',32.0), -('P112','The Book Thief','Nazi Germany. The country is holding its breath. Death has never been busier, and will be busier still.By her brother''s graveside, Liesel''s life is changed when she picks up a single object, partially hidden in the snow.','https://images.gr-assets.com/books/1522157426l/19063.jpg',30.0), -('P113','One Flew Over the Cuckoo''s Nest','Tyrannical Nurse Ratched rules her ward in an Oregon State mental hospital with a strict and unbending routine, unopposed by her patients, who remain cowed by mind-numbing medication and the threat of electric shock therapy.','https://images.gr-assets.com/books/1516211014l/332613.jpg',23.0), -('P114','Fifty Shades of Grey','When literature student Anastasia Steele goes to interview young entrepreneur Christian Grey, she encounters a man who is beautiful, brilliant, and intimidating.','https://images.gr-assets.com/books/1385207843l/10818853.jpg', 27.0) +insert into products(code, name, description, image_url, price) values +('P100','The Hunger Games','Winning will make you famous. Losing means certain death...','https://images.gr-assets.com/books/1447303603l/2767052.jpg', 34.0), +('P101','To Kill a Mockingbird','The unforgettable novel of a childhood in a sleepy Southern town and the crisis of conscience that rocked it...','https://images.gr-assets.com/books/1361975680l/2657.jpg', 45.40), +('P102','The Chronicles of Narnia','Journeys to the end of the world, fantastic creatures, and epic battles between good and evil—what more could any reader ask for in one book?...','https://images.gr-assets.com/books/1449868701l/11127.jpg', 44.50), +('P103','Gone with the Wind', 'Gone with the Wind is a novel written by Margaret Mitchell, first published in 1936.', 'https://images.gr-assets.com/books/1328025229l/18405.jpg',44.50), +('P104','The Fault in Our Stars','Despite the tumor-shrinking medical miracle that has bought her a few years, Hazel has never been anything but terminal, her final chapter inscribed upon diagnosis.','https://images.gr-assets.com/books/1360206420l/11870085.jpg',14.50), +('P105','The Giving Tree','Once there was a tree...and she loved a little boy.','https://images.gr-assets.com/books/1174210942l/370493.jpg',32.0), +('P106','The Da Vinci Code','An ingenious code hidden in the works of Leonardo da Vinci.A desperate race through the cathedrals and castles of Europe','https://images.gr-assets.com/books/1303252999l/968.jpg',14.50), +('P107','The Alchemist','Paulo Coelho''s masterpiece tells the mystical story of Santiago, an Andalusian shepherd boy who yearns to travel in search of a worldly treasure','https://images.gr-assets.com/books/1483412266l/865.jpg',12.0), +('P108','Charlotte''s Web','This beloved book by E. B. White, author of Stuart Little and The Trumpet of the Swan, is a classic of children''s literature','https://images.gr-assets.com/books/1439632243l/24178.jpg',14.0), +('P109','The Little Prince','Moral allegory and spiritual autobiography, The Little Prince is the most translated book in the French language.','https://images.gr-assets.com/books/1367545443l/157993.jpg',16.50), +('P110','A Thousand Splendid Suns','A Thousand Splendid Suns is a breathtaking story set against the volatile events of Afghanistan''s last thirty years—from the Soviet invasion to the reign of the Taliban to post-Taliban rebuilding—that puts the violence, fear, hope, and faith of this country in intimate, human terms.','https://images.gr-assets.com/books/1345958969l/128029.jpg',15.50), +('P111','A Game of Thrones','Here is the first volume in George R. R. Martin’s magnificent cycle of novels that includes A Clash of Kings and A Storm of Swords.','https://images.gr-assets.com/books/1436732693l/13496.jpg',32.0), +('P112','The Book Thief','Nazi Germany. The country is holding its breath. Death has never been busier, and will be busier still.By her brother''s graveside, Liesel''s life is changed when she picks up a single object, partially hidden in the snow.','https://images.gr-assets.com/books/1522157426l/19063.jpg',30.0), +('P113','One Flew Over the Cuckoo''s Nest','Tyrannical Nurse Ratched rules her ward in an Oregon State mental hospital with a strict and unbending routine, unopposed by her patients, who remain cowed by mind-numbing medication and the threat of electric shock therapy.','https://images.gr-assets.com/books/1516211014l/332613.jpg',23.0), +('P114','Fifty Shades of Grey','When literature student Anastasia Steele goes to interview young entrepreneur Christian Grey, she encounters a man who is beautiful, brilliant, and intimidating.','https://images.gr-assets.com/books/1385207843l/10818853.jpg', 27.0) ; \ No newline at end of file diff --git a/catalog-service/src/test/resources/test-data.sql b/catalog-service/src/test/resources/test-data.sql index e482632..7c231c9 100644 --- a/catalog-service/src/test/resources/test-data.sql +++ b/catalog-service/src/test/resources/test-data.sql @@ -1,19 +1,19 @@ -truncate table products; - -insert into products(code, name, description, image_url, price) values -('P100','The Hunger Games','Winning will make you famous. Losing means certain death...','https://images.gr-assets.com/books/1447303603l/2767052.jpg', 34.0), -('P101','To Kill a Mockingbird','The unforgettable novel of a childhood in a sleepy Southern town and the crisis of conscience that rocked it...','https://images.gr-assets.com/books/1361975680l/2657.jpg', 45.40), -('P102','The Chronicles of Narnia','Journeys to the end of the world, fantastic creatures, and epic battles between good and evil—what more could any reader ask for in one book?...','https://images.gr-assets.com/books/1449868701l/11127.jpg', 44.50), -('P103','Gone with the Wind', 'Gone with the Wind is a novel written by Margaret Mitchell, first published in 1936.', 'https://images.gr-assets.com/books/1328025229l/18405.jpg',44.50), -('P104','The Fault in Our Stars','Despite the tumor-shrinking medical miracle that has bought her a few years, Hazel has never been anything but terminal, her final chapter inscribed upon diagnosis.','https://images.gr-assets.com/books/1360206420l/11870085.jpg',14.50), -('P105','The Giving Tree','Once there was a tree...and she loved a little boy.','https://images.gr-assets.com/books/1174210942l/370493.jpg',32.0), -('P106','The Da Vinci Code','An ingenious code hidden in the works of Leonardo da Vinci.A desperate race through the cathedrals and castles of Europe','https://images.gr-assets.com/books/1303252999l/968.jpg',14.50), -('P107','The Alchemist','Paulo Coelho''s masterpiece tells the mystical story of Santiago, an Andalusian shepherd boy who yearns to travel in search of a worldly treasure','https://images.gr-assets.com/books/1483412266l/865.jpg',12.0), -('P108','Charlotte''s Web','This beloved book by E. B. White, author of Stuart Little and The Trumpet of the Swan, is a classic of children''s literature','https://images.gr-assets.com/books/1439632243l/24178.jpg',14.0), -('P109','The Little Prince','Moral allegory and spiritual autobiography, The Little Prince is the most translated book in the French language.','https://images.gr-assets.com/books/1367545443l/157993.jpg',16.50), -('P110','A Thousand Splendid Suns','A Thousand Splendid Suns is a breathtaking story set against the volatile events of Afghanistan''s last thirty years—from the Soviet invasion to the reign of the Taliban to post-Taliban rebuilding—that puts the violence, fear, hope, and faith of this country in intimate, human terms.','https://images.gr-assets.com/books/1345958969l/128029.jpg',15.50), -('P111','A Game of Thrones','Here is the first volume in George R. R. Martin’s magnificent cycle of novels that includes A Clash of Kings and A Storm of Swords.','https://images.gr-assets.com/books/1436732693l/13496.jpg',32.0), -('P112','The Book Thief','Nazi Germany. The country is holding its breath. Death has never been busier, and will be busier still.By her brother''s graveside, Liesel''s life is changed when she picks up a single object, partially hidden in the snow.','https://images.gr-assets.com/books/1522157426l/19063.jpg',30.0), -('P113','One Flew Over the Cuckoo''s Nest','Tyrannical Nurse Ratched rules her ward in an Oregon State mental hospital with a strict and unbending routine, unopposed by her patients, who remain cowed by mind-numbing medication and the threat of electric shock therapy.','https://images.gr-assets.com/books/1516211014l/332613.jpg',23.0), -('P114','Fifty Shades of Grey','When literature student Anastasia Steele goes to interview young entrepreneur Christian Grey, she encounters a man who is beautiful, brilliant, and intimidating.','https://images.gr-assets.com/books/1385207843l/10818853.jpg', 27.0) +truncate table products; + +insert into products(code, name, description, image_url, price) values +('P100','The Hunger Games','Winning will make you famous. Losing means certain death...','https://images.gr-assets.com/books/1447303603l/2767052.jpg', 34.0), +('P101','To Kill a Mockingbird','The unforgettable novel of a childhood in a sleepy Southern town and the crisis of conscience that rocked it...','https://images.gr-assets.com/books/1361975680l/2657.jpg', 45.40), +('P102','The Chronicles of Narnia','Journeys to the end of the world, fantastic creatures, and epic battles between good and evil—what more could any reader ask for in one book?...','https://images.gr-assets.com/books/1449868701l/11127.jpg', 44.50), +('P103','Gone with the Wind', 'Gone with the Wind is a novel written by Margaret Mitchell, first published in 1936.', 'https://images.gr-assets.com/books/1328025229l/18405.jpg',44.50), +('P104','The Fault in Our Stars','Despite the tumor-shrinking medical miracle that has bought her a few years, Hazel has never been anything but terminal, her final chapter inscribed upon diagnosis.','https://images.gr-assets.com/books/1360206420l/11870085.jpg',14.50), +('P105','The Giving Tree','Once there was a tree...and she loved a little boy.','https://images.gr-assets.com/books/1174210942l/370493.jpg',32.0), +('P106','The Da Vinci Code','An ingenious code hidden in the works of Leonardo da Vinci.A desperate race through the cathedrals and castles of Europe','https://images.gr-assets.com/books/1303252999l/968.jpg',14.50), +('P107','The Alchemist','Paulo Coelho''s masterpiece tells the mystical story of Santiago, an Andalusian shepherd boy who yearns to travel in search of a worldly treasure','https://images.gr-assets.com/books/1483412266l/865.jpg',12.0), +('P108','Charlotte''s Web','This beloved book by E. B. White, author of Stuart Little and The Trumpet of the Swan, is a classic of children''s literature','https://images.gr-assets.com/books/1439632243l/24178.jpg',14.0), +('P109','The Little Prince','Moral allegory and spiritual autobiography, The Little Prince is the most translated book in the French language.','https://images.gr-assets.com/books/1367545443l/157993.jpg',16.50), +('P110','A Thousand Splendid Suns','A Thousand Splendid Suns is a breathtaking story set against the volatile events of Afghanistan''s last thirty years—from the Soviet invasion to the reign of the Taliban to post-Taliban rebuilding—that puts the violence, fear, hope, and faith of this country in intimate, human terms.','https://images.gr-assets.com/books/1345958969l/128029.jpg',15.50), +('P111','A Game of Thrones','Here is the first volume in George R. R. Martin’s magnificent cycle of novels that includes A Clash of Kings and A Storm of Swords.','https://images.gr-assets.com/books/1436732693l/13496.jpg',32.0), +('P112','The Book Thief','Nazi Germany. The country is holding its breath. Death has never been busier, and will be busier still.By her brother''s graveside, Liesel''s life is changed when she picks up a single object, partially hidden in the snow.','https://images.gr-assets.com/books/1522157426l/19063.jpg',30.0), +('P113','One Flew Over the Cuckoo''s Nest','Tyrannical Nurse Ratched rules her ward in an Oregon State mental hospital with a strict and unbending routine, unopposed by her patients, who remain cowed by mind-numbing medication and the threat of electric shock therapy.','https://images.gr-assets.com/books/1516211014l/332613.jpg',23.0), +('P114','Fifty Shades of Grey','When literature student Anastasia Steele goes to interview young entrepreneur Christian Grey, she encounters a man who is beautiful, brilliant, and intimidating.','https://images.gr-assets.com/books/1385207843l/10818853.jpg', 27.0) ; \ No newline at end of file diff --git a/deployment/docker-compose/apps.yml b/deployment/docker-compose/apps.yml index 560979c..d3ae124 100644 --- a/deployment/docker-compose/apps.yml +++ b/deployment/docker-compose/apps.yml @@ -1,20 +1,20 @@ -name: 'bookstore-microservice-application' -services: - catalog-service: - image: supersection/bookstore-catalog-service - container_name: catalog-service - environment: - - SPRING_PROFILES_ACTIVE=docker - - DB_URL=jdbc:postgresql://catalog-db:5432/postgres - - DB_USERNAME=postgres - - DB_PASSWORD=postgres - ports: - - "8081:8081" - restart: unless-stopped - depends_on: - catalog-db: - condition: service_healthy - deploy: - resources: - limits: +name: 'bookstore-microservice-application' +services: + catalog-service: + image: supersection/bookstore-catalog-service + container_name: catalog-service + environment: + - SPRING_PROFILES_ACTIVE=docker + - DB_URL=jdbc:postgresql://catalog-db:5432/postgres + - DB_USERNAME=postgres + - DB_PASSWORD=postgres + ports: + - "8081:8081" + restart: unless-stopped + depends_on: + catalog-db: + condition: service_healthy + deploy: + resources: + limits: memory: 700m \ No newline at end of file diff --git a/devtools/docker-compose/portainer.yml b/devtools/docker-compose/portainer.yml index f8b4d8e..c8f7a30 100644 --- a/devtools/docker-compose/portainer.yml +++ b/devtools/docker-compose/portainer.yml @@ -1,14 +1,14 @@ -name: 'devtools' -services: - portainer: - image: portainer/portainer-ce:latest - container_name: portainer - restart: always - ports: - - "9000:9000" - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - portainer_data:/data - -volumes: +name: 'devtools' +services: + portainer: + image: portainer/portainer-ce:latest + container_name: portainer + restart: always + ports: + - "9000:9000" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - portainer_data:/data + +volumes: portainer_data: \ No newline at end of file diff --git a/order-service/.gitattributes b/order-service/.gitattributes index 3b41682..5389ec3 100644 --- a/order-service/.gitattributes +++ b/order-service/.gitattributes @@ -1,2 +1,5 @@ -/mvnw text eol=lf -*.cmd text eol=crlf +/mvnw text eol=lf +*.cmd text eol=crlf + +# Force text files to use Unix-style LF line endings +*.java text eol=lf \ No newline at end of file diff --git a/order-service/.gitignore b/order-service/.gitignore index 667aaef..d3f8d6e 100644 --- a/order-service/.gitignore +++ b/order-service/.gitignore @@ -1,33 +1,33 @@ -HELP.md -target/ -.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ - -### VS Code ### -.vscode/ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/order-service/.mvn/wrapper/maven-wrapper.properties b/order-service/.mvn/wrapper/maven-wrapper.properties index d58dfb7..b2f4fc0 100644 --- a/order-service/.mvn/wrapper/maven-wrapper.properties +++ b/order-service/.mvn/wrapper/maven-wrapper.properties @@ -1,19 +1,19 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -wrapperVersion=3.3.2 -distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/order-service/pom.xml b/order-service/pom.xml index 9f61da3..cf11263 100644 --- a/order-service/pom.xml +++ b/order-service/pom.xml @@ -1,236 +1,247 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.5.0 - - - com.supersection.bookstore - order-service - 0.0.1-SNAPSHOT - order-service - Order Service using Spring Boot - - - 21 - 2.8.8 - 2.44.5 - 5.4.1 - 2.3.0 - 3.13.0 - 1.0-alpha-15 - supersection/bookstore-${project.artifactId} - - - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-amqp - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.boot - spring-boot-starter-web - - - org.flywaydb - flyway-core - - - org.flywaydb - flyway-database-postgresql - - - io.github.resilience4j - resilience4j-spring-boot3 - ${resilience4j.version} - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - ${springdoc-openapi.version} - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - io.micrometer - micrometer-registry-prometheus - runtime - - - org.postgresql - postgresql - runtime - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.projectlombok - lombok - true - - - org.springframework.boot - spring-boot-starter-test - test - - - io.rest-assured - rest-assured - test - - - org.springframework.boot - spring-boot-testcontainers - test - - - org.springframework.amqp - spring-rabbit-test - test - - - org.testcontainers - junit-jupiter - test - - - org.testcontainers - postgresql - test - - - org.testcontainers - rabbitmq - test - - - org.instancio - instancio-junit - ${instancio.version} - test - - - org.wiremock - wiremock-standalone - ${wiremock.version} - test - - - org.wiremock.integrations.testcontainers - wiremock-testcontainers-module - ${wiremock-testcontainers.version} - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.springframework.boot - spring-boot-configuration-processor - - - org.projectlombok - lombok - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - ${dockerImageName} - - - - org.projectlombok - lombok - - - - - - - build-info - - - - - - io.github.git-commit-id - git-commit-id-maven-plugin - - - - revision - - - - - false - false - true - - ^git.branch$ - ^git.commit.id.abbrev$ - ^git.commit.user.name$ - ^git.commit.message.full$ - - - - - com.diffplug.spotless - spotless-maven-plugin - ${spotless-maven-plugin.version} - - - - - - 2.35.0 - - - - - - - compile - - check - - - - - - - - + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.0 + + + com.supersection.bookstore + order-service + 0.0.1-SNAPSHOT + order-service + Order Service using Spring Boot + + + 21 + 2.8.8 + 2.44.5 + 5.4.1 + 2.3.0 + 6.7.0 + 3.13.0 + 1.0-alpha-15 + supersection/bookstore-${project.artifactId} + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-web + + + org.flywaydb + flyway-core + + + org.flywaydb + flyway-database-postgresql + + + io.github.resilience4j + resilience4j-spring-boot3 + ${resilience4j.version} + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc-openapi.version} + + + net.javacrumbs.shedlock + shedlock-spring + ${shedlock.version} + + + net.javacrumbs.shedlock + shedlock-provider-jdbc-template + ${shedlock.version} + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + io.micrometer + micrometer-registry-prometheus + runtime + + + org.postgresql + postgresql + runtime + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + io.rest-assured + rest-assured + test + + + org.springframework.boot + spring-boot-testcontainers + test + + + org.springframework.amqp + spring-rabbit-test + test + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + postgresql + test + + + org.testcontainers + rabbitmq + test + + + org.instancio + instancio-junit + ${instancio.version} + test + + + org.wiremock + wiremock-standalone + ${wiremock.version} + test + + + org.wiremock.integrations.testcontainers + wiremock-testcontainers-module + ${wiremock-testcontainers.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.springframework.boot + spring-boot-configuration-processor + + + org.projectlombok + lombok + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + ${dockerImageName} + + + + org.projectlombok + lombok + + + + + + + build-info + + + + + + io.github.git-commit-id + git-commit-id-maven-plugin + + + + revision + + + + + false + false + true + + ^git.branch$ + ^git.commit.id.abbrev$ + ^git.commit.user.name$ + ^git.commit.message.full$ + + + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless-maven-plugin.version} + + + + + + 2.35.0 + + + + + + + compile + + check + + + + + + + + diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/OrderServiceApplication.java b/order-service/src/main/java/com/supersection/bookstore/orders/OrderServiceApplication.java index 98b7d0e..4600f31 100644 --- a/order-service/src/main/java/com/supersection/bookstore/orders/OrderServiceApplication.java +++ b/order-service/src/main/java/com/supersection/bookstore/orders/OrderServiceApplication.java @@ -1,11 +1,15 @@ package com.supersection.bookstore.orders; +import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @ConfigurationPropertiesScan +@EnableScheduling +@EnableSchedulerLock(defaultLockAtMostFor = "10m") public class OrderServiceApplication { public static void main(String[] args) { diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/config/SchedulerConfig.java b/order-service/src/main/java/com/supersection/bookstore/orders/config/SchedulerConfig.java new file mode 100644 index 0000000..c9927ca --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/config/SchedulerConfig.java @@ -0,0 +1,20 @@ +package com.supersection.bookstore.orders.config; + +import javax.sql.DataSource; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +class SchedulerConfig { + + @Bean + public LockProvider lockProvider(DataSource dataSource) { + return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration.builder() + .withJdbcTemplate(new JdbcTemplate(dataSource)) + .usingDbTime() + .build()); + } +} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventEntity.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventEntity.java new file mode 100644 index 0000000..e4d0d58 --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventEntity.java @@ -0,0 +1,45 @@ +package com.supersection.bookstore.orders.domain; + +import com.supersection.bookstore.orders.domain.models.OrderEventType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Table(name = "order_events") +@Getter +@Setter +class OrderEventEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "order_event_id_generator") + @SequenceGenerator(name = "order_event_id_generator", sequenceName = "order_event_id_seq") + private Long id; + + @Column(nullable = false) + private String orderNumber; + + @Column(nullable = false, unique = true) + private String eventId; + + @Enumerated(EnumType.STRING) + private OrderEventType eventType; + + @Column(nullable = false) + private String payload; + + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt = LocalDateTime.now(); + + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventMapper.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventMapper.java new file mode 100644 index 0000000..eedb79d --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventMapper.java @@ -0,0 +1,58 @@ +package com.supersection.bookstore.orders.domain; + +import com.supersection.bookstore.orders.domain.models.*; +import java.time.LocalDateTime; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +class OrderEventMapper { + + static OrderCreatedEvent buildOrderCreatedEvent(OrderEntity order) { + return new OrderCreatedEvent( + UUID.randomUUID().toString(), + order.getOrderNumber(), + getOrderItems(order), + order.getCustomer(), + order.getDeliveryAddress(), + LocalDateTime.now()); + } + + static OrderDeliveredEvent buildOrderDeliveredEvent(OrderEntity order) { + return new OrderDeliveredEvent( + UUID.randomUUID().toString(), + order.getOrderNumber(), + getOrderItems(order), + order.getCustomer(), + order.getDeliveryAddress(), + LocalDateTime.now()); + } + + static OrderCancelledEvent buildOrderCancelledEvent(OrderEntity order, String reason) { + return new OrderCancelledEvent( + UUID.randomUUID().toString(), + order.getOrderNumber(), + getOrderItems(order), + order.getCustomer(), + order.getDeliveryAddress(), + reason, + LocalDateTime.now()); + } + + static OrderErrorEvent buildOrderErrorEvent(OrderEntity order, String reason) { + return new OrderErrorEvent( + UUID.randomUUID().toString(), + order.getOrderNumber(), + getOrderItems(order), + order.getCustomer(), + order.getDeliveryAddress(), + reason, + LocalDateTime.now()); + } + + private static Set getOrderItems(OrderEntity order) { + return order.getItems().stream() + .map(item -> new OrderItem(item.getCode(), item.getName(), item.getPrice(), item.getQuantity())) + .collect(Collectors.toSet()); + } +} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventPublisher.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventPublisher.java new file mode 100644 index 0000000..2db119f --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventPublisher.java @@ -0,0 +1,41 @@ +package com.supersection.bookstore.orders.domain; + +import com.supersection.bookstore.orders.ApplicationProperties; +import com.supersection.bookstore.orders.domain.models.OrderCancelledEvent; +import com.supersection.bookstore.orders.domain.models.OrderCreatedEvent; +import com.supersection.bookstore.orders.domain.models.OrderDeliveredEvent; +import com.supersection.bookstore.orders.domain.models.OrderErrorEvent; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.stereotype.Component; + +@Component +class OrderEventPublisher { + + private final RabbitTemplate rabbitTemplate; + private final ApplicationProperties properties; + + OrderEventPublisher(RabbitTemplate rabbitTemplate, ApplicationProperties properties) { + this.rabbitTemplate = rabbitTemplate; + this.properties = properties; + } + + public void publish(OrderCreatedEvent event) { + this.send(properties.newOrdersQueue(), event); + } + + public void publish(OrderDeliveredEvent event) { + this.send(properties.deliveredOrdersQueue(), event); + } + + public void publish(OrderCancelledEvent event) { + this.send(properties.cancelledOrdersQueue(), event); + } + + public void publish(OrderErrorEvent event) { + this.send(properties.errorOrdersQueue(), event); + } + + private void send(String routingKey, Object payload) { + rabbitTemplate.convertAndSend(properties.orderEventsExchange(), routingKey, payload); + } +} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventRepository.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventRepository.java new file mode 100644 index 0000000..c00a73e --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventRepository.java @@ -0,0 +1,5 @@ +package com.supersection.bookstore.orders.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +interface OrderEventRepository extends JpaRepository {} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventService.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventService.java new file mode 100644 index 0000000..3d2a042 --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderEventService.java @@ -0,0 +1,126 @@ +package com.supersection.bookstore.orders.domain; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.supersection.bookstore.orders.domain.models.OrderCancelledEvent; +import com.supersection.bookstore.orders.domain.models.OrderCreatedEvent; +import com.supersection.bookstore.orders.domain.models.OrderDeliveredEvent; +import com.supersection.bookstore.orders.domain.models.OrderErrorEvent; +import com.supersection.bookstore.orders.domain.models.OrderEventType; +import jakarta.transaction.Transactional; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +@Service +@Transactional +public class OrderEventService { + + private static final Logger log = LoggerFactory.getLogger(OrderEventService.class); + + private final OrderEventRepository orderEventRepository; + private final OrderEventPublisher orderEventPublisher; + private final ObjectMapper objectMapper; + + OrderEventService( + OrderEventRepository orderEventRepository, + OrderEventPublisher orderEventPublisher, + ObjectMapper objectMapper) { + this.orderEventRepository = orderEventRepository; + this.orderEventPublisher = orderEventPublisher; + this.objectMapper = objectMapper; + } + + void save(OrderCreatedEvent event) { + OrderEventEntity orderEvent = new OrderEventEntity(); + orderEvent.setEventId(event.eventId()); + orderEvent.setEventType(OrderEventType.ORDER_CREATED); + orderEvent.setOrderNumber(event.orderNumber()); + orderEvent.setCreatedAt(event.createdAt()); + orderEvent.setPayload(toJsonPayload(event)); + this.orderEventRepository.save(orderEvent); + } + + void save(OrderDeliveredEvent event) { + OrderEventEntity orderEvent = new OrderEventEntity(); + orderEvent.setEventId(event.eventId()); + orderEvent.setEventType(OrderEventType.ORDER_DELIVERED); + orderEvent.setOrderNumber(event.orderNumber()); + orderEvent.setCreatedAt(event.createdAt()); + orderEvent.setPayload(toJsonPayload(event)); + this.orderEventRepository.save(orderEvent); + } + + void save(OrderCancelledEvent event) { + OrderEventEntity orderEvent = new OrderEventEntity(); + orderEvent.setEventId(event.eventId()); + orderEvent.setEventType(OrderEventType.ORDER_CANCELLED); + orderEvent.setOrderNumber(event.orderNumber()); + orderEvent.setCreatedAt(event.createdAt()); + orderEvent.setPayload(toJsonPayload(event)); + this.orderEventRepository.save(orderEvent); + } + + void save(OrderErrorEvent event) { + OrderEventEntity orderEvent = new OrderEventEntity(); + orderEvent.setEventId(event.eventId()); + orderEvent.setEventType(OrderEventType.ORDER_PROCESSING_FAILED); + orderEvent.setOrderNumber(event.orderNumber()); + orderEvent.setCreatedAt(event.createdAt()); + orderEvent.setPayload(toJsonPayload(event)); + this.orderEventRepository.save(orderEvent); + } + + public void publishOrderEvents() { + Sort sort = Sort.by("createdAt").ascending(); + List events = orderEventRepository.findAll(sort); + log.info("Found {} Order Events to be published", events.size()); + for (OrderEventEntity event : events) { + this.publishEvent(event); + orderEventRepository.delete(event); + } + } + + private void publishEvent(OrderEventEntity event) { + OrderEventType eventType = event.getEventType(); + switch (eventType) { + case ORDER_CREATED -> { + OrderCreatedEvent orderCreatedEvent = fromJsonPayload(event.getPayload(), OrderCreatedEvent.class); + orderEventPublisher.publish(orderCreatedEvent); + } + case ORDER_DELIVERED -> { + OrderDeliveredEvent orderDeliveredEvent = + fromJsonPayload(event.getPayload(), OrderDeliveredEvent.class); + orderEventPublisher.publish(orderDeliveredEvent); + } + case ORDER_CANCELLED -> { + OrderCancelledEvent orderCancelledEvent = + fromJsonPayload(event.getPayload(), OrderCancelledEvent.class); + orderEventPublisher.publish(orderCancelledEvent); + } + case ORDER_PROCESSING_FAILED -> { + OrderErrorEvent orderErrorEvent = fromJsonPayload(event.getPayload(), OrderErrorEvent.class); + orderEventPublisher.publish(orderErrorEvent); + } + default -> log.warn("Unsupported OrderEventType: {}", eventType); + } + } + + private String toJsonPayload(Object object) { + try { + return objectMapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private T fromJsonPayload(String json, Class type) { + try { + return objectMapper.readValue(json, type); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderMapper.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderMapper.java index db127aa..3bb305a 100644 --- a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderMapper.java +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderMapper.java @@ -1,7 +1,7 @@ package com.supersection.bookstore.orders.domain; import com.supersection.bookstore.orders.domain.dtos.CreateOrderRequest; -import com.supersection.bookstore.orders.domain.dtos.OrderItem; +import com.supersection.bookstore.orders.domain.models.OrderItem; import com.supersection.bookstore.orders.domain.models.OrderStatus; import java.util.HashSet; import java.util.Set; diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderRepository.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderRepository.java index fcaebae..790d252 100644 --- a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderRepository.java +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderRepository.java @@ -1,7 +1,21 @@ package com.supersection.bookstore.orders.domain; +import com.supersection.bookstore.orders.domain.models.OrderStatus; +import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository -interface OrderRepository extends JpaRepository {} +interface OrderRepository extends JpaRepository { + + List findByStatus(OrderStatus status); + + Optional findByOrderNumber(String orderNumber); + + default void updateOrderStatus(String orderNumber, OrderStatus status) { + OrderEntity order = this.findByOrderNumber(orderNumber).orElseThrow(); + order.setStatus(status); + this.save(order); + } +} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderService.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderService.java index 2562fee..9d05be7 100644 --- a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderService.java +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderService.java @@ -2,6 +2,8 @@ import com.supersection.bookstore.orders.domain.dtos.CreateOrderRequest; import com.supersection.bookstore.orders.domain.dtos.CreateOrderResponse; +import com.supersection.bookstore.orders.domain.models.OrderCreatedEvent; +import com.supersection.bookstore.orders.domain.models.OrderStatus; import jakarta.validation.Valid; import java.util.List; import org.slf4j.Logger; @@ -17,10 +19,13 @@ public class OrderService { private final OrderRepository orderRepository; private final OrderValidator orderValidator; + private final OrderEventService orderEventService; - public OrderService(OrderRepository orderRepository, OrderValidator orderValidator) { + public OrderService( + OrderRepository orderRepository, OrderValidator orderValidator, OrderEventService orderEventService) { this.orderRepository = orderRepository; this.orderValidator = orderValidator; + this.orderEventService = orderEventService; } public CreateOrderResponse createOrder(String userName, @Valid CreateOrderRequest request) { @@ -31,6 +36,42 @@ public CreateOrderResponse createOrder(String userName, @Valid CreateOrderReques OrderEntity savedOrder = orderRepository.save(newOrder); log.info("Created Order with orderNumber={}", savedOrder.getOrderNumber()); + OrderCreatedEvent orderCreatedEvent = OrderEventMapper.buildOrderCreatedEvent(savedOrder); + orderEventService.save(orderCreatedEvent); + return new CreateOrderResponse(savedOrder.getOrderNumber()); } + + public void processNewOrders() { + List orders = orderRepository.findByStatus(OrderStatus.NEW); + log.info("Found {} new orders to process", orders.size()); + for (OrderEntity order : orders) { + this.process(order); + } + } + + private void process(OrderEntity order) { + try { + if (canBeDelivered(order)) { + log.info("OrderNumber: {} can be delivered", order.getOrderNumber()); + orderRepository.updateOrderStatus(order.getOrderNumber(), OrderStatus.DELIVERED); + orderEventService.save(OrderEventMapper.buildOrderDeliveredEvent(order)); + + } else { + log.info("OrderNumber: {} can not be delivered", order.getOrderNumber()); + orderRepository.updateOrderStatus(order.getOrderNumber(), OrderStatus.CANCELLED); + orderEventService.save( + OrderEventMapper.buildOrderCancelledEvent(order, "Can't deliver to the location")); + } + } catch (RuntimeException e) { + log.error("Failed to process Order with orderNumber: {}", order.getOrderNumber(), e); + orderRepository.updateOrderStatus(order.getOrderNumber(), OrderStatus.ERROR); + orderEventService.save(OrderEventMapper.buildOrderErrorEvent(order, e.getMessage())); + } + } + + private boolean canBeDelivered(OrderEntity order) { + return DELIVERY_ALLOWED_COUNTRIES.contains( + order.getDeliveryAddress().country().toUpperCase()); + } } diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderValidator.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderValidator.java index aee5ea9..941086d 100644 --- a/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderValidator.java +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/OrderValidator.java @@ -3,8 +3,8 @@ import com.supersection.bookstore.orders.clients.catalog.Product; import com.supersection.bookstore.orders.clients.catalog.ProductServiceClient; import com.supersection.bookstore.orders.domain.dtos.CreateOrderRequest; -import com.supersection.bookstore.orders.domain.dtos.OrderItem; import com.supersection.bookstore.orders.domain.exception.InvalidOrderException; +import com.supersection.bookstore.orders.domain.models.OrderItem; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/dtos/CreateOrderRequest.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/dtos/CreateOrderRequest.java index c1aa4e4..4f9b9a0 100644 --- a/order-service/src/main/java/com/supersection/bookstore/orders/domain/dtos/CreateOrderRequest.java +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/dtos/CreateOrderRequest.java @@ -2,6 +2,7 @@ import com.supersection.bookstore.orders.domain.models.Address; import com.supersection.bookstore.orders.domain.models.Customer; +import com.supersection.bookstore.orders.domain.models.OrderItem; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import java.util.Set; diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderCancelledEvent.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderCancelledEvent.java new file mode 100644 index 0000000..b989385 --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderCancelledEvent.java @@ -0,0 +1,13 @@ +package com.supersection.bookstore.orders.domain.models; + +import java.time.LocalDateTime; +import java.util.Set; + +public record OrderCancelledEvent( + String eventId, + String orderNumber, + Set items, + Customer customer, + Address deliveryAddress, + String reason, + LocalDateTime createdAt) {} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderCreatedEvent.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderCreatedEvent.java new file mode 100644 index 0000000..8926ae7 --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderCreatedEvent.java @@ -0,0 +1,12 @@ +package com.supersection.bookstore.orders.domain.models; + +import java.time.LocalDateTime; +import java.util.Set; + +public record OrderCreatedEvent( + String eventId, + String orderNumber, + Set items, + Customer customer, + Address deliveryAddress, + LocalDateTime createdAt) {} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderDeliveredEvent.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderDeliveredEvent.java new file mode 100644 index 0000000..8aa67c9 --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderDeliveredEvent.java @@ -0,0 +1,12 @@ +package com.supersection.bookstore.orders.domain.models; + +import java.time.LocalDateTime; +import java.util.Set; + +public record OrderDeliveredEvent( + String eventId, + String orderNumber, + Set items, + Customer customer, + Address deliveryAddress, + LocalDateTime createdAt) {} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderErrorEvent.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderErrorEvent.java new file mode 100644 index 0000000..fb0865c --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderErrorEvent.java @@ -0,0 +1,13 @@ +package com.supersection.bookstore.orders.domain.models; + +import java.time.LocalDateTime; +import java.util.Set; + +public record OrderErrorEvent( + String eventId, + String orderNumber, + Set items, + Customer customer, + Address deliveryAddress, + String reason, + LocalDateTime createdAt) {} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderEventType.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderEventType.java new file mode 100644 index 0000000..c9ad279 --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderEventType.java @@ -0,0 +1,8 @@ +package com.supersection.bookstore.orders.domain.models; + +public enum OrderEventType { + ORDER_CREATED, + ORDER_DELIVERED, + ORDER_CANCELLED, + ORDER_PROCESSING_FAILED +} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/dtos/OrderItem.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderItem.java similarity index 89% rename from order-service/src/main/java/com/supersection/bookstore/orders/domain/dtos/OrderItem.java rename to order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderItem.java index 03d8d13..6638652 100644 --- a/order-service/src/main/java/com/supersection/bookstore/orders/domain/dtos/OrderItem.java +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderItem.java @@ -1,4 +1,4 @@ -package com.supersection.bookstore.orders.domain.dtos; +package com.supersection.bookstore.orders.domain.models; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderSummary.java b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderSummary.java new file mode 100644 index 0000000..64f0f00 --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/domain/models/OrderSummary.java @@ -0,0 +1,3 @@ +package com.supersection.bookstore.orders.domain.models; + +public record OrderSummary(String orderNumber, OrderStatus status) {} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/jobs/OrderEventsPublishingJob.java b/order-service/src/main/java/com/supersection/bookstore/orders/jobs/OrderEventsPublishingJob.java new file mode 100644 index 0000000..6bd5ee5 --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/jobs/OrderEventsPublishingJob.java @@ -0,0 +1,29 @@ +package com.supersection.bookstore.orders.jobs; + +import com.supersection.bookstore.orders.domain.OrderEventService; +import java.time.Instant; +import net.javacrumbs.shedlock.core.LockAssert; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +class OrderEventsPublishingJob { + private static final Logger log = LoggerFactory.getLogger(OrderEventsPublishingJob.class); + + private final OrderEventService orderEventService; + + OrderEventsPublishingJob(OrderEventService orderEventService) { + this.orderEventService = orderEventService; + } + + @Scheduled(cron = "${orders.publish-order-events-job-cron}") + @SchedulerLock(name = "publishOrderEvents") + public void publishOrderEvents() { + LockAssert.assertLocked(); + log.info("Publishing Order Events at {}", Instant.now()); + orderEventService.publishOrderEvents(); + } +} diff --git a/order-service/src/main/java/com/supersection/bookstore/orders/jobs/OrderProcessingJob.java b/order-service/src/main/java/com/supersection/bookstore/orders/jobs/OrderProcessingJob.java new file mode 100644 index 0000000..9d328cd --- /dev/null +++ b/order-service/src/main/java/com/supersection/bookstore/orders/jobs/OrderProcessingJob.java @@ -0,0 +1,29 @@ +package com.supersection.bookstore.orders.jobs; + +import com.supersection.bookstore.orders.domain.OrderService; +import java.time.Instant; +import net.javacrumbs.shedlock.core.LockAssert; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +class OrderProcessingJob { + private static final Logger log = LoggerFactory.getLogger(OrderProcessingJob.class); + + private final OrderService orderService; + + OrderProcessingJob(OrderService orderService) { + this.orderService = orderService; + } + + @Scheduled(cron = "${orders.new-orders-job-cron}") + @SchedulerLock(name = "processNewOrders") + public void processNewOrders() { + LockAssert.assertLocked(); + log.info("Processing new orders at {}", Instant.now()); + orderService.processNewOrders(); + } +} diff --git a/order-service/src/main/resources/application.properties b/order-service/src/main/resources/application.properties index ff72e98..09907d0 100644 --- a/order-service/src/main/resources/application.properties +++ b/order-service/src/main/resources/application.properties @@ -1,40 +1,44 @@ -spring.application.name=order-service -server.port=8082 -server.shutdown=graceful - -## Actuator Configuration -management.endpoints.web.exposure.include=* -management.info.git.mode=full - -## Order Service Configuration -orders.catalog-service-url=http://localhost:8081 -orders.order-events-exchange=orders-exchange -orders.new-orders-queue=new-orders -orders.delivered-orders-queue=delivered-orders -orders.cancelled-orders-queue=cancelled-orders -orders.error-orders-queue=error-orders - -## Database Configuration -spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:25432/postgres} -spring.datasource.username=${DB_USERNAME:postgres} -spring.datasource.password=${DB_PASSWORD:postgres} -spring.jpa.open-in-view=false -spring.jpa.show-sql=true - -## RabbitMQ Configuration -spring.rabbitmq.host=${RABBITMQ_HOST:localhost} -spring.rabbitmq.port=${RABBITMQ_PORT:5672} -spring.rabbitmq.username=${RABBITMQ_USERNAME:guest} -spring.rabbitmq.password=${RABBITMQ_PASSWORD:guest} - -## Resilience4j Configuration -resilience4j.retry.backends.catalog-service.max-attempts=2 -resilience4j.retry.backends.catalog-service.wait-duration=1s - -resilience4j.circuitbreaker.backends.catalog-service.sliding-window-type=COUNT_BASED -resilience4j.circuitbreaker.backends.catalog-service.sliding-window-size=6 -resilience4j.circuitbreaker.backends.catalog-service.minimum-number-of-calls=4 -resilience4j.circuitbreaker.backends.catalog-service.wait-duration-in-open-state=20s -resilience4j.circuitbreaker.backends.catalog-service.permitted-number-of-calls-in-half-open-state=2 -resilience4j.circuitbreaker.backends.catalog-service.failure-rate-threshold=50 +spring.application.name=order-service +server.port=8082 +server.shutdown=graceful + +## Cron Job Scheduler Configuration +orders.publish-order-events-job-cron=*/5 * * * * * +orders.new-orders-job-cron=*/10 * * * * * + +## Actuator Configuration +management.endpoints.web.exposure.include=* +management.info.git.mode=full + +## Order Service Configuration +orders.catalog-service-url=http://localhost:8081 +orders.order-events-exchange=orders-exchange +orders.new-orders-queue=new-orders +orders.delivered-orders-queue=delivered-orders +orders.cancelled-orders-queue=cancelled-orders +orders.error-orders-queue=error-orders + +## Database Configuration +spring.datasource.url=${DB_URL:jdbc:postgresql://localhost:25432/postgres} +spring.datasource.username=${DB_USERNAME:postgres} +spring.datasource.password=${DB_PASSWORD:postgres} +spring.jpa.open-in-view=false +spring.jpa.show-sql=true + +## RabbitMQ Configuration +spring.rabbitmq.host=${RABBITMQ_HOST:localhost} +spring.rabbitmq.port=${RABBITMQ_PORT:5672} +spring.rabbitmq.username=${RABBITMQ_USERNAME:guest} +spring.rabbitmq.password=${RABBITMQ_PASSWORD:guest} + +## Resilience4j Configuration +resilience4j.retry.backends.catalog-service.max-attempts=2 +resilience4j.retry.backends.catalog-service.wait-duration=1s + +resilience4j.circuitbreaker.backends.catalog-service.sliding-window-type=COUNT_BASED +resilience4j.circuitbreaker.backends.catalog-service.sliding-window-size=6 +resilience4j.circuitbreaker.backends.catalog-service.minimum-number-of-calls=4 +resilience4j.circuitbreaker.backends.catalog-service.wait-duration-in-open-state=20s +resilience4j.circuitbreaker.backends.catalog-service.permitted-number-of-calls-in-half-open-state=2 +resilience4j.circuitbreaker.backends.catalog-service.failure-rate-threshold=50 #resilience4j.circuitbreaker.backends.catalog-service.register-health-indicator=true \ No newline at end of file diff --git a/order-service/src/main/resources/db/migration/V1__create_order_tables.sql b/order-service/src/main/resources/db/migration/V1__create_order_tables.sql index bbc5f35..cdce89e 100644 --- a/order-service/src/main/resources/db/migration/V1__create_order_tables.sql +++ b/order-service/src/main/resources/db/migration/V1__create_order_tables.sql @@ -1,34 +1,34 @@ -create sequence order_id_seq start with 1 increment by 50; -create sequence order_item_id_seq start with 1 increment by 50; - -create table orders -( - id bigint default nextval('order_id_seq') not null, - order_number text not null unique, - username text not null, - customer_name text not null, - customer_email text not null, - customer_phone text not null, - delivery_address_line1 text not null, - delivery_address_line2 text, - delivery_address_city text not null, - delivery_address_state text not null, - delivery_address_zip_code text not null, - delivery_address_country text not null, - status text not null, - comments text, - created_at timestamp, - updated_at timestamp, - primary key (id) -); - -create table order_items -( - id bigint default nextval('order_item_id_seq') not null, - code text not null, - name text not null, - price numeric not null, - quantity integer not null, - primary key (id), - order_id bigint not null references orders (id) +create sequence order_id_seq start with 1 increment by 50; +create sequence order_item_id_seq start with 1 increment by 50; + +create table orders +( + id bigint default nextval('order_id_seq') not null, + order_number text not null unique, + username text not null, + customer_name text not null, + customer_email text not null, + customer_phone text not null, + delivery_address_line1 text not null, + delivery_address_line2 text, + delivery_address_city text not null, + delivery_address_state text not null, + delivery_address_zip_code text not null, + delivery_address_country text not null, + status text not null, + comments text, + created_at timestamp, + updated_at timestamp, + primary key (id) +); + +create table order_items +( + id bigint default nextval('order_item_id_seq') not null, + code text not null, + name text not null, + price numeric not null, + quantity integer not null, + primary key (id), + order_id bigint not null references orders (id) ); \ No newline at end of file diff --git a/order-service/src/main/resources/db/migration/V2__create_order_events_table.sql b/order-service/src/main/resources/db/migration/V2__create_order_events_table.sql new file mode 100644 index 0000000..3c456d7 --- /dev/null +++ b/order-service/src/main/resources/db/migration/V2__create_order_events_table.sql @@ -0,0 +1,13 @@ +create sequence order_event_id_seq start with 1 increment by 50; + +create table order_events +( + id bigint default nextval('order_event_id_seq') not null, + order_number text not null references orders (order_number), + event_id text not null unique, + event_type text not null, + payload text not null, + created_at timestamp not null, + updated_at timestamp, + primary key (id) +); diff --git a/order-service/src/main/resources/db/migration/V3__create_shedlock_table.sql b/order-service/src/main/resources/db/migration/V3__create_shedlock_table.sql new file mode 100644 index 0000000..e70aacd --- /dev/null +++ b/order-service/src/main/resources/db/migration/V3__create_shedlock_table.sql @@ -0,0 +1,6 @@ +CREATE TABLE shedlock( + name VARCHAR(64) NOT NULL PRIMARY KEY, + lock_until TIMESTAMPTZ NOT NULL, + locked_at TIMESTAMPTZ NOT NULL, + locked_by VARCHAR(255) NOT NULL +); \ No newline at end of file diff --git a/order-service/src/test/java/com/supersection/bookstore/orders/testdata/TestDataFactory.java b/order-service/src/test/java/com/supersection/bookstore/orders/testdata/TestDataFactory.java index 7d01316..1491ce3 100644 --- a/order-service/src/test/java/com/supersection/bookstore/orders/testdata/TestDataFactory.java +++ b/order-service/src/test/java/com/supersection/bookstore/orders/testdata/TestDataFactory.java @@ -3,9 +3,9 @@ import static org.instancio.Select.field; import com.supersection.bookstore.orders.domain.dtos.CreateOrderRequest; -import com.supersection.bookstore.orders.domain.dtos.OrderItem; import com.supersection.bookstore.orders.domain.models.Address; import com.supersection.bookstore.orders.domain.models.Customer; +import com.supersection.bookstore.orders.domain.models.OrderItem; import java.math.BigDecimal; import java.util.List; import java.util.Set;