Skip to content

[UPLUS-121] feat : Cloud Function API 로직 수정 및 DTO 수정#38

Merged
arlen02-01 merged 12 commits into
developfrom
feat/UPLUS-121
Jan 26, 2026
Merged

[UPLUS-121] feat : Cloud Function API 로직 수정 및 DTO 수정#38
arlen02-01 merged 12 commits into
developfrom
feat/UPLUS-121

Conversation

@paul0755
Copy link
Copy Markdown
Contributor

🎫 지라 티켓

UPLUS-121


✅ 작업 사항

  • Batch_Schedule 테이블 삭제
  • Cloud Function을 통해 cron식 조회 및 수정
    -크론식은 Cloud Scheduler 기준 UNIX 5필드 사용
  • Spring Cron 계산 시에는 초 필드를 보정(6필드)
  • cron-utils는 CronType.UNIX로 설명 문자열 생성
  • RestTemplate은 **HttpClient 5.x 방식(ConnectionConfig 분리)**으로 수정
  • 배치 대시보드용 DTO는 Builder 패턴으로 생성
  • 테스트에서는 LocalDateTime.now() 대신 고정 시간 사용
  • 실패 로그 조회는 FAILED + 특정 Job(invoiceJob, invoiceItemJob) 조건으로 제한

📋 체크리스트

  • 코드가 정상적으로 빌드됩니다.
  • 관련 테스트 코드를 작성했습니다.
  • 기존 테스트가 모두 통과합니다.
  • 코드 스타일(Spotless, Checkstyle)을 준수합니다.

⌨ 기타

@paul0755 paul0755 self-assigned this Jan 26, 2026
@github-actions github-actions Bot requested a review from starboxxxx January 26, 2026 07:27
@github-actions github-actions Bot added size/L PR size: L feat 새로운 기능 labels Jan 26, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @paul0755, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 배치 스케줄링 시스템을 데이터베이스 기반에서 Cloud Function API 기반으로 전환하고, 배치 대시보드에 스케줄 정보를 통합하여 가시성을 높이는 것을 목표로 합니다. 또한, RestTemplateHttpClient 설정을 최적화하고, cron-utils 라이브러리를 도입하여 크론식의 가독성을 향상시켰습니다. 불필요한 로깅 및 OTLP 설정을 정리하여 시스템 효율성도 개선했습니다.

Highlights

  • 배치 스케줄 관리 시스템 전환: 기존 데이터베이스 기반의 Batch_Schedule 테이블 및 관련 서비스/리포지토리를 제거하고, Cloud Function API를 통해 배치 스케줄을 조회하고 수정하는 방식으로 전환했습니다.
  • 크론식 처리 및 가시성 개선: cron-utils 라이브러리를 추가하여 Cloud Scheduler 기준의 UNIX 5필드 크론식을 Spring Cron (6필드)으로 보정하고, 크론식 설명을 생성하여 배치 대시보드에 cronExpression, cronDescription, nextFireTime 필드를 추가했습니다.
  • RestTemplate 설정 최적화: RestTemplateHttpClient 5.x 방식으로 수정하고, ConnectionConfigRequestConfig를 분리하여 연결 및 요청 타임아웃, 커넥션 풀(setMaxConnTotal, setMaxConnPerRoute) 설정을 세분화했습니다.
  • 테스트 및 로깅 개선: 테스트 코드에서 LocalDateTime.now() 대신 고정 시간을 사용하여 테스트의 일관성을 높였으며, 실패 로그 조회 시 특정 Job(invoiceJob, invoiceItemJob) 조건으로 제한하여 불필요한 로그를 줄였습니다.
  • OTLP 설정 비활성화: application.yml에서 OTLP 메트릭스, 로그, 트레이스 익스포터를 비활성화하여 불필요한 리소스 사용을 줄였습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이번 PR은 배치 스케줄링 로직을 Cloud Function을 사용하도록 리팩터링하고, 관련 DTO와 API 로직을 수정한 내용이네요. 전반적으로 PR의 목적에 맞게 잘 구현되었지만, 코드의 유지보수성, 테스트 용이성, 안정성을 높일 수 있는 몇 가지 개선점을 발견했습니다. 특히, 하드코딩된 문자열 제거, 테스트 코드의 완전성 확보, 외부 API 호출 시 URL 생성 방식 개선 등에 대한 피드백을 포함했습니다. 해당 내용들을 반영하면 더 견고한 코드가 될 것 같습니다.

