From 9c6a185d61a40fa9d63af82e207ed60e6365db29 Mon Sep 17 00:00:00 2001 From: Niall Thomson Date: Wed, 3 Jan 2024 17:05:27 +0000 Subject: [PATCH] Migrated orders to postgres and added some basic tests --- deploy/docker-compose/docker-compose.yml | 22 ++-- src/catalog/docker-compose.yml | 2 +- src/orders/docker-compose.yml | 26 ++--- src/orders/pom.xml | 29 +++-- .../orders/config/PersistenceConfig.java | 17 ++- .../sample/orders/entities/OrderEntity.java | 16 ++- .../orders/entities/OrderItemEntity.java | 1 - .../src/main/resources/application-mysql.yml | 4 - src/orders/src/main/resources/application.yml | 4 +- .../migration/V1__Initial.sql} | 0 ...tricsTest.java => OrdersMetricsTests.java} | 2 +- .../services/OrderServicePostgresTests.java | 104 ++++++++++++++++++ 12 files changed, 183 insertions(+), 44 deletions(-) delete mode 100644 src/orders/src/main/resources/application-mysql.yml rename src/orders/src/main/resources/{schema.sql => db/migration/V1__Initial.sql} (100%) rename src/orders/src/test/java/com/amazon/sample/orders/metrics/{OrdersMetricsTest.java => OrdersMetricsTests.java} (99%) create mode 100644 src/orders/src/test/java/com/amazon/sample/orders/services/OrderServicePostgresTests.java diff --git a/deploy/docker-compose/docker-compose.yml b/deploy/docker-compose/docker-compose.yml index 000837e54..664335653 100644 --- a/deploy/docker-compose/docker-compose.yml +++ b/deploy/docker-compose/docker-compose.yml @@ -87,8 +87,8 @@ services: environment: - JAVA_OPTS=-XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/urandom - SERVER_TOMCAT_ACCESSLOG_ENABLED=true - - SPRING_PROFILES_ACTIVE=mysql,rabbitmq - - SPRING_DATASOURCE_URL=jdbc:mariadb://orders-db:3306/orders + - SPRING_PROFILES_ACTIVE=rabbitmq + - SPRING_DATASOURCE_URL=jdbc:postgresql://orders-db:5432/orders - SPRING_DATASOURCE_USERNAME=orders_user - SPRING_DATASOURCE_PASSWORD=${MYSQL_PASSWORD} - SPRING_RABBITMQ_HOST=rabbitmq @@ -97,15 +97,21 @@ services: - ALL orders-db: - image: mariadb:10.9 + image: postgres:16.1 hostname: orders-db restart: always + security_opt: + - no-new-privileges:true environment: - - MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD} - - MYSQL_ALLOW_EMPTY_PASSWORD=true - - MYSQL_DATABASE=orders - - MYSQL_USER=orders_user - - MYSQL_PASSWORD=${MYSQL_PASSWORD} + - reschedule=on-node-failure + - POSTGRES_PASSWORD=${MYSQL_PASSWORD} + - POSTGRES_DB=orders + - POSTGRES_USER=orders_user + healthcheck: + test: [ "CMD-SHELL", "pg_isready -d orders -U orders_user" ] + interval: 10s + timeout: 5s + retries: 30 mem_limit: 128m checkout: diff --git a/src/catalog/docker-compose.yml b/src/catalog/docker-compose.yml index f456e9b40..704e20080 100644 --- a/src/catalog/docker-compose.yml +++ b/src/catalog/docker-compose.yml @@ -10,7 +10,7 @@ services: image: microservices-demo/catalog hostname: catalog depends_on: - - catalog-db + - catalog-db restart: always cap_drop: - all diff --git a/src/orders/docker-compose.yml b/src/orders/docker-compose.yml index 6c27cf406..0bb8d6af1 100644 --- a/src/orders/docker-compose.yml +++ b/src/orders/docker-compose.yml @@ -22,8 +22,8 @@ services: environment: - reschedule=on-node-failure - SERVER_TOMCAT_ACCESSLOG_ENABLED=true - - SPRING_PROFILES_ACTIVE=mysql,rabbitmq - - SPRING_DATASOURCE_URL=jdbc:mariadb://orders-db:3306/orders + - SPRING_PROFILES_ACTIVE=rabbitmq + - SPRING_DATASOURCE_URL=jdbc:postgresql://orders-db:5432/orders - SPRING_DATASOURCE_USERNAME=orders_user - SPRING_DATASOURCE_PASSWORD=${MYSQL_PASSWORD} - SPRING_RABBITMQ_HOST=rabbitmq @@ -42,28 +42,22 @@ services: # nosemgrep: yaml.docker-compose.security.writable-filesystem-service.writable-filesystem-service orders-db: - image: mysql:5.7 + image: postgres:16.1 hostname: orders-db restart: always security_opt: - no-new-privileges:true environment: - reschedule=on-node-failure - - MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD} - - MYSQL_ALLOW_EMPTY_PASSWORD=true - - MYSQL_DATABASE=orders - - MYSQL_USER=orders_user - - MYSQL_PASSWORD=${MYSQL_PASSWORD} + - POSTGRES_PASSWORD=${MYSQL_PASSWORD} + - POSTGRES_DB=orders + - POSTGRES_USER=orders_user ports: - - "3306:3306" + - "5432:5432" healthcheck: - test: - [ - "CMD-SHELL", - "mysql -u root -p${MYSQL_PASSWORD} -e 'SELECT 1 cache'" - ] - interval: 1s - timeout: 3s + test: [ "CMD-SHELL", "pg_isready -d orders -U orders_user" ] + interval: 10s + timeout: 5s retries: 30 # nosemgrep: yaml.docker-compose.security.writable-filesystem-service.writable-filesystem-service diff --git a/src/orders/pom.xml b/src/orders/pom.xml index df2abe12c..a834eeea1 100644 --- a/src/orders/pom.xml +++ b/src/orders/pom.xml @@ -35,11 +35,6 @@ spring-boot-properties-migrator runtime - - org.springframework.boot - spring-boot-starter-validation - runtime - io.micrometer micrometer-registry-prometheus @@ -49,9 +44,27 @@ spring-boot-starter-data-jdbc - org.mariadb.jdbc - mariadb-java-client - 3.3.2 + org.flywaydb + flyway-core + + + org.postgresql + postgresql + + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + postgresql + test + + + io.rest-assured + rest-assured + test org.springframework.boot diff --git a/src/orders/src/main/java/com/amazon/sample/orders/config/PersistenceConfig.java b/src/orders/src/main/java/com/amazon/sample/orders/config/PersistenceConfig.java index 39dd4af10..2f5453d1a 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/config/PersistenceConfig.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/config/PersistenceConfig.java @@ -1,15 +1,20 @@ package com.amazon.sample.orders.config; - import com.amazon.sample.orders.entities.OrderEntity; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; +import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; +import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration; +import org.springframework.data.relational.RelationalManagedTypes; +import org.springframework.data.relational.core.mapping.NamingStrategy; import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback; +import java.util.Optional; import java.util.UUID; @Configuration -public class PersistenceConfig { +public class PersistenceConfig extends AbstractJdbcConfiguration { @Bean BeforeConvertCallback beforeSaveCallback() { return (entity) -> { @@ -19,4 +24,12 @@ BeforeConvertCallback beforeSaveCallback() { return entity; }; } + + @Override + public JdbcMappingContext jdbcMappingContext(Optional namingStrategy, + JdbcCustomConversions customConversions, RelationalManagedTypes jdbcManagedTypes) { + JdbcMappingContext context = super.jdbcMappingContext(namingStrategy, customConversions, jdbcManagedTypes); + context.setForceQuote(false); + return context; + } } diff --git a/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderEntity.java b/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderEntity.java index b35cbdfc7..04c652eab 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderEntity.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderEntity.java @@ -33,9 +33,19 @@ public class OrderEntity { private String lastName; private String email; - @MappedCollection(keyColumn = "PRODUCT_ID") + @MappedCollection(keyColumn = "product_id") private List items = new ArrayList<>(); + public OrderEntity() { + + } + + public OrderEntity(String firstName, String lastName, String email) { + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + } + public String getId() { return id; } @@ -76,8 +86,10 @@ public void setItems(List items) { this.items = items; } - public void addItem(OrderItemEntity item) { + public OrderEntity addItem(OrderItemEntity item) { this.items.add(item); + + return this; } @Override diff --git a/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderItemEntity.java b/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderItemEntity.java index 367ebcf47..dc921c7c2 100644 --- a/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderItemEntity.java +++ b/src/orders/src/main/java/com/amazon/sample/orders/entities/OrderItemEntity.java @@ -18,7 +18,6 @@ package com.amazon.sample.orders.entities; -import org.springframework.data.annotation.Id; import org.springframework.data.relational.core.mapping.Table; @Table diff --git a/src/orders/src/main/resources/application-mysql.yml b/src/orders/src/main/resources/application-mysql.yml deleted file mode 100644 index 6ed773730..000000000 --- a/src/orders/src/main/resources/application-mysql.yml +++ /dev/null @@ -1,4 +0,0 @@ -spring: - sql: - init: - mode: always \ No newline at end of file diff --git a/src/orders/src/main/resources/application.yml b/src/orders/src/main/resources/application.yml index b59a3fef9..98b6b50a1 100644 --- a/src/orders/src/main/resources/application.yml +++ b/src/orders/src/main/resources/application.yml @@ -5,4 +5,6 @@ management: include: '*' server: - port: ${port:8080} \ No newline at end of file + port: ${port:8080} + +spring.flyway.baseline-on-migrate: true \ No newline at end of file diff --git a/src/orders/src/main/resources/schema.sql b/src/orders/src/main/resources/db/migration/V1__Initial.sql similarity index 100% rename from src/orders/src/main/resources/schema.sql rename to src/orders/src/main/resources/db/migration/V1__Initial.sql diff --git a/src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTest.java b/src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTests.java similarity index 99% rename from src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTest.java rename to src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTests.java index bf64a4881..ea499d18d 100644 --- a/src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTest.java +++ b/src/orders/src/test/java/com/amazon/sample/orders/metrics/OrdersMetricsTests.java @@ -32,7 +32,7 @@ import static org.assertj.core.api.BDDAssertions.then; -public class OrdersMetricsTest { +public class OrdersMetricsTests { private MeterRegistry meterRegistry; private final String PRODUCT_1 = "Product1"; diff --git a/src/orders/src/test/java/com/amazon/sample/orders/services/OrderServicePostgresTests.java b/src/orders/src/test/java/com/amazon/sample/orders/services/OrderServicePostgresTests.java new file mode 100644 index 000000000..246e69317 --- /dev/null +++ b/src/orders/src/test/java/com/amazon/sample/orders/services/OrderServicePostgresTests.java @@ -0,0 +1,104 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: MIT-0 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.amazon.sample.orders.services; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.hasSize; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.PostgreSQLContainer; + +import com.amazon.sample.orders.entities.OrderEntity; +import com.amazon.sample.orders.repositories.OrderRepository; + +import io.restassured.RestAssured; +import io.restassured.http.ContentType; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class OrderServicePostgresTests { + + @LocalServerPort + private Integer port; + + static PostgreSQLContainer postgres = new PostgreSQLContainer<>( + "postgres:16.1"); + + @BeforeAll + static void beforeAll() { + postgres.start(); + } + + @AfterAll + static void afterAll() { + postgres.stop(); + } + + @DynamicPropertySource + static void configureProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", postgres::getJdbcUrl); + registry.add("spring.datasource.username", postgres::getUsername); + registry.add("spring.datasource.password", postgres::getPassword); + } + + @Autowired + OrderRepository orderRepository; + + @BeforeEach + void setUp() { + RestAssured.baseURI = "http://localhost:" + port; + orderRepository.deleteAll(); + } + + @Test + void shouldGetEmptyOrders() { + given() + .contentType(ContentType.JSON) + .when() + .get("/orders") + .then() + .statusCode(200) + .body(".", hasSize(0)); + } + + @Test + void shouldGetAllOrders() { + List orders = List.of( + new OrderEntity("first", "last", "email@example.com"), + new OrderEntity("first", "last", "email@example.com")); + orderRepository.saveAll(orders); + + given() + .contentType(ContentType.JSON) + .when() + .get("/orders") + .then() + .statusCode(200) + .body(".", hasSize(2)); + } +} \ No newline at end of file