-
Notifications
You must be signed in to change notification settings - Fork 35
[volume-10] Collect, Stack, Zip #229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: kilian-develop
Are you sure you want to change the base?
Changes from all commits
0876588
72603de
ab12a68
f7b2134
1e868e9
4c47398
c8fe05b
8ed3b46
05a212c
3d54e49
18e0a6b
9d527c7
b15faea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| dependencies { | ||
| implementation(project(":core:infra:database:mysql:mysql-config")) | ||
| implementation(project(":supports:jackson")) | ||
| implementation(project(":supports:logging")) | ||
| implementation(project(":supports:monitoring")) | ||
|
|
||
| //service | ||
| implementation(project(":core:service")) | ||
|
|
||
| //domain | ||
| implementation(project(":core:domain")) | ||
|
|
||
| // web | ||
| implementation("org.springframework.boot:spring-boot-starter-web") | ||
|
|
||
| //batch | ||
| implementation("org.springframework.boot:spring-boot-starter-batch") | ||
|
|
||
| implementation("org.springframework:spring-tx") | ||
| implementation("org.springframework.boot:spring-boot-starter-actuator") | ||
|
|
||
| // test-fixtures | ||
| testImplementation(project(":core:infra:database:mysql:mysql-config")) | ||
| testImplementation(testFixtures(project(":core:domain"))) | ||
| testImplementation(testFixtures(project(":core:infra:database:mysql"))) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.loopers; | ||
|
|
||
| import org.springframework.boot.SpringApplication; | ||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
| import org.springframework.scheduling.annotation.EnableScheduling; | ||
|
|
||
| @EnableScheduling | ||
| @SpringBootApplication | ||
| public class CommerceBatchApplication { | ||
|
|
||
| public static void main(String[] args) { | ||
| SpringApplication.run(CommerceBatchApplication.class, args); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package com.loopers.application.batch.product; | ||
|
|
||
| import com.loopers.core.domain.product.repository.DailyProductMetricRepository; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.batch.core.configuration.annotation.StepScope; | ||
| import org.springframework.batch.core.partition.support.Partitioner; | ||
| import org.springframework.batch.item.ExecutionContext; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| @Component | ||
| @StepScope | ||
| @RequiredArgsConstructor | ||
| public class MonthlyProductMetricBatchPartitioner implements Partitioner { | ||
|
|
||
| private final DailyProductMetricRepository dailyProductMetricRepository; | ||
|
|
||
| @Value("#{jobParameters['startDate']}") | ||
| private String startDateParam; | ||
|
|
||
| @Value("#{jobParameters['endDate']}") | ||
| private String endDateParam; | ||
|
|
||
| @Override | ||
| public Map<String, ExecutionContext> partition(int gridSize) { | ||
| LocalDate startDate = LocalDate.parse(startDateParam); | ||
| LocalDate endDate = LocalDate.parse(endDateParam); | ||
| Long totalCount = dailyProductMetricRepository.countDistinctProductIdsBy(startDate, endDate); | ||
|
|
||
| if (totalCount == 0) { | ||
| return Collections.emptyMap(); | ||
| } | ||
|
|
||
| long targetSize = (totalCount / gridSize) + 1; | ||
| Map<String, ExecutionContext> partitions = new HashMap<>(); | ||
|
|
||
| for (int i = 0; i < gridSize; i++) { | ||
| ExecutionContext context = new ExecutionContext(); | ||
|
|
||
| context.putLong("partitionOffset", i * targetSize); | ||
| context.putLong("partitionLimit", targetSize); | ||
| context.putString("startDate", startDateParam); | ||
| context.putString("endDate", endDateParam); | ||
|
|
||
| partitions.put("partition" + i, context); | ||
| } | ||
|
|
||
| return partitions; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package com.loopers.application.batch.product; | ||
|
|
||
| import com.loopers.core.domain.product.repository.DailyProductMetricRepository; | ||
| import com.loopers.core.domain.product.vo.ProductMetricAggregation; | ||
| import lombok.NonNull; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.batch.item.ExecutionContext; | ||
| import org.springframework.batch.item.ItemStreamReader; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.Iterator; | ||
| import java.util.Objects; | ||
|
|
||
| @RequiredArgsConstructor | ||
| public class MonthlyProductMetricBatchReader implements ItemStreamReader<ProductMetricAggregation> { | ||
|
|
||
| private final DailyProductMetricRepository dailyProductMetricRepository; | ||
| private Iterator<ProductMetricAggregation> iterator; | ||
|
|
||
| @Override | ||
| public void open(@NonNull ExecutionContext executionContext) { | ||
| LocalDate startDate = LocalDate.parse(executionContext.getString("startDate")); | ||
| LocalDate endDate = LocalDate.parse(executionContext.getString("endDate")); | ||
| long partitionOffset = executionContext.getLong("partitionOffset"); | ||
| long partitionLimit = executionContext.getLong("partitionLimit"); | ||
|
|
||
| this.iterator = dailyProductMetricRepository.findAggregatedBy(startDate, endDate, partitionOffset, partitionLimit) | ||
| .iterator(); | ||
| } | ||
|
|
||
| @Override | ||
| public ProductMetricAggregation read() { | ||
| if (Objects.isNull(iterator) || !iterator.hasNext()) { | ||
| return null; | ||
| } | ||
|
|
||
| return iterator.next(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.loopers.application.batch.product; | ||
|
|
||
| import com.loopers.core.domain.product.MonthlyProductMetric; | ||
| import com.loopers.core.domain.product.repository.MonthlyProductMetricRepository; | ||
| import com.loopers.core.domain.product.vo.ProductMetricAggregation; | ||
| import com.loopers.core.domain.product.vo.YearMonth; | ||
| import lombok.NonNull; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.batch.core.configuration.annotation.StepScope; | ||
| import org.springframework.batch.item.Chunk; | ||
| import org.springframework.batch.item.ExecutionContext; | ||
| import org.springframework.batch.item.ItemStreamWriter; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.List; | ||
|
|
||
| @StepScope | ||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class MonthlyProductMetricBatchWriter implements ItemStreamWriter<ProductMetricAggregation> { | ||
|
|
||
| private final MonthlyProductMetricRepository repository; | ||
| private YearMonth yearMonth; | ||
|
|
||
| @Override | ||
| public void open(@NonNull ExecutionContext executionContext) { | ||
| LocalDate startDate = LocalDate.parse(executionContext.getString("startDate")); | ||
| this.yearMonth = YearMonth.from(startDate); | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Override | ||
| public void write(@NonNull Chunk<? extends ProductMetricAggregation> chunk) { | ||
| List<MonthlyProductMetric> monthlyMetrics = chunk.getItems().stream() | ||
| .map(aggregation -> aggregation.to(yearMonth)) | ||
| .toList(); | ||
|
|
||
| repository.bulkUpsert(monthlyMetrics); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package com.loopers.application.batch.product; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.batch.core.Job; | ||
| import org.springframework.batch.core.JobParameters; | ||
| import org.springframework.batch.core.JobParametersBuilder; | ||
| import org.springframework.batch.core.JobParametersInvalidException; | ||
| import org.springframework.batch.core.launch.JobLauncher; | ||
| import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; | ||
| import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; | ||
| import org.springframework.batch.core.repository.JobRestartException; | ||
| import org.springframework.scheduling.annotation.Scheduled; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.time.YearMonth; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class MonthlyProductMetricScheduler { | ||
|
|
||
| private final JobLauncher jobLauncher; | ||
| private final Job monthlyProductMetricJob; | ||
|
|
||
| @Scheduled(cron = "0 0 2 1 * ?") | ||
| public void run() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { | ||
|
Comment on lines
+25
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๋ฐฐ์น ์์ ์คํ ์คํจ ์ ๊ด์ฐฐ์ฑ๊ณผ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ๊ฐ์ ํ์ธ์. ์ค์ผ์ค๋ฌ ๋ฉ์๋๊ฐ 4๊ฐ์ ์ฒดํฌ ์์ธ๋ฅผ ๋์ง์ง๋ง ๋ก๊น ์ด๋ ์์ธ ์ฒ๋ฆฌ๊ฐ ์์ด, ๋ฐฐ์น ์์ ์คํจ๊ฐ ์กฐ์ฉํ ๋ฌด์๋ ์ ์์ต๋๋ค. ์ด์ ํ๊ฒฝ์์ ๋ฌธ์ ์ง๋จ์ ์ด๋ ต๊ฒ ๋ง๋ญ๋๋ค. ๐ ๋ก๊น ๊ณผ ์์ธ ์ฒ๋ฆฌ ์ถ๊ฐ ์ ์ @Scheduled(cron = "0 0 2 1 * ?")
- public void run() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException {
+ public void run() {
+ try {
+ log.info("Starting monthly product metric aggregation batch job");
YearMonth lastMonth = YearMonth.now().minusMonths(1);
LocalDate startDate = lastMonth.atDay(1);
LocalDate endDate = lastMonth.atEndOfMonth();
JobParameters jobParameters = new JobParametersBuilder()
.addString("startDate", startDate.toString())
.addString("endDate", endDate.toString())
.addLong("timestamp", System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(monthlyProductMetricJob, jobParameters);
+ log.info("Monthly product metric aggregation batch job completed successfully");
+ } catch (JobExecutionAlreadyRunningException e) {
+ log.warn("Monthly product metric batch job is already running", e);
+ } catch (JobInstanceAlreadyCompleteException | JobRestartException | JobParametersInvalidException e) {
+ log.error("Failed to execute monthly product metric batch job", e);
+ }
}Lombok์
๐ค Prompt for AI Agents |
||
| YearMonth lastMonth = YearMonth.now().minusMonths(1); | ||
| LocalDate startDate = lastMonth.atDay(1); | ||
| LocalDate endDate = lastMonth.atEndOfMonth(); | ||
|
|
||
| JobParameters jobParameters = new JobParametersBuilder() | ||
| .addString("startDate", startDate.toString()) | ||
| .addString("endDate", endDate.toString()) | ||
| .addLong("timestamp", System.currentTimeMillis()) | ||
| .toJobParameters(); | ||
|
|
||
| jobLauncher.run(monthlyProductMetricJob, jobParameters); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package com.loopers.application.batch.product; | ||
|
|
||
| import com.loopers.core.domain.product.repository.DailyProductMetricRepository; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.batch.core.configuration.annotation.StepScope; | ||
| import org.springframework.batch.core.partition.support.Partitioner; | ||
| import org.springframework.batch.item.ExecutionContext; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| @Component | ||
| @StepScope | ||
| @RequiredArgsConstructor | ||
| public class WeeklyProductMetricBatchPartitioner implements Partitioner { | ||
|
|
||
| private final DailyProductMetricRepository dailyProductMetricRepository; | ||
|
|
||
| @Value("#{jobParameters['startDate']}") | ||
| private String startDateParam; | ||
|
|
||
| @Value("#{jobParameters['endDate']}") | ||
| private String endDateParam; | ||
|
|
||
| @Override | ||
| public Map<String, ExecutionContext> partition(int gridSize) { | ||
| LocalDate startDate = LocalDate.parse(startDateParam); | ||
| LocalDate endDate = LocalDate.parse(endDateParam); | ||
| Long totalCount = dailyProductMetricRepository.countDistinctProductIdsBy(startDate, endDate); | ||
|
|
||
| if (totalCount == 0) { | ||
| return Collections.emptyMap(); | ||
| } | ||
|
|
||
| long targetSize = (totalCount / gridSize) + 1; | ||
| Map<String, ExecutionContext> partitions = new HashMap<>(); | ||
|
|
||
| for (int i = 0; i < gridSize; i++) { | ||
| ExecutionContext context = new ExecutionContext(); | ||
|
|
||
| context.putLong("partitionOffset", i * targetSize); | ||
| context.putLong("partitionLimit", targetSize); | ||
| context.putString("startDate", startDateParam); | ||
| context.putString("endDate", endDateParam); | ||
|
|
||
| partitions.put("partition" + i, context); | ||
| } | ||
|
|
||
| return partitions; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package com.loopers.application.batch.product; | ||
|
|
||
| import com.loopers.core.domain.product.repository.DailyProductMetricRepository; | ||
| import com.loopers.core.domain.product.vo.ProductMetricAggregation; | ||
| import lombok.NonNull; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.batch.item.ExecutionContext; | ||
| import org.springframework.batch.item.ItemStreamReader; | ||
| import org.springframework.lang.Nullable; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.Iterator; | ||
| import java.util.Objects; | ||
|
|
||
| @RequiredArgsConstructor | ||
| public class WeeklyProductMetricBatchReader implements ItemStreamReader<ProductMetricAggregation> { | ||
|
|
||
| private final DailyProductMetricRepository dailyProductMetricRepository; | ||
| private Iterator<ProductMetricAggregation> iterator; | ||
|
|
||
| @Override | ||
| public void open(@NonNull ExecutionContext executionContext) { | ||
| LocalDate startDate = LocalDate.parse(executionContext.getString("startDate")); | ||
| LocalDate endDate = LocalDate.parse(executionContext.getString("endDate")); | ||
| long partitionOffset = executionContext.getLong("partitionOffset"); | ||
| long partitionLimit = executionContext.getLong("partitionLimit"); | ||
|
|
||
| this.iterator = dailyProductMetricRepository.findAggregatedBy(startDate, endDate, partitionOffset, partitionLimit) | ||
| .iterator(); | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public ProductMetricAggregation read() { | ||
| if (Objects.isNull(iterator) || !iterator.hasNext()) { | ||
| return null; | ||
| } | ||
|
|
||
| return iterator.next(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.loopers.application.batch.product; | ||
|
|
||
| import com.loopers.core.domain.common.vo.YearMonthWeek; | ||
| import com.loopers.core.domain.product.WeeklyProductMetric; | ||
| import com.loopers.core.domain.product.repository.WeeklyProductMetricRepository; | ||
| import com.loopers.core.domain.product.vo.ProductMetricAggregation; | ||
| import lombok.NonNull; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.batch.core.configuration.annotation.StepScope; | ||
| import org.springframework.batch.item.Chunk; | ||
| import org.springframework.batch.item.ExecutionContext; | ||
| import org.springframework.batch.item.ItemStreamWriter; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.time.LocalDate; | ||
| import java.util.List; | ||
|
|
||
| @StepScope | ||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class WeeklyProductMetricBatchWriter implements ItemStreamWriter<ProductMetricAggregation> { | ||
|
|
||
| private final WeeklyProductMetricRepository repository; | ||
| private YearMonthWeek yearMonthWeek; | ||
|
|
||
| @Override | ||
| public void open(@NonNull ExecutionContext executionContext) { | ||
| LocalDate startDate = LocalDate.parse(executionContext.getString("startDate")); | ||
| this.yearMonthWeek = YearMonthWeek.from(startDate); | ||
| } | ||
|
Comment on lines
+19
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ฑ๊ธํค Bean์์ ๊ฐ๋ณ ์ํ๋ก ์ธํ ๋์์ฑ ๋ฌธ์
๐ ์ ์๋ ์์ import org.springframework.batch.item.Chunk;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStreamWriter;
+import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.stereotype.Component;
@Component
+@StepScope
@RequiredArgsConstructor
public class WeeklyProductMetricBatchWriter implements ItemStreamWriter<ProductMetricAggregation> {๐ค Prompt for AI Agents |
||
|
|
||
| @Override | ||
| public void write(@NonNull Chunk<? extends ProductMetricAggregation> chunk) { | ||
| List<WeeklyProductMetric> weeklyMetrics = chunk.getItems().stream() | ||
| .map(aggregation -> aggregation.to(yearMonthWeek)) | ||
| .toList(); | ||
|
|
||
| repository.bulkUpsert(weeklyMetrics); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐งฉ Analysis chain
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 1574
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 828
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 1574
๐ Script executed:
# Check CommerceBatchApplication main class cat -n apps/commerce-batch/src/main/java/com/loopers/CommerceBatchApplication.javaRepository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 599
๐ Script executed:
# Check one of the scheduler files to see if web exposure is used cat -n apps/commerce-batch/src/main/java/com/loopers/application/batch/product/MonthlyProductMetricScheduler.javaRepository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 2033
๐ Script executed:
Repository: Loopers-dev-lab/loopers-spring-java-template
Length of output: 788
spring-boot-starter-web ์ ๊ฑฐ ๋ฐ ์ค๋ณต๋ mysql-config ์์กด์ฑ ์ ๋ฆฌ
spring-boot-starter-web ์ ๊ฑฐ (Line 14): ๋ฐฐ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์น ์คํํฐ๊ฐ ์ฌ์ฉ๋์ง ์์ต๋๋ค. ๋ชจ๋ ์ ์ฒด์์ HTTP ์๋ํฌ์ธํธ๋ ์น ์ปจํธ๋กค๋ฌ๊ฐ ์์ผ๋ฉฐ, ๋ชจ๋ ๋ฐฐ์น ์์ ์
@Scheduled์ด๋ ธํ ์ด์ ์ ํตํด ์ค์ผ์ค๋ง์ผ๋ก ์คํ๋ฉ๋๋ค. ์ด ์์กด์ฑ์ ์ ๊ฑฐํ์ธ์.์ค๋ณต๋ mysql-config ์ ๊ฑฐ (Line 23):
mysql-config๋ ์ด๋ฏธimplementation์ค์ฝํ(Line 2)์ ์ ์ธ๋์ด ์์ผ๋ฏ๋ก,testImplementation์์์ ์ ์ธ์ ์ ๊ฑฐํ์ธ์. Gradle์์ ํ ์คํธ ์์กด์ฑ์ ์๋์ผ๋ก ๊ตฌํ ์์กด์ฑ์ ์์๋ฐ์ต๋๋ค.๐ค Prompt for AI Agents