Comment on lines 37 to 38
@DisplayName("[조회] 성공 - 배치 대시보드 조회")
void getDashboard_success() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

이 테스트는 현재 실패할 가능성이 높습니다. InvoiceServiceBatchScheduleClient에 의존하지만, 이 테스트 클래스에서는 BatchScheduleClient를 Mock으로 만들거나 주입하지 않았습니다. @InjectMocksnull인 필드를 주입하지 않으므로 invoiceService.batchScheduleClientnull이 되어 getDashboard() 메서드 실행 중 NullPointerException이 발생합니다.

테스트가 정상적으로 동작하려면 BatchScheduleClient를 Mock으로 추가하고, given()을 사용하여 해당 Mock의 동작을 정의해야 합니다.

@ExtendWith(MockitoExtension.class)
class InvoiceServiceTest {
    // ...
    @Mock private com.project.core.util.BatchScheduleClient batchScheduleClient;
    @InjectMocks private InvoiceService invoiceService;

    @Test
    void getDashboard_success() {
        // given
        // ...
        given(batchScheduleClient.getSchedule(anyString())).willReturn(Optional.empty()); // 또는 mock 응답을 설정
        
        // when
        // ...
    }
}

Comment on lines +18 to +20
public boolean isEnabled() {
return "ENABLED".equalsIgnoreCase(state);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

isEnabled() 메서드에서 "ENABLED" 문자열을 직접 사용하고 있습니다. 이런 매직 스트링은 오타를 유발하거나 일관성을 해칠 수 있습니다. 상태 값을 나타내는 상수를 선언하여 사용하는 것이 좋습니다.

public class BatchScheduleResponse {
    private static final String ENABLED_STATE = "ENABLED";
    // ...
    public boolean isEnabled() {
        return ENABLED_STATE.equalsIgnoreCase(state);
    }
}

JOIN BATCH_JOB_EXECUTION je
ON ji.JOB_INSTANCE_ID = je.JOB_INSTANCE_ID
WHERE je.STATUS = 'FAILED'
AND ji.JOB_NAME IN ('invoiceJob', 'invoiceItemJob')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

SQL 쿼리 내에 'invoiceJob', 'invoiceItemJob'과 같은 잡 이름이 하드코딩되어 있습니다. 이는 유지보수를 어렵게 만들 수 있습니다. BatchJobType enum을 활용하여 잡 이름을 동적으로 구성하거나, PreparedStatement의 파라미터로 전달하는 것이 좋습니다. 이렇게 하면 BatchJobType enum이 변경될 때 쿼리도 자동으로 반영됩니다.

예를 들어, 다음과 같이 ? 플레이스홀더를 사용하고 jdbcTemplate.query 메서드에 인자를 전달할 수 있습니다.

// 쿼리 수정
String sql =
    """
        ...
          AND ji.JOB_NAME IN (?, ?)
        ...
        LIMIT ?
    """;

// jdbcTemplate 호출 수정
jdbcTemplate.query(sql, rowMapper, BatchJobType.INVOICE_JOB.getJobName(), BatchJobType.INVOICE_ITEM_JOB.getJobName(), limit);


CronExpression springCron = CronExpression.parse(springCronStr);

ZonedDateTime nextFire = springCron.next(ZonedDateTime.now(zoneId));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

ZonedDateTime.now(zoneId)를 직접 호출하면 현재 시간에 의존하게 되어 단위 테스트를 작성하기 어렵습니다. 테스트 용이성을 높이기 위해 java.time.Clock을 주입받아 사용하거나, getDashboard 메서드에서 현재 시간을 변수로 받아 하위 메서드로 전달하는 방식을 고려해 보세요.

Clock을 사용하는 예시입니다:

// In a @Configuration class
@Bean
public Clock clock() {
    return Clock.systemDefaultZone();
}

// In InvoiceService
@RequiredArgsConstructor
public class InvoiceService {
    private final Clock clock;
    // ...
    private BatchSummaryDto applySchedule(...) {
        // ...
        ZonedDateTime nextFire = springCron.next(ZonedDateTime.now(clock).withZoneSameInstant(zoneId));
        // ...
    }
}

테스트에서는 Clock.fixed(...)를 주입하여 시간을 고정할 수 있습니다.

Comment on lines +101 to +105
CronParser parser =
new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.UNIX));
Cron cron = parser.parse(cronStr);

