feat: redis, fileserver, httpclient 런타임 시점 구현 추가

This commit is contained in:
donghyeon-ka
2026-07-28 14:26:54 +09:00
parent 7363b2aa1e
commit b3add0162d
257 changed files with 30430 additions and 1357 deletions
+2 -2
View File
@@ -4,9 +4,9 @@
- Module ID: `adapter-inbound-graphql`
- Gradle path: `:adapter:inbound:graphql`
- Focused test: `./gradlew :adapter:inbound:graphql:test --console=plain`
- Focused test (derived from Gradle path): `./gradlew :adapter:inbound:graphql:test --console=plain`
- Runtime baseline: Java 21; repository framework baseline: Spring Boot 4.0.0.
- Registry SSOT: `.harness/project/modules.yaml`.
- Registry SSOT: `src/config/architecture/modules.json`.
Package root: `dev.caskeleton.adapter.inbound.graphql`.
-5
View File
@@ -11,15 +11,10 @@
description = 'Inbound adapter: GraphQL API (Spring for GraphQL, skeleton machinery)'
dependencies {
implementation project(':application-core')
implementation project(':domain-core')
implementation project(':shared-contract')
implementation 'org.springframework.boot:spring-boot-starter-graphql'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
// GraphQlTester (spring-graphql-test, BOM-managed) — the health test assembles the schema +
// controller through a real AnnotatedControllerConfigurer and drives it with an
@@ -5,10 +5,6 @@ biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,testCompileClasspa
ch.qos.logback:logback-classic:1.5.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
ch.qos.logback:logback-core:1.5.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.20=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.20.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.20.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson:jackson-bom:2.20.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.ben-manes.caffeine:caffeine:3.2.3=annotationProcessor,testAnnotationProcessor
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,testAnnotationProcessor
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
@@ -121,7 +117,6 @@ org.slf4j:jul-to-slf4j:2.0.17=compileClasspath,runtimeClasspath,testCompileClass
org.slf4j:slf4j-api:2.0.17=compileClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-simple:2.0.17=checkstyle,spotbugsSlf4j
org.springframework.boot:spring-boot-autoconfigure:4.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-configuration-processor:4.0.0=annotationProcessor
org.springframework.boot:spring-boot-graphql-test:4.0.0=testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-graphql:4.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-http-client:4.0.0=testCompileClasspath,testRuntimeClasspath
+2 -2
View File
@@ -4,9 +4,9 @@
- Module ID: `adapter-inbound-grpc`
- Gradle path: `:adapter:inbound:grpc`
- Focused test: `./gradlew :adapter:inbound:grpc:test --console=plain`
- Focused test (derived from Gradle path): `./gradlew :adapter:inbound:grpc:test --console=plain`
- Runtime baseline: Java 21; repository framework baseline: Spring Boot 4.0.0.
- Registry SSOT: `.harness/project/modules.yaml`.
- Registry SSOT: `src/config/architecture/modules.json`.
Package root: `dev.caskeleton.adapter.inbound.grpc`.
+4 -8
View File
@@ -17,20 +17,16 @@ dependencyManagement {
}
dependencies {
implementation project(':application-core')
implementation project(':domain-core')
implementation project(':shared-contract')
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'io.grpc:grpc-netty-shaded'
implementation 'io.grpc:grpc-protobuf'
implementation 'io.grpc:grpc-stub'
implementation 'io.grpc:grpc-services' // health + reflection (grpc.health.v1 / reflection)
// grpc-java generated stubs reference javax.annotation.Generated; kept compileOnly for parity
// with the feature module (the skeleton itself generates no stubs).
compileOnly 'org.apache.tomcat:annotations-api:6.0.53'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
// The boot test directly builds generated health/reflection protobuf messages. grpc-services
// does not expose protobuf-java on its compile API, so keep the narrower test-only declaration.
testImplementation 'io.grpc:grpc-protobuf'
}
+3 -4
View File
@@ -12,7 +12,7 @@ com.github.spotbugs:spotbugs-annotations:4.8.6=compileClasspath,testCompileClass
com.github.spotbugs:spotbugs:4.10.2=spotbugs
com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs
com.google.android:annotations:4.1.1.4=runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-common-protos:2.41.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-common-protos:2.41.0=runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,testAnnotationProcessor
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,testAnnotationProcessor
com.google.auto:auto-common:1.2.2=annotationProcessor,testAnnotationProcessor
@@ -37,7 +37,7 @@ com.google.j2objc:j2objc-annotations:2.8=runtimeClasspath,testRuntimeClasspath
com.google.j2objc:j2objc-annotations:3.0.0=compileClasspath,testCompileClasspath
com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,testAnnotationProcessor
com.google.protobuf:protobuf-java-util:3.25.5=runtimeClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:3.25.5=annotationProcessor,compileClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:3.25.5=annotationProcessor,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins
com.jayway.jsonpath:json-path:2.9.0=testCompileClasspath,testRuntimeClasspath
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
@@ -54,7 +54,7 @@ io.grpc:grpc-context:1.68.1=runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-core:1.68.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-netty-shaded:1.68.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf-lite:1.68.1=runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf:1.68.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf:1.68.1=runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-services:1.68.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-stub:1.68.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-util:1.68.1=runtimeClasspath,testRuntimeClasspath
@@ -88,7 +88,6 @@ org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle
org.apache.tomcat.embed:tomcat-embed-core:11.0.14=testCompileClasspath,testRuntimeClasspath
org.apache.tomcat.embed:tomcat-embed-el:11.0.14=testCompileClasspath,testRuntimeClasspath
org.apache.tomcat.embed:tomcat-embed-websocket:11.0.14=testCompileClasspath,testRuntimeClasspath
org.apache.tomcat:annotations-api:6.0.53=compileClasspath
org.apache.xbean:xbean-reflect:3.7=checkstyle
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
org.assertj:assertj-core:3.27.6=testCompileClasspath,testRuntimeClasspath
+5 -2
View File
@@ -4,9 +4,9 @@
- Module ID: `adapter-inbound-web`
- Gradle path: `:adapter:inbound:web`
- Focused test: `./gradlew :adapter:inbound:web:test --console=plain`
- Focused test (derived from Gradle path): `./gradlew :adapter:inbound:web:test --console=plain`
- Runtime baseline: Java 21; repository framework baseline: Spring Boot 4.0.0.
- Registry SSOT: `.harness/project/modules.yaml`.
- Registry SSOT: `src/config/architecture/modules.json`.
Package root: `dev.caskeleton.adapter.inbound.web`.
@@ -18,6 +18,9 @@ Package root: `dev.caskeleton.adapter.inbound.web`.
- Request/response DTOs.
- Request DTO to application command mapping.
- Authentication, validation, error mapping, filters, and web/security settings.
- Sanitized request correlation context exposed through application-owned `CorrelationIdPort`.
- Transport-owned OpenAPI customization that keeps `ApiError.details` as `type: object` without
leaking Swagger dependencies into `shared-contract`.
## Allowed
+16
View File
@@ -10,6 +10,16 @@
---
## OpenAPI contract stabilization
Springdoc 3 represents an untyped Java `Object` as an unconstrained OAS 3.1 schema (`{}`).
`OpenApiContractConfig` owns the transport-specific correction for the shared `ApiError.details`
field and publishes it as `type: object`. This preserves the committed HTTP contract without adding
Swagger annotations or dependencies to `shared-contract`. Real-server OpenAPI tests import this
production configuration and compare the result with the committed snapshot.
---
## auth — 인증 (OIDC resource server)
### SecurityConfig
@@ -323,6 +333,12 @@
같은 논리 ID 의 envelope 형태(camelCase)와 HTTP 헤더 형태(kebab-case)는 D19 projection 이며,
변환 단일 지점은 `ResponseMetaFactory`.
### MdcCorrelationIdPortAdapter
- `RequestLoggingFilter`가 무해화하고 MDC `correlation_id`에 넣은 값을 application-core의
`CorrelationIdPort`로 투영한다.
- absent/blank는 `Optional.empty()`로 반환한다. application/sample 계층은 SLF4J/MDC를 직접
참조하지 않고 event-id fallback 정책만 소유한다.
### HeaderSanitizer
- 인바운드 헤더 값을 MDC/로그 도달 전에 무해화(D14, OWASP-LOG-C3/C5, CWE-117). 스켈레톤은 구조화 JSON 로깅을
가정하므로 위협은 CR/LF/제어문자를 통한 로그 라인 위조 — 값은 보존하되 `\r`/`\n`/ASCII 제어문자(`< 0x20`)를
+6 -4
View File
@@ -1,16 +1,18 @@
// HTTP / web adapters. Depends on application, domain, and shared operational contracts.
// HTTP / web adapters. Depends on application and shared operational contracts.
dependencies {
implementation project(':domain-core')
implementation project(':application-core')
implementation project(':shared-contract')
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
implementation('org.openapitools:jackson-databind-nullable:0.2.6') {
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
}
// feature-api-contract-baseline D10: OpenAPI producer. springdoc exposes the
// running app's machine-readable contract at /v3/api-docs (OAS 3.1, generated —
// never a hand-maintained stale schema). The release-blocking drift gate is
// owned by feature-contract-verification-test-suite (planned).
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.6'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0'
}
+6 -5
View File
@@ -50,9 +50,9 @@ io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,testAnnota
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,testAnnotationProcessor
io.micrometer:micrometer-commons:1.16.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.micrometer:micrometer-observation:1.16.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.swagger.core.v3:swagger-annotations-jakarta:2.2.29=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.swagger.core.v3:swagger-core-jakarta:2.2.29=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.swagger.core.v3:swagger-models-jakarta:2.2.29=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.swagger.core.v3:swagger-core-jakarta:2.2.38=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.swagger.core.v3:swagger-models-jakarta:2.2.38=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.activation:jakarta.activation-api:2.1.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -125,9 +125,10 @@ org.skyscreamer:jsonassert:1.5.3=testCompileClasspath,testRuntimeClasspath
org.slf4j:jul-to-slf4j:2.0.17=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.17=compileClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-simple:2.0.17=checkstyle,spotbugsSlf4j
org.springdoc:springdoc-openapi-starter-common:2.8.6=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.6=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springdoc:springdoc-openapi-starter-common:3.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-autoconfigure:4.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-configuration-processor:4.0.0=annotationProcessor
org.springframework.boot:spring-boot-http-client:4.0.0=testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-http-converter:4.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-jackson:4.0.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -0,0 +1,34 @@
package dev.caskeleton.adapter.inbound.web.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.media.ObjectSchema;
import io.swagger.v3.oas.models.media.Schema;
import java.util.Map;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Keeps transport-owned OpenAPI schema details stable across springdoc library upgrades.
*
* <p>{@code ApiError.details} is represented by {@code Object} in the shared response contract.
* Springdoc 3 renders an untyped Java {@code Object} as an unconstrained OAS 3.1 schema. The public
* HTTP contract remains object-shaped, so the web adapter restores that transport-specific type
* without adding Swagger dependencies or annotations to {@code shared-contract}.
*/
@Configuration(proxyBeanMethods = false)
public class OpenApiContractConfig {
@Bean
OpenApiCustomizer apiErrorDetailsObjectSchemaCustomizer() {
return openApi -> {
Components components = openApi.getComponents();
Map<String, Schema> schemas = components == null ? null : components.getSchemas();
Schema<?> apiError = schemas == null ? null : schemas.get("ApiError");
Map<String, Schema> properties = apiError == null ? null : apiError.getProperties();
if (properties != null && properties.containsKey("details")) {
properties.put("details", new ObjectSchema());
}
};
}
}
@@ -0,0 +1,16 @@
package dev.caskeleton.adapter.inbound.web.observability;
import dev.caskeleton.application.observability.CorrelationIdPort;
import java.util.Optional;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
/** Reads the current request's sanitized correlation identifier from the inbound web MDC. */
@Component
public class MdcCorrelationIdPortAdapter implements CorrelationIdPort {
@Override
public Optional<String> currentCorrelationId() {
return Optional.ofNullable(MDC.get(MdcKeys.CORRELATION_ID)).filter(value -> !value.isBlank());
}
}
@@ -0,0 +1,40 @@
package dev.caskeleton.adapter.inbound.web.config;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.openapitools.jackson.nullable.JsonNullable;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
class JacksonNullableConfigTest {
private final ObjectMapper mapper =
JsonMapper.builder().addModule(new JacksonNullableConfig().jsonNullableModule()).build();
@Test
void readsPresentValue() throws Exception {
Payload payload = mapper.readValue("{\"value\":\"configured\"}", Payload.class);
assertThat(payload.value().isPresent()).isTrue();
assertThat(payload.value().get()).isEqualTo("configured");
}
@Test
void readsExplicitNullAsPresentNull() throws Exception {
Payload payload = mapper.readValue("{\"value\":null}", Payload.class);
assertThat(payload.value().isPresent()).isTrue();
assertThat(payload.value().get()).isNull();
}
@Test
void readsMissingPropertyAsUndefined() throws Exception {
Payload payload = mapper.readValue("{}", Payload.class);
assertThat(payload.value()).isNotNull();
assertThat(payload.value().isPresent()).isFalse();
}
private record Payload(JsonNullable<String> value) {}
}
@@ -0,0 +1,36 @@
package dev.caskeleton.adapter.inbound.web.observability;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.slf4j.MDC;
class MdcCorrelationIdPortAdapterTest {
private final MdcCorrelationIdPortAdapter adapter = new MdcCorrelationIdPortAdapter();
@AfterEach
void clearMdc() {
MDC.clear();
}
@Test
void readsSanitizedRequestCorrelationId() {
MDC.put(MdcKeys.CORRELATION_ID, "corr-123");
assertThat(adapter.currentCorrelationId()).contains("corr-123");
}
@Test
void treatsBlankMdcValueAsAbsent() {
MDC.put(MdcKeys.CORRELATION_ID, " ");
assertThat(adapter.currentCorrelationId()).isEmpty();
}
@Test
void reportsAbsenceWhenRequestContextIsMissing() {
assertThat(adapter.currentCorrelationId()).isEmpty();
}
}
+2 -2
View File
@@ -4,9 +4,9 @@
- Module ID: `adapter-inbound-websocket`
- Gradle path: `:adapter:inbound:websocket`
- Focused test: `./gradlew :adapter:inbound:websocket:test --console=plain`
- Focused test (derived from Gradle path): `./gradlew :adapter:inbound:websocket:test --console=plain`
- Runtime baseline: Java 21; repository framework baseline: Spring Boot 4.0.0.
- Registry SSOT: `.harness/project/modules.yaml`.
- Registry SSOT: `src/config/architecture/modules.json`.
Package root: `dev.caskeleton.adapter.inbound.websocket`.
@@ -11,13 +11,9 @@
description = 'Inbound adapter: WebSocket (STOMP over SockJS, skeleton machinery)'
dependencies {
implementation project(':application-core')
implementation project(':domain-core')
implementation project(':shared-contract')
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}
@@ -5,10 +5,6 @@ biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,testCompileClasspa
ch.qos.logback:logback-classic:1.5.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
ch.qos.logback:logback-core:1.5.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.20=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.20.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.20.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson:jackson-bom:2.20.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.ben-manes.caffeine:caffeine:3.2.3=annotationProcessor,testAnnotationProcessor
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,testAnnotationProcessor
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs