{ "schema_version": "1.0", "document": "/home/donghyeon/workspace/chat-gpt-container/document-haness/docs/clean-architecture-backend-template/final/document.md", "document_sha256": "8071fe71b3359d9cf60b95909c26c7b50653ce2f22bbc5fcf6988719bb91236d", "line_count": 47035, "line_number_space": "canonical-source-with-managed-blocks-collapsed", "anchor": { "kind": "line", "value": 37778, "line": 37778 }, "current_section": { "heading": { "line": 37778, "level": 4, "text": "2. 의존성과 런타임 배선" }, "start_line": 37778, "end_line": 37799, "text": "#### 2. 의존성과 런타임 배선\n\n들어오는 것: `messaging-core-api`(api) 하나.\n\n나가는 것: `messaging-runtime-core`, `messaging-kafka`, `messaging-rabbit`, `messaging-admin-runtime`, `messaging-pulsar-experimental`, `messaging-nats-experimental`, `messaging-spring-boot-starter`.\n\n**이 leaf는 messaging family에서 배선이 가장 잘 된 축에 속한다.** 어댑터 두 곳이 직접 소비한다.\n\n| 소비자 | 무엇을 쓰는가 |\n|---|---|\n| `messaging-kafka/KafkaSecurityConfigurer` | `BrokerTlsPolicy`, `CredentialRuntimeRegistry`, `CredentialProvider` |\n| `messaging-rabbit/RabbitSecurityConfigurer` | `BrokerTlsPolicy`, `CredentialRuntimeRegistry` |\n| `messaging-runtime-core/DefaultMessagePublisher` | `DestinationAccessPolicy` |\n| `messaging-runtime-core/DeclaredDestinationAccess` | `DestinationAccessPolicy` |\n| starter `MessagingCoreAutoConfiguration` | `MessageSecurityValidator`·`BrokerTlsPolicy`·`CredentialRuntimeRegistry` bean |\n| starter `MessagingCredentialRequirementValidator` | `CredentialProvider` |\n| starter `Kafka/RabbitMessagingAutoConfiguration` | `BrokerTlsPolicy`, `CredentialRuntimeRegistry` |\n\n이 leaf 자체는 Spring 주석을 갖지 않는다.\n\n---\n" }, "previous_section": { "heading": { "line": 37739, "level": 4, "text": "1. 모듈의 정체와 경계" }, "start_line": 37739, "end_line": 37777, "text": "#### 1. 모듈의 정체와 경계\n\n이 leaf는 **\"브로커에 연결하기 전에 무엇이 참이어야 하는가\"**를 소유한다. 벤더 의존성이 0이고 브로커를 만지지 않는다 — 어댑터의 security configurer가 이 leaf의 타입을 받아 실제 클라이언트 설정을 만든다.\n\n세 가지 원칙이 코드 전반에 반복된다.\n\n**(a) 비밀은 참조로만 다룬다.**\n\n```java\n// BrokerCredentialProfile.java:5-8\n *
No variant carries a secret. The platform stores an identifier and resolves the material\n * through a {@link CredentialProvider} at connect time, so a rotation is a provider concern and a\n * heap dump or configuration print never yields a usable credential.\n```\n\n`BrokerCredentialProfile`의 다섯 변형 전부가 `credentialId` 하나만 갖는다 — `SaslScram`, `OAuth2`, `MutualTls`, `UsernamePassword`, `Nkey`. sealed interface이므로 여섯 번째를 만들려면 이 파일을 고쳐야 한다.\n\n**(b) 타입이 통제의 일부다.**\n\n```java\n// CredentialRuntime.java:13-15\n *
Holds the material in a {@code char[]} that {@link #clear()} overwrites. A {@code String}\n * cannot be erased — it stays in the constant pool and in every heap dump taken until the next GC\n * decides otherwise — so the type of the field is itself part of the control.\n```\n\n`CredentialProvider.resolve`가 `char[]`을 반환하고 `CredentialRuntime`이 그것을 참조로 보관하며 `clear()`가 `Arrays.fill(material, '\\0')` 후 빈 배열로 교체한다.\n\n**(c) 역할 분리가 강제된다.** `BrokerSecurityProfile`이 producer·consumer·admin 세 자격증명을 **별도 필드**로 갖는다.\n\n```java\n// BrokerSecurityProfile.java:9-11\n *
Producer, consumer, and admin credentials are separate fields rather than one connection\n * credential. That separation is what makes \"an application cannot purge a topic\" enforceable: the\n * runtime never holds admin material, so a compromised handler has nothing to escalate with.\n```\n\n---\n"
},
"next_section": {
"heading": {
"line": 37800,
"level": 4,
"text": "3. 패키지/컴포넌트 지도"
},
"start_line": 37800,
"end_line": 37827,
"text": "#### 3. 패키지/컴포넌트 지도\n\n```\n자격증명 수명주기\n CredentialProvider (port)\n ↓ resolve(id) → char[] / expiresAt(id) → Optional No variant carries a secret. The platform stores an identifier and resolves the material"
},
{
"line": 37750,
"text": " * through a {@link CredentialProvider} at connect time, so a rotation is a provider concern and a"
},
{
"line": 37751,
"text": " * heap dump or configuration print never yields a usable credential."
},
{
"line": 37752,
"text": "```"
},
{
"line": 37753,
"text": ""
},
{
"line": 37754,
"text": "`BrokerCredentialProfile`의 다섯 변형 전부가 `credentialId` 하나만 갖는다 — `SaslScram`, `OAuth2`, `MutualTls`, `UsernamePassword`, `Nkey`. sealed interface이므로 여섯 번째를 만들려면 이 파일을 고쳐야 한다."
},
{
"line": 37755,
"text": ""
},
{
"line": 37756,
"text": "**(b) 타입이 통제의 일부다.**"
},
{
"line": 37757,
"text": ""
},
{
"line": 37758,
"text": "```java"
},
{
"line": 37759,
"text": "// CredentialRuntime.java:13-15"
},
{
"line": 37760,
"text": " * Holds the material in a {@code char[]} that {@link #clear()} overwrites. A {@code String}"
},
{
"line": 37761,
"text": " * cannot be erased — it stays in the constant pool and in every heap dump taken until the next GC"
},
{
"line": 37762,
"text": " * decides otherwise — so the type of the field is itself part of the control."
},
{
"line": 37763,
"text": "```"
},
{
"line": 37764,
"text": ""
},
{
"line": 37765,
"text": "`CredentialProvider.resolve`가 `char[]`을 반환하고 `CredentialRuntime`이 그것을 참조로 보관하며 `clear()`가 `Arrays.fill(material, '\\0')` 후 빈 배열로 교체한다."
},
{
"line": 37766,
"text": ""
},
{
"line": 37767,
"text": "**(c) 역할 분리가 강제된다.** `BrokerSecurityProfile`이 producer·consumer·admin 세 자격증명을 **별도 필드**로 갖는다."
},
{
"line": 37768,
"text": ""
},
{
"line": 37769,
"text": "```java"
},
{
"line": 37770,
"text": "// BrokerSecurityProfile.java:9-11"
},
{
"line": 37771,
"text": " * Producer, consumer, and admin credentials are separate fields rather than one connection"
},
{
"line": 37772,
"text": " * credential. That separation is what makes \"an application cannot purge a topic\" enforceable: the"
},
{
"line": 37773,
"text": " * runtime never holds admin material, so a compromised handler has nothing to escalate with."
},
{
"line": 37774,
"text": "```"
},
{
"line": 37775,
"text": ""
},
{
"line": 37776,
"text": "---"
},
{
"line": 37777,
"text": ""
},
{
"line": 37778,
"text": "#### 2. 의존성과 런타임 배선"
},
{
"line": 37779,
"text": ""
},
{
"line": 37780,
"text": "들어오는 것: `messaging-core-api`(api) 하나."
},
{
"line": 37781,
"text": ""
},
{
"line": 37782,
"text": "나가는 것: `messaging-runtime-core`, `messaging-kafka`, `messaging-rabbit`, `messaging-admin-runtime`, `messaging-pulsar-experimental`, `messaging-nats-experimental`, `messaging-spring-boot-starter`."
},
{
"line": 37783,
"text": ""
},
{
"line": 37784,
"text": "**이 leaf는 messaging family에서 배선이 가장 잘 된 축에 속한다.** 어댑터 두 곳이 직접 소비한다."
},
{
"line": 37785,
"text": ""
},
{
"line": 37786,
"text": "| 소비자 | 무엇을 쓰는가 |"
},
{
"line": 37787,
"text": "|---|---|"
},
{
"line": 37788,
"text": "| `messaging-kafka/KafkaSecurityConfigurer` | `BrokerTlsPolicy`, `CredentialRuntimeRegistry`, `CredentialProvider` |"
},
{
"line": 37789,
"text": "| `messaging-rabbit/RabbitSecurityConfigurer` | `BrokerTlsPolicy`, `CredentialRuntimeRegistry` |"
},
{
"line": 37790,
"text": "| `messaging-runtime-core/DefaultMessagePublisher` | `DestinationAccessPolicy` |"
},
{
"line": 37791,
"text": "| `messaging-runtime-core/DeclaredDestinationAccess` | `DestinationAccessPolicy` |"
},
{
"line": 37792,
"text": "| starter `MessagingCoreAutoConfiguration` | `MessageSecurityValidator`·`BrokerTlsPolicy`·`CredentialRuntimeRegistry` bean |"
},
{
"line": 37793,
"text": "| starter `MessagingCredentialRequirementValidator` | `CredentialProvider` |"
},
{
"line": 37794,
"text": "| starter `Kafka/RabbitMessagingAutoConfiguration` | `BrokerTlsPolicy`, `CredentialRuntimeRegistry` |"
},
{
"line": 37795,
"text": ""
},
{
"line": 37796,
"text": "이 leaf 자체는 Spring 주석을 갖지 않는다."
},
{
"line": 37797,
"text": ""
},
{
"line": 37798,
"text": "---"
},
{
"line": 37799,
"text": ""
},
{
"line": 37800,
"text": "#### 3. 패키지/컴포넌트 지도"
},
{
"line": 37801,
"text": ""
},
{
"line": 37802,
"text": "```"
},
{
"line": 37803,
"text": "자격증명 수명주기"
},
{
"line": 37804,
"text": " CredentialProvider (port)"
},
{
"line": 37805,
"text": " ↓ resolve(id) → char[] / expiresAt(id) → Optional No variant carries a secret. The platform stores an identifier and resolves the material\n37750 | * through a {@link CredentialProvider} at connect time, so a rotation is a provider concern and a\n37751 | * heap dump or configuration print never yields a usable credential.\n37752 | ```\n37753 | \n37754 | `BrokerCredentialProfile`의 다섯 변형 전부가 `credentialId` 하나만 갖는다 — `SaslScram`, `OAuth2`, `MutualTls`, `UsernamePassword`, `Nkey`. sealed interface이므로 여섯 번째를 만들려면 이 파일을 고쳐야 한다.\n37755 | \n37756 | **(b) 타입이 통제의 일부다.**\n37757 | \n37758 | ```java\n37759 | // CredentialRuntime.java:13-15\n37760 | * Holds the material in a {@code char[]} that {@link #clear()} overwrites. A {@code String}\n37761 | * cannot be erased — it stays in the constant pool and in every heap dump taken until the next GC\n37762 | * decides otherwise — so the type of the field is itself part of the control.\n37763 | ```\n37764 | \n37765 | `CredentialProvider.resolve`가 `char[]`을 반환하고 `CredentialRuntime`이 그것을 참조로 보관하며 `clear()`가 `Arrays.fill(material, '\\0')` 후 빈 배열로 교체한다.\n37766 | \n37767 | **(c) 역할 분리가 강제된다.** `BrokerSecurityProfile`이 producer·consumer·admin 세 자격증명을 **별도 필드**로 갖는다.\n37768 | \n37769 | ```java\n37770 | // BrokerSecurityProfile.java:9-11\n37771 | * Producer, consumer, and admin credentials are separate fields rather than one connection\n37772 | * credential. That separation is what makes \"an application cannot purge a topic\" enforceable: the\n37773 | * runtime never holds admin material, so a compromised handler has nothing to escalate with.\n37774 | ```\n37775 | \n37776 | ---\n37777 | \n37778 | #### 2. 의존성과 런타임 배선\n37779 | \n37780 | 들어오는 것: `messaging-core-api`(api) 하나.\n37781 | \n37782 | 나가는 것: `messaging-runtime-core`, `messaging-kafka`, `messaging-rabbit`, `messaging-admin-runtime`, `messaging-pulsar-experimental`, `messaging-nats-experimental`, `messaging-spring-boot-starter`.\n37783 | \n37784 | **이 leaf는 messaging family에서 배선이 가장 잘 된 축에 속한다.** 어댑터 두 곳이 직접 소비한다.\n37785 | \n37786 | | 소비자 | 무엇을 쓰는가 |\n37787 | |---|---|\n37788 | | `messaging-kafka/KafkaSecurityConfigurer` | `BrokerTlsPolicy`, `CredentialRuntimeRegistry`, `CredentialProvider` |\n37789 | | `messaging-rabbit/RabbitSecurityConfigurer` | `BrokerTlsPolicy`, `CredentialRuntimeRegistry` |\n37790 | | `messaging-runtime-core/DefaultMessagePublisher` | `DestinationAccessPolicy` |\n37791 | | `messaging-runtime-core/DeclaredDestinationAccess` | `DestinationAccessPolicy` |\n37792 | | starter `MessagingCoreAutoConfiguration` | `MessageSecurityValidator`·`BrokerTlsPolicy`·`CredentialRuntimeRegistry` bean |\n37793 | | starter `MessagingCredentialRequirementValidator` | `CredentialProvider` |\n37794 | | starter `Kafka/RabbitMessagingAutoConfiguration` | `BrokerTlsPolicy`, `CredentialRuntimeRegistry` |\n37795 | \n37796 | 이 leaf 자체는 Spring 주석을 갖지 않는다.\n37797 | \n37798 | ---\n37799 | \n37800 | #### 3. 패키지/컴포넌트 지도\n37801 | \n37802 | ```\n37803 | 자격증명 수명주기\n37804 | CredentialProvider (port)\n37805 | ↓ resolve(id) → char[] / expiresAt(id) → Optional