Files
llm-wiki/raw/company-tech-blogs/spring-modulith-archunit-generated-exemption-and-violations-as-data.md
T

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
https://github.com/spring-projects/spring-modulith/blob/main/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java
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
https://docs.spring.io/spring-modulith/reference/events.html
feature-architecture-enforcement-rules
feature-application-port-usecase-contract
ca-skeleton
company-tech-blog
ca-skeleton
architecture
spring-modulith
archunit
code-generation
domain-event
transaction
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 임에 유의. 검증된 요약은 /ingestwiki/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


왜 저장했는지 / 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 TransactionSynchronizationManagerregisterSynchronization() 과의 내부 구현 동등성을 증명하지 않음. 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-study strength 로만 취급.
  • 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.Generated FQN 으로 annotatedWith(Generated.class) predicate 를 ca-tmpl build 에서 실제 검증. MapStruct generated class 에 해당 annotation 이 실제로 붙는지 build output 확인.
  • detectViolations() violations-as-data 패턴을 ca-tmpl negative test fixture 에 적용하려면 Spring Modulith 의존을 추가하거나 동일 패턴을 ArchUnit EvaluationResult 로 직접 구현.
  • @ApplicationModuleListener 도입 여부는 feature-domain-event-outbox-contract 브랜치에서 결정. 현재 범위 밖.

메모 / Notes

  • IS_GENERATED predicate 의 classpath 존재 여부 체크 패턴(classpath-conditional predicate)은 AOT 컴파일 환경과 일반 JVM 환경 모두를 지원하는 방어적 구현. ca-tmpl 의 MapStruct exemption 은 AOT 가 아닌 annotation processor path 의 Generated annotation 을 다루므로 classpath check 방식이 다를 수 있음.
  • detectViolations()Violations 를 반환하는 구조는 ArchUnit 의 ConditionEvents 와 유사한 결과 누적 패턴. ca-tmpl 이 Spring Modulith 없이 동일 패턴을 구현하려면 ArchUnit ArchRule.evaluate(JavaClasses)EvaluationResultFailureReport 경로 사용.
  • @ApplicationModuleListener Javadoc 에서 "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 의 실제 사용 맥락 확인 권장.