refactor: 각 어댑터터별 리펙토링 진행

This commit is contained in:
DongHyeonka
2026-08-24 18:26:40 +09:00
parent e98b56eb03
commit 0137263441
439 changed files with 31935 additions and 4719 deletions
@@ -26,8 +26,12 @@ dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.Notification
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformMode
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationProviderAssembly
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationSecretRequirements
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationSmtpProviderConfig
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationSmtpSettings
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.ProviderRuntimeAssembler
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.ProviderType
dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.SmtpProviderRuntimeAssembler
dev.caskeleton.adapter.outbound.notification.platform.dispatch.AttemptPermit
dev.caskeleton.adapter.outbound.notification.platform.dispatch.CapabilityReconciliationGateway
dev.caskeleton.adapter.outbound.notification.platform.dispatch.ConfiguredProfileCatalog
@@ -56,6 +60,7 @@ dev.caskeleton.adapter.outbound.notification.platform.observation.LoggingNotific
dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthReporter
dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthSnapshot
dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationServingThresholds
dev.caskeleton.adapter.outbound.notification.platform.provider.EmailAttachments
dev.caskeleton.adapter.outbound.notification.platform.provider.ProviderResults
dev.caskeleton.adapter.outbound.notification.platform.provider.UnconfiguredAttachmentResolver
dev.caskeleton.adapter.outbound.notification.platform.provider.apns.ApnsFailureClassifier
@@ -89,6 +94,7 @@ dev.caskeleton.adapter.outbound.notification.platform.provider.ses.SesRequestMap
dev.caskeleton.adapter.outbound.notification.platform.provider.ses.SesSuppressionUpdater
dev.caskeleton.adapter.outbound.notification.platform.provider.ses.SnsCertificateProvider
dev.caskeleton.adapter.outbound.notification.platform.provider.ses.SnsSignatureVerifier
dev.caskeleton.adapter.outbound.notification.platform.provider.smtp.JavaMailSenderSmtpDispatch
dev.caskeleton.adapter.outbound.notification.platform.provider.smtp.SmtpDispatch
dev.caskeleton.adapter.outbound.notification.platform.provider.smtp.SmtpDispatchException
dev.caskeleton.adapter.outbound.notification.platform.provider.smtp.SmtpFailureClassifier
@@ -122,6 +128,7 @@ dev.caskeleton.adapter.outbound.notification.platform.reactor.ReactorContextBrid
dev.caskeleton.adapter.outbound.notification.platform.reactor.ReactorNotificationOrchestrator
dev.caskeleton.adapter.outbound.notification.platform.security.AesGcmCallbackPayloadProtection
dev.caskeleton.adapter.outbound.notification.platform.security.AesGcmContactPointProtector
dev.caskeleton.adapter.outbound.notification.platform.security.AesGcmNotificationPayloadProtection
dev.caskeleton.adapter.outbound.notification.platform.security.CredentialGeneration
dev.caskeleton.adapter.outbound.notification.platform.security.HmacProviderRequestIdHasher
dev.caskeleton.adapter.outbound.notification.platform.security.ProviderCredentialManager
@@ -137,6 +144,7 @@ dev.caskeleton.adapter.outbound.notification.platform.template.NotificationTempl
dev.caskeleton.adapter.outbound.notification.platform.template.PlaceholderTemplateEngine
dev.caskeleton.adapter.outbound.notification.platform.template.Sha256MessageDigestAdapter
dev.caskeleton.adapter.outbound.notification.platform.template.TemplateSlotMode
dev.caskeleton.adapter.outbound.notification.platform.template.TemplateSlotPolicy
dev.caskeleton.adapter.outbound.notification.platform.template.ThymeleafNotificationRenderer
dev.caskeleton.adapter.outbound.notification.platform.template.ThymeleafStringTemplateEngine
dev.caskeleton.adapter.outbound.notification.provider.AttemptCorrelationId
@@ -384,6 +392,7 @@ dev.caskeleton.application.notification.platform.callback.ProviderEventProjector
dev.caskeleton.application.notification.platform.callback.ProviderEventRecord
dev.caskeleton.application.notification.platform.callback.ProviderEventRecordId
dev.caskeleton.application.notification.platform.callback.ProviderEventSource
dev.caskeleton.application.notification.platform.callback.ProviderRequestIdHash
dev.caskeleton.application.notification.platform.callback.StandardDeliveryProjector
dev.caskeleton.application.notification.platform.callback.SuppressionFacts
dev.caskeleton.application.notification.platform.callback.VerifiedCallback
@@ -400,6 +409,7 @@ dev.caskeleton.application.notification.platform.contact.LegacyFcmRegistrationTo
dev.caskeleton.application.notification.platform.contact.MobilePushTarget
dev.caskeleton.application.notification.platform.contact.PhoneNumber
dev.caskeleton.application.notification.platform.contact.WebPushSubscriptionValue
dev.caskeleton.application.notification.platform.dispatch.AcceptNotificationApplicationUseCase
dev.caskeleton.application.notification.platform.dispatch.ApplicationReceiptServiceImpl
dev.caskeleton.application.notification.platform.dispatch.AttachmentIntegrityGuard
dev.caskeleton.application.notification.platform.dispatch.CancelNotificationApplicationUseCase
@@ -430,6 +440,7 @@ dev.caskeleton.application.notification.platform.dispatch.PolicyRoutePlanner
dev.caskeleton.application.notification.platform.dispatch.ProviderDispatchGatewayPort
dev.caskeleton.application.notification.platform.dispatch.ProviderProfileCatalogPort
dev.caskeleton.application.notification.platform.dispatch.ProviderRequestIdHasherPort
dev.caskeleton.application.notification.platform.dispatch.PublishNotificationTemplateApplicationUseCase
dev.caskeleton.application.notification.platform.dispatch.RecipientDeliveryRecord
dev.caskeleton.application.notification.platform.dispatch.RecipientDeliveryStorePort
dev.caskeleton.application.notification.platform.dispatch.RecipientLease
@@ -499,12 +510,16 @@ dev.caskeleton.application.notification.platform.policy.SuppressionReason
dev.caskeleton.application.notification.platform.policy.SuppressionScope
dev.caskeleton.application.notification.platform.policy.SuppressionSource
dev.caskeleton.application.notification.platform.policy.SuppressionStorePort
dev.caskeleton.application.notification.platform.port.in.AcceptNotificationCommand
dev.caskeleton.application.notification.platform.port.in.AcceptNotificationUseCase
dev.caskeleton.application.notification.platform.port.in.CancelNotificationCommand
dev.caskeleton.application.notification.platform.port.in.CancelNotificationUseCase
dev.caskeleton.application.notification.platform.port.in.GetNotificationQuery
dev.caskeleton.application.notification.platform.port.in.GetNotificationUseCase
dev.caskeleton.application.notification.platform.port.in.IngestProviderCallbackCommand
dev.caskeleton.application.notification.platform.port.in.IngestProviderCallbackUseCase
dev.caskeleton.application.notification.platform.port.in.PublishNotificationTemplateCommand
dev.caskeleton.application.notification.platform.port.in.PublishNotificationTemplateUseCase
dev.caskeleton.application.notification.platform.port.in.ScheduleNotificationCommand
dev.caskeleton.application.notification.platform.port.in.ScheduleNotificationUseCase
dev.caskeleton.application.notification.platform.port.in.SubmitNotificationCommand
@@ -539,6 +554,8 @@ dev.caskeleton.application.notification.platform.push.ReceiptKind
dev.caskeleton.application.notification.platform.push.ReceiptResult
dev.caskeleton.application.notification.platform.security.AccessContext
dev.caskeleton.application.notification.platform.security.ContactPointProtector
dev.caskeleton.application.notification.platform.security.NotificationPayloadProtection
dev.caskeleton.application.notification.platform.security.NotificationPayloadUnreadableException
dev.caskeleton.application.notification.platform.security.NotificationRedactor
dev.caskeleton.application.notification.platform.security.ProtectedContactPoint
dev.caskeleton.application.notification.platform.security.SafeDiagnosticContext
@@ -41,6 +41,12 @@ still waiting on gets claimed by a second worker, and the recipient receives the
| `callbacks.enabled` | `APP_NOTIFICATION_PLATFORM_CALLBACKS_ENABLED` | `false` | boolean |
| `callbacks.max-body-bytes` | `APP_NOTIFICATION_PLATFORM_CALLBACK_MAX_BODY_BYTES` | `65508` | 1..65508 |
| `callbacks.replay-skew` | `APP_NOTIFICATION_PLATFORM_CALLBACK_REPLAY_SKEW` | `5m` | positive |
| `callbacks.trusted-proxies` | `APP_NOTIFICATION_PLATFORM_CALLBACK_TRUSTED_PROXIES` | *(empty)* | CSV of peer addresses |
여러 provider가 요청 URL에 서명하므로, 그 URL을 잘못 재구성하면 정상 webhook이 전부 서명 실패가 된다.
`trusted-proxies`가 비어 있으면 forwarded 헤더를 **믿지 않고** 컨테이너가 관측한 값을 쓴다. 무조건 믿으면
아무 호출자나 자기 서명이 검증될 URL을 고를 수 있어 서명 자체가 무의미해진다. 로드밸런서 뒤에 있는 배포는
그 peer를 명시한다.
65508 is not a round number by accident: it is the ciphertext column's 65536 bytes minus the AES-GCM
nonce and tag. A larger configured value would pass every check above the database and fail the
@@ -69,6 +75,43 @@ them, because the keys are deployment-chosen; supply them as YAML or as
A profile pins provider type, environment, credential profile, timeouts, concurrency and rate limit.
Sender identity and credential profile are separate concerns.
## SMTP relay
The one provider profile the template ships, off. A deployment that wants the common case sets
`APP_NOTIFICATION_PLATFORM_SMTP_ENABLED=true` and the relay address; one that wants a different
profile id or a second family declares it in its own YAML instead.
The profile and the relay are separate tables below because they answer different questions. The
profile says *which* provider serves EMAIL and under what limits; the relay says *what the transport
is*. Host, port and credentials are not here at all — they stay `spring.mail.*`, because Spring
already owns them and a second spelling would be a second thing to keep in step.
| Property | Environment variable | Default | Bound |
|---|---|---|---|
| `providers.smtp.enabled` | `APP_NOTIFICATION_PLATFORM_SMTP_ENABLED` | `false` | boolean |
| `providers.smtp.primary-for-channel` | `APP_NOTIFICATION_PLATFORM_SMTP_PRIMARY` | `true` | boolean; exactly one primary per channel |
| `providers.smtp.environment` | `APP_NOTIFICATION_PLATFORM_SMTP_ENVIRONMENT` | `local` | required when enabled |
| `providers.smtp.credential-profile` | `APP_NOTIFICATION_PLATFORM_SMTP_CREDENTIAL_PROFILE` | `default` | resolved through `SecretMaterialProvider`, never inline material |
| `providers.smtp.timeout` | `APP_NOTIFICATION_PLATFORM_SMTP_TIMEOUT` | `10s` | positive, finite |
| `providers.smtp.max-concurrency` | `APP_NOTIFICATION_PLATFORM_SMTP_MAX_CONCURRENCY` | `4` | positive |
| `providers.smtp.rate-per-second` | `APP_NOTIFICATION_PLATFORM_SMTP_RATE_PER_SECOND` | `10` | positive |
| Property | Environment variable | Default | Bound |
|---|---|---|---|
| `smtp.tls-mode` | `APP_NOTIFICATION_PLATFORM_SMTP_TLS_MODE` | `STARTTLS_REQUIRED` | `STARTTLS_REQUIRED` or `IMPLICIT_TLS` |
| `smtp.sender-identity` | `APP_NOTIFICATION_PLATFORM_SMTP_SENDER_IDENTITY` | `no-reply@example.invalid` | address |
| `smtp.connect-timeout` | `APP_NOTIFICATION_PLATFORM_SMTP_CONNECT_TIMEOUT` | `5s` | positive, finite |
| `smtp.read-timeout` | `APP_NOTIFICATION_PLATFORM_SMTP_READ_TIMEOUT` | `10s` | positive, finite |
| `smtp.write-timeout` | `APP_NOTIFICATION_PLATFORM_SMTP_WRITE_TIMEOUT` | `10s` | positive, finite |
| `smtp.max-concurrency` | `APP_NOTIFICATION_PLATFORM_SMTP_DISPATCH_CONCURRENCY` | `4` | positive |
The TLS mode enum has no plaintext member. An unencrypted relay is refused by construction rather
than by a validator somebody has to remember to run.
The default sender is an RFC 2606 reserved domain that resolves nowhere, so a deployment that forgot
to set one produces a traceable bounce instead of mail apparently sent from an address it does not
own.
## Startup failures
Startup fails rather than degrading when:
@@ -91,6 +134,33 @@ All key material arrives through `SecretMaterialProvider`. Nothing is read from
committed file, or from a plaintext log. Contact point encryption and lookup HMAC keys must be
distinct, and the encryption key must be exactly 256 bits.
Eight purposes, eight keys. Each is base64 of at least 32 bytes and each must differ from every
other; the platform decodes them at startup and refuses to boot if one is blank, short or shared. A
blank value used to be skipped, which meant the platform started without the key and found out on
the first contact point — in production, on a recipient's notification.
Every default below is **unset**, deliberately. Supply the values out of band, per environment. Do
not write one into this table, into `application.yml`, into an `.env` file that is tracked, or into
any example: a value that appears in the repository is a value that has been disclosed.
| Purpose | Key material | Active key id |
|---|---|---|
| Contact point encryption | `APP_NOTIFICATION_PLATFORM_CONTACT_ENCRYPTION_KEY` | `APP_NOTIFICATION_PLATFORM_CONTACT_ENCRYPTION_KEY_ID` |
| Contact point lookup HMAC | `APP_NOTIFICATION_PLATFORM_CONTACT_LOOKUP_HMAC_KEY` | `APP_NOTIFICATION_PLATFORM_CONTACT_LOOKUP_HMAC_KEY_ID` |
| Callback signing | `APP_NOTIFICATION_PLATFORM_CALLBACK_SIGNING_KEY` | `APP_NOTIFICATION_PLATFORM_CALLBACK_SIGNING_KEY_ID` |
| Callback fingerprint HMAC | `APP_NOTIFICATION_PLATFORM_CALLBACK_FINGERPRINT_HMAC_KEY` | `APP_NOTIFICATION_PLATFORM_CALLBACK_FINGERPRINT_HMAC_KEY_ID` |
| Provider credential encryption | `APP_NOTIFICATION_PLATFORM_PROVIDER_CREDENTIAL_KEY` | `APP_NOTIFICATION_PLATFORM_PROVIDER_CREDENTIAL_KEY_ID` |
| Provider request lookup HMAC | `APP_NOTIFICATION_PLATFORM_PROVIDER_REQUEST_LOOKUP_HMAC_KEY` | `APP_NOTIFICATION_PLATFORM_PROVIDER_REQUEST_LOOKUP_HMAC_KEY_ID` |
| Payload encryption | `APP_NOTIFICATION_PLATFORM_PAYLOAD_ENCRYPTION_KEY` | `APP_NOTIFICATION_PLATFORM_PAYLOAD_ENCRYPTION_KEY_ID` |
| Web Push VAPID signing | `APP_NOTIFICATION_PLATFORM_VAPID_SIGNING_KEY` | `APP_NOTIFICATION_PLATFORM_VAPID_SIGNING_KEY_ID` |
A key id is not secret — an id identifies key material without revealing it — but it is required,
and it has no default on purpose. A constant id makes a rotation indistinguishable from the key it
replaced, so nothing could decrypt what was written before it. Change the id in the same deployment
that changes the material, and keep the superseded key readable under its old id until the data it
wrote has been re-encrypted. The rotation sequence is in
[at-rest-threat-model.md](at-rest-threat-model.md).
## Readiness
The platform contributes a `notifications` actuator endpoint and a health indicator. It reports DOWN