String cronDescription = CronDescriptor.instance(Locale.KOREAN).describe(cron);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

applySchedule 메서드가 호출될 때마다 CronParserCronDescriptor 인스턴스가 새로 생성됩니다. 이 객체들은 스레드에 안전하며 재사용이 가능하므로, private static final 필드로 선언하여 애플리케이션 시작 시 한 번만 생성하도록 하는 것이 성능에 더 효율적입니다.

public class InvoiceService {
    private static final CronParser UNIX_CRON_PARSER = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.UNIX));
    private static final CronDescriptor CRON_DESCRIPTOR_KOREAN = CronDescriptor.instance(Locale.KOREAN);

    // ...

    private BatchSummaryDto applySchedule(...) {
        // ...
        try {
            // ...
            Cron cron = UNIX_CRON_PARSER.parse(cronStr);
            String cronDescription = CRON_DESCRIPTOR_KOREAN.describe(cron);
            // ...
        } catch (Exception e) {
            // ...
        }
    }
}

public Optional<BatchScheduleResponse> getSchedule(String jobName) {

String title = BatchJobType.getTitleByJobName(jobName);
String url = properties.getBaseUrl() + "/schedule?job=" + title;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

URL을 문자열 연결로 생성하고 있습니다. 쿼리 파라미터에 특수 문자가 포함될 경우 문제가 발생할 수 있으므로, UriComponentsBuilder를 사용하여 URL을 안전하게 생성하고 파라미터를 인코딩하는 것이 좋습니다.

        String url = org.springframework.web.util.UriComponentsBuilder.fromHttpUrl(properties.getBaseUrl())
                .path("/schedule")
                .queryParam("job", title)
                .toUriString();

@github-actions
Copy link
Copy Markdown

SonarQube Quality Summary (Community)

Quality Gate FAILED

Branch: feat/UPLUS-121
Compared to: default branch

Issues

  • 🐞 Bugs: 0
  • 🔐 Vulnerabilities: 2
  • 📎 Code Smells: 50

Measures

  • Coverage: 0%
  • Duplication: 0%

🔗 Dashboard: https://sonarqube.swthewhite.store/dashboard?id=api-core&branch=feat/UPLUS-121

Generated automatically by GitHub Actions.

@github-actions
Copy link
Copy Markdown

SonarQube Quality Summary (Community)

Quality Gate PASSED

Branch: feat/UPLUS-121
Compared to: default branch

Issues

  • 🐞 Bugs: 0
  • 🔐 Vulnerabilities: 2
  • 📎 Code Smells: 50

Measures

  • Coverage: 0%
  • Duplication: 0%

🔗 Dashboard: https://sonarqube.swthewhite.store/dashboard?id=api-core&branch=feat/UPLUS-121

Generated automatically by GitHub Actions.

Copy link
Copy Markdown
Contributor

@arlen02-01 arlen02-01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인 했습니다

@arlen02-01 arlen02-01 merged commit 710c623 into develop Jan 26, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새로운 기능 size/L PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants