Skip to content

Feat: common 초기 구성 - #7

Merged
Jinyoung-Kim96 merged 19 commits into
devfrom
feat/1-common-setting
Apr 27, 2026
Merged

Jinyoung-Kim96 merged 19 commits into
devfrom
feat/1-common-setting

Conversation

@Jinyoung-Kim96

@Jinyoung-Kim96 Jinyoung-Kim96 commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

📝 작업 내용

MSA 에서 모든 서비스가 공통으로 쓸 라이브러리 ( common ) 분리

🚀 주요 변경 사항

완료한 이슈 번호 #1

✅ 자체 체크리스트 (필수)

  • ./gradlew build 실행 결과 정상 (인증샷 첨부)
  • Postman 테스트 완료 (인증샷 첨부)
  • 팀 내 컨벤션 준수 및 불필요한 로그, import 제거
  • 중요한 변경 사항이 팀에 공유되었는지

📸 테스트 인증샷

빌드 결과 및 Postman 실행 화면을 여기에 첨부해 주세요.

💬 리뷰어 전달사항 (선택)

필수적인것 같은 의존성을 추가해 뒀는데 뺐으면 하는거나 추가했으면 하는거 있으면 수정해주시거나 의견 말씀해주시면 감사합니다.
현재 AuditorAware Bean 을 각 서비스에서 등록해야하는데 ( read.me 참고 )
이것도 config 수정하면 가능할 것 같은데 한번 알아봐 주시면 감사하겠습니다.


📎 참고 자료

관련 문서, 레퍼런스 링크 등이 있다면 여기에 첨부해주세요.

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 표준화된 API 응답 포맷(ApiResponse) 및 성공 코드 인터페이스 추가
    • 비즈니스 예외(BusinessException)와 글로벌 REST 예외 핸들러 적용(유효성/형식/헤더/기타 오류 표준 응답)
    • JPA 감사(생성자/수정자/타임스탬프) 활성화 및 소프트 삭제 지원하는 공통 엔티티 제공
    • Spring Boot 자동설정 진입점 추가로 모듈 자동 등록 지원
  • Documentation

    • 사용 가이드, 적용 방법 및 주의사항(감사자 등록 필요 등) 문서화
  • Chores

    • Gradle 빌드·래퍼, 프로젝트 설정 및 Git 설정(.gitattributes/.gitignore) 추가

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 25b3cd71-f437-4787-a3f9-a5d1ff5dad0c

📥 Commits

Reviewing files that changed from the base of the PR and between 3c4f0a8 and 6fa177b.

📒 Files selected for processing (1)
  • build.gradle
🚧 Files skipped from review as they are similar to previous changes (1)
  • build.gradle

📝 Walkthrough

Walkthrough

Java 17 기반의 공통 라이브러리 모듈을 추가합니다. Spring Boot 자동설정 진입점, JPA 감사 설정, 표준 API 응답 레코드, 도메인 예외/전역 예외처리, BaseEntity(감사 필드·소프트 삭제), Gradle 빌드·래퍼 및 Git 설정 파일들이 포함됩니다.


Changes

Cohort / File(s) Summary
빌드 및 프로젝트 설정
build.gradle, gradle/wrapper/gradle-wrapper.properties, gradlew, gradlew.bat, settings.gradle, .gitattributes, .gitignore
Java 17용 Gradle 모듈 및 Wrapper 추가, BOM·의존성 선언, wrapper 스크립트, Git 라인엔딩 규칙 및 개발 아티팩트 무시 규칙 추가.
문서화
README.md
모듈 구성·사용법, AuditorAware 등록 필요성, 패키지 구조와 예시 및 로컬 빌드/퍼블리시 명령 문서화.
Auto-Configuration 등록
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, src/main/java/com/michelet/common/config/CommonAutoConfiguration.java
Spring Boot 자동설정에 CommonAutoConfiguration 등록하여 모듈의 자동구성 진입점 활성화.
JPA 감시 설정
src/main/java/com/michelet/common/config/JpaAuditingConfig.java
@EnableJpaAuditing(auditorAwareRef = "auditorAware")로 JPA 감사 활성화(외부에서 auditorAware 빈 필요).
엔티티 공통기능
src/main/java/com/michelet/common/entity/BaseEntity.java
생성/수정 시간·사용자 ID 자동 기록을 위한 감사 필드 및 softDelete(UUID)·isDeleted() 구현 추가.
예외 처리 계층
src/main/java/com/michelet/common/exception/BusinessException.java, src/main/java/com/michelet/common/exception/ErrorCode.java, src/main/java/com/michelet/common/exception/GlobalExceptionHandler.java
비즈니스 예외 타입과 ErrorCode 계약 추가, 전역 REST 예외 처리기 추가(비즈니스 예외, 검증·타입 불일치·요청 포맷 오류, 기타 예외 매핑).
응답 포맷
src/main/java/com/michelet/common/response/ApiResponse.java, src/main/java/com/michelet/common/response/SuccessCode.java
제네릭 API 응답 레코드와 성공 코드 계약 추가. MDC 기반 traceId 포함, 성공/실패 팩토리 메서드 제공.

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
Loading
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: 저장 결과
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Poem

