15 KiB
title, source_type, url, archive_url, related_branches, related_projects, tags, status, confidence, created
| title | source_type | url | archive_url | related_branches | related_projects | tags | status | confidence | created | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| company-tech-blog / Spring Modulith — ArchUnit IS_GENERATED predicate, detectViolations() Violations-as-data, @ApplicationModuleListener meta-annotation | company-tech-blog |
|
|
|
raw | high | 2026-05-28 |
Spring Modulith — ArchUnit IS_GENERATED predicate, detectViolations() Violations-as-data, @ApplicationModuleListener meta-annotation
Layer:
raw/company-tech-blogs/— Spring 공식 incubator 프로젝트(spring-projects org) 소스코드 및 공식 참조 문서 발췌. Spring Modulith 는 Spring Framework 1급 표준이 아닌 incubator project 임에 유의. 검증된 요약은/ingest후wiki/concepts/에 별도 작성. 원본은 raw 에 영구 보관.
Parent / 활용 branch
| Branch | 이 자료가 정당화하는 결정 |
|---|---|
| raw/branch-notes/feature-architecture-enforcement-rules | D9 (MapStruct @Generated exemption): Spring Modulith 자체가 annotatedWith(Generated.class) 패턴을 production에서 사용함을 보임 → ArchUnit predicate DSL로 generated code 면제가 실현 가능한 패턴임을 corroborate. S1 (negative test fixture): detectViolations() 가 Violations 객체를 반환하는 violations-as-data 패턴 — Spring Modulith 공식 negative test 패턴 |
| raw/branch-notes/feature-application-port-usecase-contract | D1 (Spring @Transactional forbidden) counter-evidence: @ApplicationModuleListener 가 @Transactional(propagation = Propagation.REQUIRES_NEW) 를 meta-annotation 으로 재노출 — ca-tmpl 의 application layer @Transactional 직접 import 금지 정책과 정면 충돌하는 패턴 존재. 추가 증거로 기록 (D3 counter-evidence, does not override D3) |
출처 / Source
- 원본 URL 1: https://github.com/spring-projects/spring-modulith/blob/main/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java
- 원본 URL 2: https://github.com/spring-projects/spring-modulith/blob/main/spring-modulith-events/spring-modulith-events-api/src/main/java/org/springframework/modulith/events/ApplicationModuleListener.java
- 원본 URL 3: https://docs.spring.io/spring-modulith/reference/events.html
- 아카이브 URL: (미제공)
- 저자 / 조직: Oliver Drotbohm / spring-projects (Spring 공식 incubator org)
- 발행일: ongoing (GitHub main branch, accessed 2026-05-28)
- 마지막 확인일: 2026-05-28
왜 저장했는지 / Why archived
Spring 공식 incubator(spring-projects org)가 ArchUnit을 production 코드에서 사용하는 방식을 직접 확인하기 위해 보관한다. ca-tmpl의 3가지 결정(D9 MapStruct generated exemption, S1 negative test fixture, D1/D3 @Transactional forbidden counter-evidence)이 이 자료로 corroborate 또는 counter-evidence 처리된다.
핵심 인용 / Key quotes (verbatim)
[ApplicationModules.java — IS_GENERATED field & static initializer]
private static final @Nullable DescribedPredicate<CanBeAnnotated> IS_GENERATED; static { IS_GENERATED = ClassUtils.isPresent("org.springframework.aot.generate.Generated", ApplicationModules.class.getClassLoader()) ? getAtGenerated() : DescribedPredicate.alwaysFalse(); }
[ApplicationModules.java — getAtGenerated() implementation]
@Nullable private static DescribedPredicate<CanBeAnnotated> getAtGenerated() { return annotatedWith(Generated.class); }
[ApplicationModules.java — detectViolations(VerificationOptions) method]
public Violations detectViolations(VerificationOptions options) { var cycleViolations = rootPackages.stream() // .map(this::assertNoCyclesFor) // .flatMap(it -> it.getDetails().stream()) // .collect(toViolations()); var additionalViolations = options.getAdditionalVerifications().stream() .map(it -> it.evaluate(allClasses)) .map(EvaluationResult::getFailureReport) .flatMap(it -> it.getDetails().stream()) .collect(toViolations()); var dependencyViolations = allModules() // .map(it -> it.detectDependencies(this)) // .reduce(NONE, Violations::and); return cycleViolations.and(additionalViolations).and(dependencyViolations); }
[ApplicationModuleListener.java — meta-annotation 선언부 verbatim]
@Async @Transactional(propagation = Propagation.REQUIRES_NEW) @TransactionalEventListener @Documented @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface ApplicationModuleListener {
[ApplicationModuleListener.java Javadoc — motivation 원문]
"An ApplicationModuleListener is an Async Spring TransactionalEventListener that runs in a transaction itself. Thus, the annotation serves as syntactic sugar for the generally recommend setup to integrate application modules via events. The setup makes sure that an original business transaction completes successfully and the integration asynchronously runs in a transaction itself to decouple the integration as much as possible from the original unit of work."
[docs.spring.io/spring-modulith/reference/events.html — Event Publication Registry]
"Spring Modulith ships with an event publication registry that hooks into the core event publication mechanism of Spring Framework. On event publication, it finds out about the transactional event listeners that will get the event delivered and writes entries for each of them (dark blue) into an event publication log as part of the original business transaction."
Claims Extracted / 추출된 주장
| Claim ID | Claim (이 자료가 직접 말하는 것) | Evidence quote | Strength | Applies to | Does not prove |
|---|---|---|---|---|---|
| SPRING-MOD-AU-C1 | Spring Modulith 은 AOT Generated annotation 이 classpath 에 존재하면 annotatedWith(Generated.class) predicate 를 사용하고, 없으면 alwaysFalse() 로 fallback 하는 IS_GENERATED predicate 를 production 코드에서 사용한다 |
IS_GENERATED = ClassUtils.isPresent("org.springframework.aot.generate.Generated", ...) ? getAtGenerated() : DescribedPredicate.alwaysFalse(); |
company-case-study (incubator project — Spring Framework 1급 표준 아님) |
Spring AOT org.springframework.aot.generate.Generated annotation 이 붙은 클래스를 ArchUnit rule 에서 면제할 때 |
MapStruct 의 javax.annotation.processing.Generated 또는 javax.annotation.Generated 가 동일 FQN 임을 증명하지 않음. annotatedWith(Generated.class) 패턴의 적용 가능성을 증명하되 annotation FQN 은 별도 확인 필요 |
| SPRING-MOD-AU-C2 | Spring Modulith 의 detectViolations(VerificationOptions) 는 예외를 throw 하지 않고 Violations 객체를 반환한다 — 위반을 data 로 다루는 violations-as-data 패턴 |
public Violations detectViolations(VerificationOptions options) { ... return cycleViolations.and(additionalViolations).and(dependencyViolations); } |
company-case-study (incubator project) |
Spring Modulith verifier 를 사용할 때 위반을 assertion 대신 data 로 수집해 처리하는 패턴 | ArchUnit verify() 호출과의 동등성을 증명하지 않음. ca-tmpl 이 Spring Modulith verifier 를 도입한다는 결정을 정당화하지 않음 (feature-architecture-enforcement-rules Out of scope — "Spring Modulith verifier 도입") |
| SPRING-MOD-TX-C1 | @ApplicationModuleListener 는 @Async, @Transactional(propagation = Propagation.REQUIRES_NEW), @TransactionalEventListener 를 meta-annotation 으로 포함한다 — Spring incubator 공식 event integration annotation 이 @Transactional 을 재노출함 |
@Async @Transactional(propagation = Propagation.REQUIRES_NEW) @TransactionalEventListener ... public @interface ApplicationModuleListener |
company-case-study (incubator project) |
Spring event-driven 모듈 통합에서 asynchronous transactional event listener 를 선언할 때 | Spring Framework 공식이 application layer 에서 @Transactional 직접 사용을 권장한다는 뜻이 아님. ca-tmpl 의 D3 (@Transactional direct import 금지) 가 잘못됨을 증명하지 않음 — 이 자료는 counter-evidence 로 기록되며 D3 를 override 하지 않음 |
| SPRING-MOD-TX-C2 | Spring Modulith Event Publication Registry 는 이벤트 발행 시 transactional event listener 각각에 대한 항목을 원래 비즈니스 트랜잭션의 일부로 event publication log 에 기록한다 | "writes entries for each of them (dark blue) into an event publication log as part of the original business transaction." | company-case-study (incubator project) |
Spring Modulith Event Publication Registry 가 outbox-like durability 를 제공하는 방식 이해 시 | Spring Framework TransactionSynchronizationManager 의 registerSynchronization() 과의 내부 구현 동등성을 증명하지 않음. Event Publication Registry 도입 없이도 동일 보장이 가능하다는 뜻이 아님 |
| SPRING-MOD-TX-C3 | @ApplicationModuleListener 는 원래 비즈니스 트랜잭션이 성공적으로 완료된 후 비동기로 자체 트랜잭션 안에서 실행된다 |
"The setup makes sure that an original business transaction completes successfully and the integration asynchronously runs in a transaction itself to decouple the integration as much as possible from the original unit of work." | company-case-study (incubator project) |
Spring Modulith 기반 모듈 간 이벤트 통합에서 transaction decoupling 패턴 이해 시 | ca-tmpl 의 현재 outbox/event 구현 없이도 이 동작이 보장된다는 뜻이 아님. Event Publication Registry 없이 @ApplicationModuleListener 단독 사용 시 유실 가능성 있음 (Javadoc 자체가 "In combination with ... Event Publication Registry" 를 권고함) |
Usage Boundaries / 적용 경계
이 자료가 직접 증명하는 것
SPRING-MOD-AU-C1:annotatedWith(Generated.class)ArchUnit predicate DSL 패턴이 Spring 공식 incubator 코드에서 실제로 사용됨SPRING-MOD-AU-C2:detectViolations()가 예외 대신Violations객체를 반환하는 violations-as-data 패턴이 Spring Modulith 공식 API 임SPRING-MOD-TX-C1:@ApplicationModuleListener가@Transactional(propagation = Propagation.REQUIRES_NEW)를 meta-annotation 으로 포함함SPRING-MOD-TX-C2 / C3: Event Publication Registry 가 original business transaction 내에서 log 를 기록하고, listener 가 비동기·독립 트랜잭션으로 실행됨
이 자료가 증명하지 않는 것
- Spring Modulith 는 incubator project — Spring Framework 1급 표준이 아님.
company-case-studystrength 로만 취급. - MapStruct 가 생성하는 annotation 의 FQN 은
javax.annotation.processing.Generated(Java 9+) 또는javax.annotation.Generated(Java 8) 이며, Spring AOT 의org.springframework.aot.generate.Generated와 다른 FQN 임.SPRING-MOD-AU-C1은 동일 ArchUnit predicate 패턴이 사용됨을 보이지만, D9 corroboration 을 완성하려면 MapStruct annotation FQN 별도 확인 필요. SPRING-MOD-TX-C1은 ca-tmpl D3 결정(application layer@Transactional직접 import 금지)의 반례(counter-evidence)로 기록되나, Spring Modulith 가 사용한다고 해서 ca-tmpl 의 D3 가 잘못되었음을 의미하지 않음.@ApplicationModuleListener는 application layer annotation 이 아닌 event listener meta-annotation 임.@TransactionalEventListener동작 자체는 이미 raw/official-docs/spring-transactional-event-listener 에 기록됨 (있다면). 본 archive 는 그 위에 Modulith 의 meta-annotation 결합 패턴을 추가하는 자료.
내 프로젝트에 적용하려면 추가 확인이 필요한 것
- D9 (MapStruct exemption) 완성:
javax.annotation.processing.GeneratedFQN 으로annotatedWith(Generated.class)predicate 를 ca-tmpl build 에서 실제 검증. MapStruct generated class 에 해당 annotation 이 실제로 붙는지 build output 확인. detectViolations()violations-as-data 패턴을 ca-tmpl negative test fixture 에 적용하려면 Spring Modulith 의존을 추가하거나 동일 패턴을 ArchUnitEvaluationResult로 직접 구현.@ApplicationModuleListener도입 여부는feature-domain-event-outbox-contract브랜치에서 결정. 현재 범위 밖.
메모 / Notes
IS_GENERATEDpredicate 의 classpath 존재 여부 체크 패턴(classpath-conditional predicate)은 AOT 컴파일 환경과 일반 JVM 환경 모두를 지원하는 방어적 구현. ca-tmpl 의 MapStruct exemption 은 AOT 가 아닌 annotation processor path 의Generatedannotation 을 다루므로 classpath check 방식이 다를 수 있음.detectViolations()가Violations를 반환하는 구조는 ArchUnit 의ConditionEvents와 유사한 결과 누적 패턴. ca-tmpl 이 Spring Modulith 없이 동일 패턴을 구현하려면 ArchUnitArchRule.evaluate(JavaClasses)→EvaluationResult→FailureReport경로 사용.@ApplicationModuleListenerJavadoc 에서 "it is advisable that you use these integration listeners in combination with the Spring Modulith Event Publication Registry" — Event Publication Registry 없이 단독 사용은 listener 실패 시 재시도 보장이 없음.- 추가로 봐야 할 동일 출처 페이지:
spring-modulith-core/src/main/java/org/springframework/modulith/core/ArchitecturallyEvidentType.java— IS_GENERATED 의 실제 사용 맥락 확인 권장.
Related / 관련
- raw/official-docs/mapstruct-generated-annotation-official — D9: MapStruct 가
@Generated를 generated mapper 에 부착한다는 공식 근거 (MS-ANNOT-C1, MS-ANNOT-C2). 본 archive 의 SPRING-MOD-AU-C1 과 함께 D9 UNSUPPORTED_DECISION 해제 판단에 사용. - raw/official-docs/archunit-user-guide — ArchUnit predicate DSL 공식 문서. SPRING-MOD-AU-C1 의
annotatedWith(Generated.class)패턴을 ca-tmpl 에 적용할 때 레퍼런스. - raw/company-tech-blogs/modulith-kakaobank-techblog-2025 — KakaoBank 의 Spring Modulith + hexagonal multi-module 사례. 같은 주제 다른 company-tech-blog.
- raw/company-tech-blogs/modulith-arawn-github-modular-monoliths-spring — Spring Modulith 기반 modular monolith 패턴. 같은 주제 다른 tech blog.
- raw/branch-notes/feature-architecture-enforcement-rules — 본 archive 를 D9 근거로 활용하는 branch.
- raw/branch-notes/feature-application-port-usecase-contract — 본 archive 를 D1/D3 counter-evidence 로 활용하는 branch.