Feat: common 초기 구성 - #7
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughJava 17 기반의 공통 라이브러리 모듈을 추가합니다. Spring Boot 자동설정 진입점, JPA 감사 설정, 표준 API 응답 레코드, 도메인 예외/전역 예외처리, BaseEntity(감사 필드·소프트 삭제), Gradle 빌드·래퍼 및 Git 설정 파일들이 포함됩니다. Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant Controller
participant Service
participant GlobalExceptionHandler
participant ApiResponse
Client->>Controller: HTTP Request
Controller->>Service: 비즈니스 호출
Service-->>Controller: 정상 응답 / 예외 발생
Controller-->>GlobalExceptionHandler: 예외 전달
GlobalExceptionHandler->>ApiResponse: 실패 응답 조립 (code, message)
GlobalExceptionHandler-->>Client: HTTP status + ApiResponse
sequenceDiagram
participant Service
participant Repository
participant Entity as BaseEntity
participant Auditing as AuditingEntityListener
participant Auditor as AuditorAware<UUID>
participant Database
Service->>Repository: save(entity)
Repository->>Auditing: 트리거 `@CreatedDate` / `@LastModifiedDate`
Auditing->>Auditor: auditor 요청
Auditor-->>Auditing: UUID 반환
Auditing-->>Entity: createdBy/updatedBy, timestamps 설정
Repository->>Database: persist
Database-->>Service: 저장 결과
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (6)
src/main/java/com/michelet/common/exception/ErrorCode.java (1)
5-9: 인터페이스 반환값 계약(Null/Blank 불가)을 명시해 주세요.
ErrorCode구현체가null/빈 문자열을 반환하면 예외 응답 품질이 떨어집니다. 최소한 Javadoc에 값 제약을 명시하거나, 구현 enum 생성자에서 검증하는 규약을 함께 제시해두는 편이 유지보수에 유리합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/michelet/common/exception/ErrorCode.java` around lines 5 - 9, Declare non-null/non-blank contracts on the ErrorCode interface by adding Javadoc to getCode(), getMessage(), and getHttpStatus() stating that getCode/getMessage must be non-null and non-blank and getHttpStatus must be a valid HTTP status; additionally, enforce these contracts in all implementations (e.g., in any implementing enum constructors) by validating inputs (throw IllegalArgumentException/NullPointerException for null/blank code/message and invalid http status) so invalid ErrorCode instances cannot be created (refer to ErrorCode, getCode, getMessage, getHttpStatus and the enum constructors that implement ErrorCode).build.gradle (2)
36-39: 공통 라이브러리에서 starter를api로 노출하면 소비 서비스의 불필요한 자동설정 강제 및 과결합 위험이 있습니다.
spring-boot-starter-web,spring-boot-starter-data-jpa등을api의존성으로 제공하면 모든 소비 서비스에 관련 자동설정과 전이 의존성이 강제되어 충돌, 버전 관리 복잡도 증가, 불필요한 라이브러리 로딩으로 이어질 수 있습니다. 공통 모듈은 가능한 한spring-web,spring-data-jpa,spring-context등 더 좁은 범위의 프레임워크 모듈을 의존성으로 사용하여 운영 안정성과 유연성을 높이는 것이 권장됩니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@build.gradle` around lines 36 - 39, 현재 공통 모듈에서 선언된 'org.springframework.boot:spring-boot-starter-web', 'org.springframework.boot:spring-boot-starter-data-jpa', 'org.springframework.boot:spring-boot-starter-validation'를 api로 노출하면 소비 서비스에 스타터 자동설정과 전이 의존성이 강제되므로, 이들을 api로 그대로 노출하지 말고 필요하면 implementation(또는 compileOnly)으로 변경하여 전파를 막고, 공통 모듈의 공개 API로 반드시 필요한 경우에는 좁은 범위의 모듈(예: org.springframework:spring-web, org.springframework:spring-context, org.springframework.data:spring-data-jpa 등)만 api로 명시하도록 build.gradle에서 해당 의존성 선언을 수정하세요.
41-43: Lombok은api대신compileOnly로 변경하세요.Lombok은 컴파일 타임에만 필요한 의존성으로, 런타임에는 불필요합니다.
api로 노출하면 라이브러리 사용자도 Lombok에 의존하게 되어 불필요한 전이 의존성을 추가합니다. 공식 Gradle 및 Lombok 문서에서compileOnly와annotationProcessor조합을 권장합니다.제안 패치
- api 'org.projectlombok:lombok' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@build.gradle` around lines 41 - 43, 현재 build.gradle에서 lombok을 api로 선언한 부분(api 'org.projectlombok:lombok')을 런타임 전이 의존성을 만들지 않도록 compileOnly로 변경하고(annotationProcessor 'org.projectlombok:lombok'은 그대로 유지), 즉 api 'org.projectlombok:lombok'을 compileOnly 'org.projectlombok:lombok'로 바꿔 컴파일 타임 전용 의존성으로 설정하세요.src/main/java/com/michelet/common/entity/BaseEntity.java (1)
26-27:columnDefinition = "uuid"하드코딩은 공통 모듈의 DB 이식성을 낮춥니다.Spring Boot 3.5.14의 Hibernate 6.6+는 UUID 필드를 DB 방언에 맞게 자동으로 매핑합니다. 명시적
columnDefinition = "uuid"는 이 자동 매핑 로직을 무시하고 PostgreSQL 특화 SQL을 강제하므로 다른 DB 지원 시 스키마 불일치 문제가 발생할 수 있습니다.공통 라이브러리라면 방언에 의존하지 않는 것이 안전합니다.
columnDefinition을 제거하면 Hibernate가 대상 DB에 맞게 적절한 타입을 선택합니다.제안 패치
- `@Column`(name = "created_by", nullable = false, updatable = false, columnDefinition = "uuid") + `@Column`(name = "created_by", nullable = false, updatable = false) private UUID createdBy; ... - `@Column`(name = "updated_by", columnDefinition = "uuid") + `@Column`(name = "updated_by") private UUID updatedBy; ... - `@Column`(name = "deleted_by", columnDefinition = "uuid") + `@Column`(name = "deleted_by") private UUID deletedBy;적용 대상: 34-35, 40-41
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/michelet/common/entity/BaseEntity.java` around lines 26 - 27, The `@Column` annotations on the UUID fields createdBy and updatedBy currently hardcode columnDefinition = "uuid", which forces a PostgreSQL-specific type and breaks DB portability; remove the columnDefinition attribute from the `@Column` declarations for the fields createdBy (UUID createdBy) and updatedBy (UUID updatedBy) so Hibernate can auto-map UUID to the dialect-appropriate SQL type while keeping the existing nullable/updatable settings.src/main/java/com/michelet/common/exception/GlobalExceptionHandler.java (2)
54-56: BadRequest 핸들러는 예외별 메서드 분리를 고려해도 좋겠습니다.Line 54-56은 대상 예외를 2개로 제한해두고 파라미터를
Exception으로 받고 있어, 이후 예외별 메시지/로그를 세분화할 때 확장성이 떨어질 수 있습니다. 필요 시 핸들러를 분리해 의도를 더 명확히 할 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/michelet/common/exception/GlobalExceptionHandler.java` around lines 54 - 56, The current handleBadRequest(Exception e) groups MissingRequestHeaderException and HttpMessageNotReadableException but takes a generic Exception which limits per-exception logging/response customization; split into two specific `@ExceptionHandler` methods (e.g., handleMissingRequestHeader(MissingRequestHeaderException e) and handleHttpMessageNotReadable(HttpMessageNotReadableException e)), update annotations to target each exception class, tailor log messages and ApiResponse bodies per exception (include header name/missing info for MissingRequestHeaderException and parse error details for HttpMessageNotReadableException), and remove/replace the generic handleBadRequest signature so each handler returns ResponseEntity<ApiResponse<Void>> with appropriate status and message.
33-39: 검증 오류를 첫 건만 반환하는 정책은 재고를 권장합니다.Line 33-36은 첫 번째 field error만 내려주기 때문에, 클라이언트가 여러 입력 오류를 한 번에 수정하기 어렵습니다. 에러 메시지를 누적해서 반환하는 형태를 고려해 주세요.
예시 변경안
- String message = e.getBindingResult().getFieldErrors().stream() - .map(fe -> fe.getField() + ": " + fe.getDefaultMessage()) - .findFirst() - .orElse("유효성 검증 실패"); + String message = e.getBindingResult().getFieldErrors().stream() + .map(fe -> fe.getField() + ": " + fe.getDefaultMessage()) + .reduce((a, b) -> a + ", " + b) + .orElse("유효성 검증 실패");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/michelet/common/exception/GlobalExceptionHandler.java` around lines 33 - 39, GlobalExceptionHandler currently only returns the first validation error; change the handling in the method that processes MethodArgumentNotValidException (the code computing the local variable message and building the ResponseEntity) to aggregate all field errors instead of taking findFirst(). Collect all e.getBindingResult().getFieldErrors(), map each FieldError to "field: message" (or produce a List<String>), join or wrap them as an array, log the aggregated result (log.warn("[Validation] {}", aggregated)) and return it in ApiResponse.error (e.g., pass the joined string or the list) so clients receive all validation failures at once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@README.md`:
- Around line 137-138: The package tree in the README under the response package
uses two sibling entries both prefixed with "└──", which breaks the visual tree;
update the first entry (the line that lists ApiResponse.java) to use "├──"
instead of "└──" so the tree shows a branch then the final leaf
(SuccessCode.java) remains "└──".
In `@src/main/java/com/michelet/common/config/JpaAuditingConfig.java`:
- Around line 13-14: The config must explicitly require an AuditorAware bean to
fail fast; update `@EnableJpaAuditing` to declare an auditorAwareRef (e.g.,
`@EnableJpaAuditing`(auditorAwareRef = "auditorAware")) in JpaAuditingConfig and
add a bean named "auditorAware" (public AuditorAware<String> auditorAware()) in
the same config that either returns the real auditor implementation or
throws/raises an exception when not configured so the application fails at
bootstrap instead of at entity persist; reference `@EnableJpaAuditing`,
auditorAwareRef, and BaseEntity.createdBy when making the change.
In `@src/main/java/com/michelet/common/response/ApiResponse.java`:
- Around line 37-38: The ApiResponse.error factory allows null code/message,
unlike success(SuccessCode, T); update ApiResponse.error(String, String) to
perform null-defense by normalizing null code to a default error code (e.g.,
ErrorCode.UNKNOWN or a constant like "UNKNOWN_ERROR") and null message to a safe
default (e.g., "Unknown error") before constructing the ApiResponse; locate the
static method ApiResponse.error and apply the same null-check/normalization
pattern used in success(SuccessCode, T) so the constructor always receives
non-null code and message.
In
`@src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`:
- Line 1: CommonAutoConfiguration currently imports JpaAuditingConfig
unconditionally which forces `@EnableJpaAuditing` for every service; modify
JpaAuditingConfig (the class annotated with `@EnableJpaAuditing`) to be
conditionally loaded so JPA auditing is enabled only when appropriate (for
example add `@ConditionalOnClass`({javax.persistence.Entity.class,
org.springframework.data.jpa.repository.JpaRepository.class}) and/or
`@ConditionalOnBean`(org.springframework.data.domain.AuditorAware.class) and/or a
`@ConditionalOnProperty` like common.jpa.auditing.enabled) and remove the
unconditional import if needed so services not using JPA or not providing an
AuditorAware<UUID> bean will not fail on startup.
---
Nitpick comments:
In `@build.gradle`:
- Around line 36-39: 현재 공통 모듈에서 선언된
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.boot:spring-boot-starter-data-jpa',
'org.springframework.boot:spring-boot-starter-validation'를 api로 노출하면 소비 서비스에 스타터
자동설정과 전이 의존성이 강제되므로, 이들을 api로 그대로 노출하지 말고 필요하면 implementation(또는 compileOnly)으로
변경하여 전파를 막고, 공통 모듈의 공개 API로 반드시 필요한 경우에는 좁은 범위의 모듈(예:
org.springframework:spring-web, org.springframework:spring-context,
org.springframework.data:spring-data-jpa 등)만 api로 명시하도록 build.gradle에서 해당 의존성
선언을 수정하세요.
- Around line 41-43: 현재 build.gradle에서 lombok을 api로 선언한 부분(api
'org.projectlombok:lombok')을 런타임 전이 의존성을 만들지 않도록 compileOnly로
변경하고(annotationProcessor 'org.projectlombok:lombok'은 그대로 유지), 즉 api
'org.projectlombok:lombok'을 compileOnly 'org.projectlombok:lombok'로 바꿔 컴파일 타임 전용
의존성으로 설정하세요.
In `@src/main/java/com/michelet/common/entity/BaseEntity.java`:
- Around line 26-27: The `@Column` annotations on the UUID fields createdBy and
updatedBy currently hardcode columnDefinition = "uuid", which forces a
PostgreSQL-specific type and breaks DB portability; remove the columnDefinition
attribute from the `@Column` declarations for the fields createdBy (UUID
createdBy) and updatedBy (UUID updatedBy) so Hibernate can auto-map UUID to the
dialect-appropriate SQL type while keeping the existing nullable/updatable
settings.
In `@src/main/java/com/michelet/common/exception/ErrorCode.java`:
- Around line 5-9: Declare non-null/non-blank contracts on the ErrorCode
interface by adding Javadoc to getCode(), getMessage(), and getHttpStatus()
stating that getCode/getMessage must be non-null and non-blank and getHttpStatus
must be a valid HTTP status; additionally, enforce these contracts in all
implementations (e.g., in any implementing enum constructors) by validating
inputs (throw IllegalArgumentException/NullPointerException for null/blank
code/message and invalid http status) so invalid ErrorCode instances cannot be
created (refer to ErrorCode, getCode, getMessage, getHttpStatus and the enum
constructors that implement ErrorCode).
In `@src/main/java/com/michelet/common/exception/GlobalExceptionHandler.java`:
- Around line 54-56: The current handleBadRequest(Exception e) groups
MissingRequestHeaderException and HttpMessageNotReadableException but takes a
generic Exception which limits per-exception logging/response customization;
split into two specific `@ExceptionHandler` methods (e.g.,
handleMissingRequestHeader(MissingRequestHeaderException e) and
handleHttpMessageNotReadable(HttpMessageNotReadableException e)), update
annotations to target each exception class, tailor log messages and ApiResponse
bodies per exception (include header name/missing info for
MissingRequestHeaderException and parse error details for
HttpMessageNotReadableException), and remove/replace the generic
handleBadRequest signature so each handler returns
ResponseEntity<ApiResponse<Void>> with appropriate status and message.
- Around line 33-39: GlobalExceptionHandler currently only returns the first
validation error; change the handling in the method that processes
MethodArgumentNotValidException (the code computing the local variable message
and building the ResponseEntity) to aggregate all field errors instead of taking
findFirst(). Collect all e.getBindingResult().getFieldErrors(), map each
FieldError to "field: message" (or produce a List<String>), join or wrap them as
an array, log the aggregated result (log.warn("[Validation] {}", aggregated))
and return it in ApiResponse.error (e.g., pass the joined string or the list) so
clients receive all validation failures at once.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 57da394b-814f-48f1-b033-037975b89e05
⛔ Files ignored due to path filters (1)
gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jar
📒 Files selected for processing (17)
.gitattributes.gitignoreREADME.mdbuild.gradlegradle/wrapper/gradle-wrapper.propertiesgradlewgradlew.batsettings.gradlesrc/main/java/com/michelet/common/config/CommonAutoConfiguration.javasrc/main/java/com/michelet/common/config/JpaAuditingConfig.javasrc/main/java/com/michelet/common/entity/BaseEntity.javasrc/main/java/com/michelet/common/exception/BusinessException.javasrc/main/java/com/michelet/common/exception/ErrorCode.javasrc/main/java/com/michelet/common/exception/GlobalExceptionHandler.javasrc/main/java/com/michelet/common/response/ApiResponse.javasrc/main/java/com/michelet/common/response/SuccessCode.javasrc/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
| @Configuration | ||
| @EnableJpaAuditing |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Spring Data JPA, if @EnableJpaAuditingis enabled but no AuditorAware bean is registered, how are@createdby fields handled and when does failure occur if DB column is NOT NULL?
💡 Result:
When @EnableJpaAuditing is enabled but no AuditorAware bean is registered, @CreatedBy fields are set to null for new entities. The AuditingHandler has no AuditorAware, so getAuditor returns Auditor.none, an empty Optional. In touchAuditor(isNew=true), since auditor.isPresent is false, setCreatedBy is not called, leaving the field null. Failure occurs at database insert time if the column is NOT NULL, causing a constraint violation exception (e.g., "not-null property references a null or transient value").
Citations:
- 1: https://docs.spring.io/spring-data/jpa/reference/auditing.html
- 2: https://github.com/spring-projects/spring-data-commons/blob/master/src/main/java/org/springframework/data/auditing/AuditingHandler.java
- 3: https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/auditing/AuditingHandlerSupport.html
🏁 Script executed:
# Search for BaseEntity class to check created_by column definition
find . -type f -name "*.java" | xargs grep -l "class BaseEntity" | head -5Repository: Miche-Let/common
Length of output: 119
🏁 Script executed:
# Search for `@CreatedBy` annotations to see the actual usage
rg "@CreatedBy" -A 2 -B 2 --type javaRepository: Miche-Let/common
Length of output: 523
🏁 Script executed:
# Search for AuditorAware implementations in the codebase
rg "implements AuditorAware|class.*AuditorAware" --type javaRepository: Miche-Let/common
Length of output: 42
🏁 Script executed:
# Check for other `@EnableJpaAuditing` usages
rg "@EnableJpaAuditing" --type java -A 1 -B 1Repository: Miche-Let/common
Length of output: 612
🏁 Script executed:
# Search for any bean configuration that might provide AuditorAware or auditorAware bean
rg "auditorAware|@Bean" --type java -B 2 -A 3 | head -50Repository: Miche-Let/common
Length of output: 42
🏁 Script executed:
# Check the structure of the repository to understand module organization
ls -laRepository: Miche-Let/common
Length of output: 904
AuditorAware 계약을 명시적으로 강제하여 부팅 시점에 조기 실패시키세요.
@EnableJpaAuditing은 활성화되었지만 auditorAwareRef 없이는 AuditorAware 빈 부재를 런타임까지 감지하지 않습니다. BaseEntity의 createdBy는 nullable = false이므로, AuditorAware를 구현하지 않은 서비스가 이 모듈을 사용할 때 엔티티 저장 시점에 제약 조건 위반으로 실패합니다. 공통 모듈이므로 계약을 명시적으로 강제하여 부팅 시점에 빠르게 실패하는 구성이 안전합니다.
제안 패치
`@Configuration`
-@EnableJpaAuditing
+@EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JpaAuditingConfig {
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/michelet/common/config/JpaAuditingConfig.java` around lines
13 - 14, The config must explicitly require an AuditorAware bean to fail fast;
update `@EnableJpaAuditing` to declare an auditorAwareRef (e.g.,
`@EnableJpaAuditing`(auditorAwareRef = "auditorAware")) in JpaAuditingConfig and
add a bean named "auditorAware" (public AuditorAware<String> auditorAware()) in
the same config that either returns the real auditor implementation or
throws/raises an exception when not configured so the application fails at
bootstrap instead of at entity persist; reference `@EnableJpaAuditing`,
auditorAwareRef, and BaseEntity.createdBy when making the change.
| public static <T> ApiResponse<T> error(String code, String message) { | ||
| return new ApiResponse<>(false, null, code, message, LocalDateTime.now(), currentTraceId()); |
There was a problem hiding this comment.
오류 응답 팩토리도 null 방어를 맞춰주세요.
success(SuccessCode, T)는 null 방어가 있는데 error(String, String)에는 없어 code/message가 null로 내려갈 수 있습니다.
제안 패치
public static <T> ApiResponse<T> error(String code, String message) {
- return new ApiResponse<>(false, null, code, message, LocalDateTime.now(), currentTraceId());
+ String safeCode = java.util.Objects.requireNonNull(code, "code must not be null");
+ String safeMessage = java.util.Objects.requireNonNull(message, "message must not be null");
+ return new ApiResponse<>(false, null, safeCode, safeMessage, LocalDateTime.now(), currentTraceId());
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public static <T> ApiResponse<T> error(String code, String message) { | |
| return new ApiResponse<>(false, null, code, message, LocalDateTime.now(), currentTraceId()); | |
| public static <T> ApiResponse<T> error(String code, String message) { | |
| String safeCode = java.util.Objects.requireNonNull(code, "code must not be null"); | |
| String safeMessage = java.util.Objects.requireNonNull(message, "message must not be null"); | |
| return new ApiResponse<>(false, null, safeCode, safeMessage, LocalDateTime.now(), currentTraceId()); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/michelet/common/response/ApiResponse.java` around lines 37
- 38, The ApiResponse.error factory allows null code/message, unlike
success(SuccessCode, T); update ApiResponse.error(String, String) to perform
null-defense by normalizing null code to a default error code (e.g.,
ErrorCode.UNKNOWN or a constant like "UNKNOWN_ERROR") and null message to a safe
default (e.g., "Unknown error") before constructing the ApiResponse; locate the
static method ApiResponse.error and apply the same null-check/normalization
pattern used in success(SuccessCode, T) so the constructor always receives
non-null code and message.
| @@ -0,0 +1 @@ | |||
| com.michelet.common.config.CommonAutoConfiguration | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 목적: JPA Auditing 설정이 무조건 로드되는지 확인 (read-only)
set -e
echo "== AutoConfiguration imports 확인 =="
rg -n "CommonAutoConfiguration" src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
echo
echo "== CommonAutoConfiguration / JpaAuditingConfig 조건부 여부 확인 =="
rg -n "@Import|@EnableJpaAuditing|@ConditionalOnClass|@ConditionalOnBean|@ConditionalOnProperty" src/main/java/com/michelet/common/config
echo
echo "== 참고: 전역 예외처리 import 확인 =="
rg -n "GlobalExceptionHandler" src/main/java/com/michelet/common/config src/main/java/com/michelet/common/exceptionRepository: Miche-Let/common
Length of output: 1027
🏁 Script executed:
cat -n src/main/java/com/michelet/common/config/CommonAutoConfiguration.javaRepository: Miche-Let/common
Length of output: 587
🏁 Script executed:
cat -n src/main/java/com/michelet/common/config/JpaAuditingConfig.javaRepository: Miche-Let/common
Length of output: 603
JPA Auditing이 무조건 활성화되어 JPA를 사용하지 않는 서비스에서 부팅 실패 가능성이 있습니다.
CommonAutoConfiguration은 조건 없이 JpaAuditingConfig를 @Import하며, JpaAuditingConfig의 @EnableJpaAuditing도 조건부 애너테이션이 없습니다. 따라서 common 모듈을 의존하는 모든 서비스에서 JPA Auditing이 강제 활성화됩니다. JPA를 사용하지 않는 서비스거나 AuditorAware<UUID> Bean을 등록하지 않은 경우 부팅 실패나 예상치 못한 자동설정 오류가 발생할 수 있습니다.
JpaAuditingConfig를 @ConditionalOnClass, @ConditionalOnBean, 또는 @ConditionalOnProperty로 보호하여 필요한 경우에만 활성화되도록 분리하는 것이 안전합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`
at line 1, CommonAutoConfiguration currently imports JpaAuditingConfig
unconditionally which forces `@EnableJpaAuditing` for every service; modify
JpaAuditingConfig (the class annotated with `@EnableJpaAuditing`) to be
conditionally loaded so JPA auditing is enabled only when appropriate (for
example add `@ConditionalOnClass`({javax.persistence.Entity.class,
org.springframework.data.jpa.repository.JpaRepository.class}) and/or
`@ConditionalOnBean`(org.springframework.data.domain.AuditorAware.class) and/or a
`@ConditionalOnProperty` like common.jpa.auditing.enabled) and remove the
unconditional import if needed so services not using JPA or not providing an
AuditorAware<UUID> bean will not fail on startup.
ji-circle
left a comment
There was a problem hiding this comment.
확인했습니다!
AuditorAware 자동화 관련해서, JpaAuditingConfig에 @ConditionalOnMissingBean(AuditorAware.class)를 사용하면 서비스에서 직접 등록하지 않아도 common의 기본 로직이 작동하게 설정할 수 있다고 하네요...! 제가 제대로 이해한 건지는 잘 모르겠긴 합니다ㅠㅠ
3c4f0a8
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/main/java/com/michelet/common/response/ApiResponse.java (1)
23-35: 응답 메타데이터 생성 로직을 한 곳으로 모아두면 유지보수가 쉬워집니다.
LocalDateTime.now()와currentTraceId()호출이 여러 팩토리에 반복됩니다. 작은 private 헬퍼로 합치면 추후 필드 변경 시 수정 지점이 줄어듭니다.예시 리팩터링
public record ApiResponse<T>( boolean success, T data, String code, String message, LocalDateTime timestamp, String traceId ) { + private static LocalDateTime now() { + return LocalDateTime.now(); + } + + private static String traceId() { + return currentTraceId(); + } public static <T> ApiResponse<T> ok(T data) { - return new ApiResponse<>(true, data, null, null, LocalDateTime.now(), currentTraceId()); + return new ApiResponse<>(true, data, null, null, now(), traceId()); } public static <T> ApiResponse<T> ok() { - return new ApiResponse<>(true, null, null, null, LocalDateTime.now(), currentTraceId()); + return new ApiResponse<>(true, null, null, null, now(), traceId()); } public static <T> ApiResponse<T> ok(SuccessCode code, T data) { SuccessCode successCode = java.util.Objects.requireNonNull(code, "successCode must not be null"); return new ApiResponse<>(true, data, successCode.getCode(), successCode.getMessage(), - LocalDateTime.now(), currentTraceId()); + now(), traceId()); } public static <T> ApiResponse<T> fail(String code, String message) { String safeCode = java.util.Objects.requireNonNull(code, "code must not be null"); String safeMessage = java.util.Objects.requireNonNull(message, "message must not be null"); - return new ApiResponse<>(false, null, safeCode, safeMessage, LocalDateTime.now(), currentTraceId()); + return new ApiResponse<>(false, null, safeCode, safeMessage, now(), traceId()); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/michelet/common/response/ApiResponse.java` around lines 23 - 35, Consolidate repeated metadata construction in the ApiResponse factory methods by adding a single private helper that returns the shared metadata (timestamp and traceId) and use it from all ok(...) overloads; specifically, create a private static helper method (e.g., buildMeta() or createMetadata()) that calls LocalDateTime.now() and currentTraceId(), then update the public static ok(T), ok(), and ok(SuccessCode, T) methods to call that helper and pass its values into the ApiResponse constructor, so LocalDateTime.now() and currentTraceId() are no longer repeated across the ok methods.src/main/java/com/michelet/common/exception/GlobalExceptionHandler.java (1)
31-40: ConstraintViolationException 핸들러는 선택적 개선입니다.현재 코드베이스에는
@Validated나 파라미터 제약 조건이 사용되지 않고 있으며, 이 라이브러리는 이미MethodArgumentTypeMismatchException(타입 변환 실패)과MethodArgumentNotValidException(요청 바디 검증 실패)를 적절히 처리하고 있습니다.
ConstraintViolationException핸들러 추가는 소비하는 애플리케이션이@Validated와 제약 조건을 사용할 경우를 대비한 방어적 코딩으로 유용하지만, 현재로서는 필수 사항이 아닙니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/michelet/common/exception/GlobalExceptionHandler.java` around lines 31 - 40, Add a defensive handler for ConstraintViolationException in GlobalExceptionHandler to cover parameter/constraint validation when callers use `@Validated`; implement a new `@ExceptionHandler`(ConstraintViolationException.class) method (e.g., handleConstraintViolation) that extracts the first violation message from exception.getConstraintViolations(), logs it with log.warn("[Validation] {}" , message) and returns ResponseEntity.badRequest().body(ApiResponse.fail("VALIDATION_001", message)) so behavior matches the existing handleValidation response shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/main/java/com/michelet/common/exception/GlobalExceptionHandler.java`:
- Around line 31-40: Add a defensive handler for ConstraintViolationException in
GlobalExceptionHandler to cover parameter/constraint validation when callers use
`@Validated`; implement a new
`@ExceptionHandler`(ConstraintViolationException.class) method (e.g.,
handleConstraintViolation) that extracts the first violation message from
exception.getConstraintViolations(), logs it with log.warn("[Validation] {}" ,
message) and returns
ResponseEntity.badRequest().body(ApiResponse.fail("VALIDATION_001", message)) so
behavior matches the existing handleValidation response shape.
In `@src/main/java/com/michelet/common/response/ApiResponse.java`:
- Around line 23-35: Consolidate repeated metadata construction in the
ApiResponse factory methods by adding a single private helper that returns the
shared metadata (timestamp and traceId) and use it from all ok(...) overloads;
specifically, create a private static helper method (e.g., buildMeta() or
createMetadata()) that calls LocalDateTime.now() and currentTraceId(), then
update the public static ok(T), ok(), and ok(SuccessCode, T) methods to call
that helper and pass its values into the ApiResponse constructor, so
LocalDateTime.now() and currentTraceId() are no longer repeated across the ok
methods.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0b3f3987-bca8-4543-a38c-19ffc50a1d07
📒 Files selected for processing (4)
README.mdsrc/main/java/com/michelet/common/config/JpaAuditingConfig.javasrc/main/java/com/michelet/common/exception/GlobalExceptionHandler.javasrc/main/java/com/michelet/common/response/ApiResponse.java
✅ Files skipped from review due to trivial changes (1)
- README.md
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/com/michelet/common/config/JpaAuditingConfig.java
📝 작업 내용
MSA 에서 모든 서비스가 공통으로 쓸 라이브러리 ( common ) 분리
🚀 주요 변경 사항
✅ 자체 체크리스트 (필수)
./gradlew build실행 결과 정상 (인증샷 첨부)📸 테스트 인증샷
💬 리뷰어 전달사항 (선택)
필수적인것 같은 의존성을 추가해 뒀는데 뺐으면 하는거나 추가했으면 하는거 있으면 수정해주시거나 의견 말씀해주시면 감사합니다.
현재 AuditorAware Bean 을 각 서비스에서 등록해야하는데 ( read.me 참고 )
이것도 config 수정하면 가능할 것 같은데 한번 알아봐 주시면 감사하겠습니다.
📎 참고 자료
Summary by CodeRabbit
릴리스 노트
New Features
Documentation
Chores