🐰 깡충 깡충, 새 모듈 왔네
예외는 붙잡고 응답은 예쁘게,
감사는 차곡차곡, 삭제는 살며시,
빌드도 래퍼와 함께 빙그레,
토끼가 박수 쳐요 — 윙크! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 Pull Request의 주요 변경 사항을 명확하게 반영하고 있습니다. 'common 초기 구성'은 MSA 환경에서 공통 라이브러리 모듈의 초기 설정 작업이라는 핵심 목표를 정확히 전달합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/1-common-setting

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 문서에서 compileOnlyannotationProcessor 조합을 권장합니다.

제안 패치
-    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

📥 Commits

Reviewing files that changed from the base of the PR and between 4b0b937 and 4cc62fd.

⛔ Files ignored due to path filters (1)
  • gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
📒 Files selected for processing (17)
  • .gitattributes
  • .gitignore
  • README.md
  • build.gradle
  • gradle/wrapper/gradle-wrapper.properties
  • gradlew
  • gradlew.bat
  • settings.gradle
  • src/main/java/com/michelet/common/config/CommonAutoConfiguration.java
  • src/main/java/com/michelet/common/config/JpaAuditingConfig.java
  • src/main/java/com/michelet/common/entity/BaseEntity.java
  • src/main/java/com/michelet/common/exception/BusinessException.java
  • src/main/java/com/michelet/common/exception/ErrorCode.java
  • src/main/java/com/michelet/common/exception/GlobalExceptionHandler.java
  • src/main/java/com/michelet/common/response/ApiResponse.java
  • src/main/java/com/michelet/common/response/SuccessCode.java
  • src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Comment thread README.md Outdated
Comment on lines +13 to +14
@Configuration
@EnableJpaAuditing

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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:


🏁 Script executed:

# Search for BaseEntity class to check created_by column definition
find . -type f -name "*.java" | xargs grep -l "class BaseEntity" | head -5

Repository: 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 java

Repository: Miche-Let/common

Length of output: 523


🏁 Script executed:

# Search for AuditorAware implementations in the codebase
rg "implements AuditorAware|class.*AuditorAware" --type java

Repository: Miche-Let/common

Length of output: 42


🏁 Script executed:

# Check for other `@EnableJpaAuditing` usages
rg "@EnableJpaAuditing" --type java -A 1 -B 1

Repository: 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 -50

Repository: Miche-Let/common

Length of output: 42


🏁 Script executed:

# Check the structure of the repository to understand module organization
ls -la

Repository: Miche-Let/common

Length of output: 904


AuditorAware 계약을 명시적으로 강제하여 부팅 시점에 조기 실패시키세요.

@EnableJpaAuditing은 활성화되었지만 auditorAwareRef 없이는 AuditorAware 빈 부재를 런타임까지 감지하지 않습니다. BaseEntitycreatedBynullable = 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.

Comment on lines +37 to +38
public static <T> ApiResponse<T> error(String code, String message) {
return new ApiResponse<>(false, null, code, message, LocalDateTime.now(), currentTraceId());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

오류 응답 팩토리도 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.

Suggested change
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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/exception

Repository: Miche-Let/common

Length of output: 1027


🏁 Script executed:

cat -n src/main/java/com/michelet/common/config/CommonAutoConfiguration.java

Repository: Miche-Let/common

Length of output: 587


🏁 Script executed:

cat -n src/main/java/com/michelet/common/config/JpaAuditingConfig.java

Repository: 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
ji-circle previously approved these changes Apr 26, 2026

@ji-circle ji-circle left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

확인했습니다!

AuditorAware 자동화 관련해서, JpaAuditingConfig@ConditionalOnMissingBean(AuditorAware.class)를 사용하면 서비스에서 직접 등록하지 않아도 common의 기본 로직이 작동하게 설정할 수 있다고 하네요...! 제가 제대로 이해한 건지는 잘 모르겠긴 합니다ㅠㅠ

githyj-jang
githyj-jang previously approved these changes Apr 26, 2026

@githyj-jang githyj-jang left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

확인했습니다!

코드래빗 코멘트 정도만 해결하면 될꺼같아요

@Jinyoung-Kim96
Jinyoung-Kim96 dismissed stale reviews from githyj-jang and ji-circle via 3c4f0a8 April 26, 2026 15:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4cc62fd and 3c4f0a8.

📒 Files selected for processing (4)
  • README.md
  • src/main/java/com/michelet/common/config/JpaAuditingConfig.java
  • src/main/java/com/michelet/common/exception/GlobalExceptionHandler.java
  • src/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

@ji-circle ji-circle left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

확인했습니다!

@qldo qldo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

확인하였습니다.

@jihxonx jihxonx left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

확인했습니다!

@Jinyoung-Kim96
Jinyoung-Kim96 merged commit fa3720c into dev Apr 27, 2026
3 checks passed
@Jinyoung-Kim96
Jinyoung-Kim96 deleted the feat/1-common-setting branch April 27, 2026 03:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants