diff --git a/.github/workflows/analytics-ci.yml b/.github/workflows/analytics-ci.yml index 64408f3..a48a986 100644 --- a/.github/workflows/analytics-ci.yml +++ b/.github/workflows/analytics-ci.yml @@ -1,110 +1,110 @@ -name: Analytics CI - -on: - push: - branches: [ main, develop ] - paths: - - 'analytics/**' - - 'common/**' - - 'build.gradle' - - 'settings.gradle' - pull_request: - branches: [ main ] - paths: - - 'analytics/**' - - 'common/**' - - 'build.gradle' - - 'settings.gradle' - workflow_dispatch: - -env: - ACR_NAME: acrdigitalgarage03 - IMAGE_NAME: hiorder/analytics - -jobs: - build-and-push: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'temurin' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 - with: - gradle-version: '8.13' - - - name: Cache Gradle packages - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Generate Gradle Wrapper - run: | - echo "Generating gradle wrapper..." - gradle wrapper --gradle-version 8.13 - chmod +x gradlew - echo "Testing gradle wrapper..." - ./gradlew --version - - - name: Build analytics module with dependencies - run: ./gradlew analytics:build -x test - - - name: Run analytics tests - run: ./gradlew analytics:test - - - name: Generate build timestamp - id: timestamp - run: echo "BUILD_TIME=$(date +'%y%m%d%H%M')" >> $GITHUB_OUTPUT - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Azure Container Registry - uses: azure/docker-login@v1 - with: - login-server: ${{ env.ACR_NAME }}.azurecr.io - username: ${{ secrets.ACR_USERNAME }} - password: ${{ secrets.ACR_PASSWORD }} - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ./analytics/Dockerfile - platforms: linux/amd64 - push: true - tags: | - ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.timestamp.outputs.BUILD_TIME }} - ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:latest - - - name: Output image tags - run: | - echo "๐ŸŽ‰ Image pushed successfully!" - echo "๐Ÿ“ฆ Image: ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}" - echo "๐Ÿท๏ธ Tags: ${{ steps.timestamp.outputs.BUILD_TIME }}, latest" - - - name: Upload test results - uses: actions/upload-artifact@v4 - if: always() - with: - name: analytics-test-results - path: analytics/build/reports/tests/test/ - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - if: success() - with: - name: analytics-jar - path: analytics/build/libs/*.jar +name: Analytics CI + +on: + push: + branches: [ main, develop ] + paths: + - 'analytics/**' + - 'common/**' + - 'build.gradle' + - 'settings.gradle' + pull_request: + branches: [ main ] + paths: + - 'analytics/**' + - 'common/**' + - 'build.gradle' + - 'settings.gradle' + workflow_dispatch: + +env: + ACR_NAME: acrdigitalgarage03 + IMAGE_NAME: hiorder/analytics + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + with: + gradle-version: '8.13' + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Generate Gradle Wrapper + run: | + echo "Generating gradle wrapper..." + gradle wrapper --gradle-version 8.13 + chmod +x gradlew + echo "Testing gradle wrapper..." + ./gradlew --version + + - name: Build analytics module with dependencies + run: ./gradlew analytics:build -x test + + - name: Run analytics tests + run: ./gradlew analytics:test + + - name: Generate build timestamp + id: timestamp + run: echo "BUILD_TIME=$(date +'%y%m%d%H%M')" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Azure Container Registry + uses: azure/docker-login@v1 + with: + login-server: ${{ env.ACR_NAME }}.azurecr.io + username: ${{ secrets.ACR_USERNAME }} + password: ${{ secrets.ACR_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./analytics/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.timestamp.outputs.BUILD_TIME }} + ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:latest + + - name: Output image tags + run: | + echo "๐ŸŽ‰ Image pushed successfully!" + echo "๐Ÿ“ฆ Image: ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}" + echo "๐Ÿท๏ธ Tags: ${{ steps.timestamp.outputs.BUILD_TIME }}, latest" + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: analytics-test-results + path: analytics/build/reports/tests/test/ + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + if: success() + with: + name: analytics-jar + path: analytics/build/libs/*.jar diff --git a/analytics/Dockerfile b/analytics/Dockerfile index d9002e9..4863c04 100644 --- a/analytics/Dockerfile +++ b/analytics/Dockerfile @@ -1,51 +1,51 @@ -# Analytics ์„œ๋น„์Šค์šฉ Dockerfile -FROM eclipse-temurin:21-jdk-alpine AS builder - -# ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ • -WORKDIR /app - -# Gradle Wrapper์™€ ์„ค์ • ํŒŒ์ผ ๋ณต์‚ฌ -COPY gradlew . -COPY gradle/wrapper gradle/wrapper -COPY build.gradle . -COPY settings.gradle . - -# ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ -COPY common common/ -COPY analytics analytics/ - -# ์‹คํ–‰ ๊ถŒํ•œ ๋ถ€์—ฌ ๋ฐ ๋นŒ๋“œ -RUN chmod +x ./gradlew -RUN ./gradlew analytics:build -x test --no-daemon - -# ์‹คํ–‰ ๋‹จ๊ณ„ -FROM eclipse-temurin:21-jre-alpine - -# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‚ฌ์šฉ์ž ์ƒ์„ฑ -RUN addgroup -g 1001 -S appgroup && \ - adduser -u 1001 -S appuser -G appgroup - -# ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ • -WORKDIR /app - -# ๋นŒ๋“œ๋œ JAR ํŒŒ์ผ ๋ณต์‚ฌ -COPY --from=builder /app/analytics/build/libs/analytics-*.jar app.jar - -# ํŒŒ์ผ ์†Œ์œ ๊ถŒ ๋ณ€๊ฒฝ -RUN chown -R appuser:appgroup /app - -# ์‚ฌ์šฉ์ž ๋ณ€๊ฒฝ -USER appuser - -# ํฌํŠธ ๋…ธ์ถœ -EXPOSE 8080 - -# ํ—ฌ์Šค์ฒดํฌ ์ถ”๊ฐ€ -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1 - -# JVM ์˜ต์…˜ ์„ค์ • -ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication" - -# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ +# Analytics ์„œ๋น„์Šค์šฉ Dockerfile +FROM eclipse-temurin:21-jdk-alpine AS builder + +# ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ • +WORKDIR /app + +# Gradle Wrapper์™€ ์„ค์ • ํŒŒ์ผ ๋ณต์‚ฌ +COPY gradlew . +COPY gradle/wrapper gradle/wrapper +COPY build.gradle . +COPY settings.gradle . + +# ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ +COPY common common/ +COPY analytics analytics/ + +# ์‹คํ–‰ ๊ถŒํ•œ ๋ถ€์—ฌ ๋ฐ ๋นŒ๋“œ +RUN chmod +x ./gradlew +RUN ./gradlew analytics:build -x test --no-daemon + +# ์‹คํ–‰ ๋‹จ๊ณ„ +FROM eclipse-temurin:21-jre-alpine + +# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‚ฌ์šฉ์ž ์ƒ์„ฑ +RUN addgroup -g 1001 -S appgroup && \ + adduser -u 1001 -S appuser -G appgroup + +# ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ • +WORKDIR /app + +# ๋นŒ๋“œ๋œ JAR ํŒŒ์ผ ๋ณต์‚ฌ +COPY --from=builder /app/analytics/build/libs/analytics-*.jar app.jar + +# ํŒŒ์ผ ์†Œ์œ ๊ถŒ ๋ณ€๊ฒฝ +RUN chown -R appuser:appgroup /app + +# ์‚ฌ์šฉ์ž ๋ณ€๊ฒฝ +USER appuser + +# ํฌํŠธ ๋…ธ์ถœ +EXPOSE 8080 + +# ํ—ฌ์Šค์ฒดํฌ ์ถ”๊ฐ€ +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1 + +# JVM ์˜ต์…˜ ์„ค์ • +ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication" + +# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"] \ No newline at end of file diff --git a/analytics/src/main/java/com/ktds/hi/analytics/infra/config/JpaConfig.java b/analytics/src/main/java/com/ktds/hi/analytics/infra/config/JpaConfig.java index a96a216..497980e 100644 --- a/analytics/src/main/java/com/ktds/hi/analytics/infra/config/JpaConfig.java +++ b/analytics/src/main/java/com/ktds/hi/analytics/infra/config/JpaConfig.java @@ -1,5 +1,6 @@ package com.ktds.hi.analytics.infra.config; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @@ -9,7 +10,15 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; * JPA Auditing ๋ฐ Repository ์Šค์บ” ์„ค์ • */ @Configuration -@EnableJpaAuditing -@EnableJpaRepositories(basePackages = "com.ktds.hi.analytics.infra.gateway.repository") +@EnableJpaRepositories(basePackages = { + // "com.ktds.hi.store.infra.gateway.entity", + "com.ktds.hi.common.repository" +}) +@EntityScan(basePackages = { + "com.ktds.hi.store.infra.gateway.entity", + "com.ktds.hi.common.entity", + "com.ktds.hi.common.audit" +}) +@EnableJpaAuditing(auditorAwareRef = "customAuditorAware") public class JpaConfig { } diff --git a/analytics/src/main/java/com/ktds/hi/analytics/infra/config/RedisConfig.java b/analytics/src/main/java/com/ktds/hi/analytics/infra/config/RedisConfig.java index 9c89b1c..c4acd58 100644 --- a/analytics/src/main/java/com/ktds/hi/analytics/infra/config/RedisConfig.java +++ b/analytics/src/main/java/com/ktds/hi/analytics/infra/config/RedisConfig.java @@ -14,6 +14,7 @@ import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +// import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; diff --git a/common/src/main/java/com/ktds/hi/common/CommonModuleConfiguration.java b/common/src/main/java/com/ktds/hi/common/CommonModuleConfiguration.java index 04e52d1..e2cb329 100644 --- a/common/src/main/java/com/ktds/hi/common/CommonModuleConfiguration.java +++ b/common/src/main/java/com/ktds/hi/common/CommonModuleConfiguration.java @@ -15,7 +15,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; "com.ktds.hi.common.entity", "com.ktds.hi.common.audit" }) -@EnableJpaRepositories(basePackages = "com.ktds.hi.common.repository") +// @EnableJpaRepositories(basePackages = "com.ktds.hi.common.repository") public class CommonModuleConfiguration { // ์„ค์ • ํด๋ž˜์Šค๋Š” ์–ด๋…ธํ…Œ์ด์…˜๋งŒ์œผ๋กœ๋„ ์ถฉ๋ถ„ } diff --git a/store/src/main/java/com/ktds/hi/store/StoreApplication.java b/store/src/main/java/com/ktds/hi/store/StoreApplication.java index b119d24..0a3d77f 100644 --- a/store/src/main/java/com/ktds/hi/store/StoreApplication.java +++ b/store/src/main/java/com/ktds/hi/store/StoreApplication.java @@ -1,33 +1,44 @@ -package com.ktds.hi.store; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -import org.springframework.web.client.RestTemplate; - -/** - * ์ถ”์ฒœ ์„œ๋น„์Šค ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํด๋ž˜์Šค - * ๊ฐ€๊ฒŒ ์ถ”์ฒœ, ์ทจํ–ฅ ๋ถ„์„ ๊ธฐ๋Šฅ์„ ์ œ๊ณต - * - * @author ํ•˜์ด์˜ค๋” ๊ฐœ๋ฐœํŒ€ - * @version 1.0.0 - */ -@SpringBootApplication(scanBasePackages = { - "com.ktds.hi.store", - "com.ktds.hi.common" -}) - -@EnableJpaAuditing -public class StoreApplication { - - public static void main(String[] args) { - SpringApplication.run(StoreApplication.class, args); - } - - // ๐Ÿ‘ˆ ์ด ๋ถ€๋ถ„๋งŒ ์ถ”๊ฐ€ - @Bean - public RestTemplate restTemplate() { - return new RestTemplate(); - } -} +package com.ktds.hi.store; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.Bean; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.web.client.RestTemplate; + +/** + * ์ถ”์ฒœ ์„œ๋น„์Šค ๋ฉ”์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํด๋ž˜์Šค + * ๊ฐ€๊ฒŒ ์ถ”์ฒœ, ์ทจํ–ฅ ๋ถ„์„ ๊ธฐ๋Šฅ์„ ์ œ๊ณต + * + * @author ํ•˜์ด์˜ค๋” ๊ฐœ๋ฐœํŒ€ + * @version 1.0.0 + */ +@SpringBootApplication(scanBasePackages = { + "com.ktds.hi.store", + "com.ktds.hi.common" +}) +@EnableJpaRepositories(basePackages = { + "com.ktds.hi.store.infra.gateway.repository", // ๐Ÿ‘ˆ MenuJpaRepository ํŒจํ‚ค์ง€ + "com.ktds.hi.common.repository" +}) +@EntityScan(basePackages = { + "com.ktds.hi.store.infra.gateway.entity", + "com.ktds.hi.common.entity" +}) +@EnableJpaAuditing(auditorAwareRef = "customAuditorAware") +public class StoreApplication { + + public static void main(String[] args) { + SpringApplication.run(StoreApplication.class, args); + } + + // ๊ธฐ์กด ๋นˆ๋“ค... + @Bean + @ConditionalOnMissingBean(RestTemplate.class) // ๐Ÿ‘ˆ ๋‹ค๋ฅธ ๋นˆ์ด ์—†์„ ๋•Œ๋งŒ ์ƒ์„ฑ + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} \ No newline at end of file diff --git a/store/src/main/java/com/ktds/hi/store/config/RedisConfig.java b/store/src/main/java/com/ktds/hi/store/config/RedisConfig.java new file mode 100644 index 0000000..7bb997a --- /dev/null +++ b/store/src/main/java/com/ktds/hi/store/config/RedisConfig.java @@ -0,0 +1,46 @@ +package com.ktds.hi.store.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + @Value("${spring.data.redis.host:localhost}") + private String redisHost; + + @Value("${spring.data.redis.port:6379}") + private int redisPort; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(redisHost, redisPort); + } + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + // ์ง๋ ฌํ™” ์„ค์ • + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); + + template.afterPropertiesSet(); + return template; + } + + @Bean + public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) { + return new StringRedisTemplate(connectionFactory); + } +} diff --git a/store/src/main/java/com/ktds/hi/store/config/RestTemplateConfig.java b/store/src/main/java/com/ktds/hi/store/config/RestTemplateConfig.java new file mode 100644 index 0000000..1aee4b1 --- /dev/null +++ b/store/src/main/java/com/ktds/hi/store/config/RestTemplateConfig.java @@ -0,0 +1,26 @@ +package com.ktds.hi.store.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.web.client.RestTemplate; +import org.springframework.boot.web.client.RestTemplateBuilder; + +import java.time.Duration; + +@Configuration +public class RestTemplateConfig { + + @Bean + @Primary + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + // ๋˜๋Š” ๋” ๋‚˜์€ ์„ค์ •์œผ๋กœ + @Bean("configuredRestTemplate") + public RestTemplate configuredRestTemplate(RestTemplateBuilder builder) { + return builder.connectTimeout(Duration.ofSeconds(5)).readTimeout(Duration.ofSeconds(10)) + .build(); + } +} \ No newline at end of file diff --git a/store/src/main/java/com/ktds/hi/store/infra/gateway/CacheAdapter.java b/store/src/main/java/com/ktds/hi/store/infra/gateway/CacheAdapter.java index 487e172..60314d6 100644 --- a/store/src/main/java/com/ktds/hi/store/infra/gateway/CacheAdapter.java +++ b/store/src/main/java/com/ktds/hi/store/infra/gateway/CacheAdapter.java @@ -1,6 +1,7 @@ package com.ktds.hi.store.infra.gateway; import com.ktds.hi.store.biz.usecase.out.CachePort; +import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; @@ -15,6 +16,7 @@ import java.util.Optional; */ @Component @RequiredArgsConstructor +// @NoArgsConstructor(force = true) @Slf4j public class CacheAdapter implements CachePort { diff --git a/store/src/main/java/com/ktds/hi/store/infra/gateway/entity/MenuEntity.java b/store/src/main/java/com/ktds/hi/store/infra/gateway/entity/MenuEntity.java index 9c3c65b..ebf171e 100644 --- a/store/src/main/java/com/ktds/hi/store/infra/gateway/entity/MenuEntity.java +++ b/store/src/main/java/com/ktds/hi/store/infra/gateway/entity/MenuEntity.java @@ -9,6 +9,7 @@ import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import java.math.BigDecimal; import java.time.LocalDateTime; /**