Sub-scope 06 (inbound-web) pagination + cursor + conditional + cache + versioning revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5 generatedAt=2026-08-30T07:31:01+00:00 === OWNED FILES === b71cd1d422e6254261d678475e75b5185214c6d5 main/java/dev/caskeleton/adapter/inbound/web/cache/WebCacheHeaders.java 28 31b21401e6d221ea09f728a0cbea41e566b7b573 main/java/dev/caskeleton/adapter/inbound/web/cache/WebCachePolicy.java 72 b09aadbdbd52f95dd541cb14296bb3f7e42f9466 main/java/dev/caskeleton/adapter/inbound/web/cache/WebCachePolicyCatalog.java 120 19c1f5e8e3cf501cc8c8a631768daac6c7935671 main/java/dev/caskeleton/adapter/inbound/web/cache/WebVaryPolicy.java 90 a5bfb5ad0c192fa3e10e9f9086a5ab8a7b9277f0 main/java/dev/caskeleton/adapter/inbound/web/conditional/ConditionalReadDecision.java 17 de3d849582523c68911a6519b6ff856153d51b78 main/java/dev/caskeleton/adapter/inbound/web/conditional/ConditionalReadEvaluator.java 69 34312d5aad3943a3a93f1caec13056133a4e1183 main/java/dev/caskeleton/adapter/inbound/web/conditional/ETags.java 91 0b35c91c6612a73885efa9626939aa4a809dd24e main/java/dev/caskeleton/adapter/inbound/web/conditional/EntityTag.java 83 331e63451d5f308f8fdaaae7c5c17d258bc7cc02 main/java/dev/caskeleton/adapter/inbound/web/conditional/EntityTagCodec.java 72 519b207998c1af0eb4d0b8d75d1edefd2c86e3bf main/java/dev/caskeleton/adapter/inbound/web/conditional/HttpPrecondition.java 42 dfd6b5dad1fea00ad3cadcba94d7b400ea2b8638 main/java/dev/caskeleton/adapter/inbound/web/conditional/MutationPreconditionEvaluator.java 73 7fdf339fedad25745e191825922986e31af92812 main/java/dev/caskeleton/adapter/inbound/web/conditional/PreconditionDecision.java 21 5b3648d1744a9c736f2583482ce1d2a53829ee4a main/java/dev/caskeleton/adapter/inbound/web/conditional/PreconditionFailedException.java 15 4b41fd8f9c23a1f1a11f76148b7aa6a0f14dce59 main/java/dev/caskeleton/adapter/inbound/web/conditional/WebPreconditionFailedException.java 37 c18becee86c2e798c072811562b9c61b977fa647 main/java/dev/caskeleton/adapter/inbound/web/cursor/CursorCodec.java 100 2d5ccd57029f41382d5bdbdb2ebafd071b9f65bd main/java/dev/caskeleton/adapter/inbound/web/cursor/CursorException.java 15 8563a0c09368b898f8b445e83b6f1cbd22eea7c5 main/java/dev/caskeleton/adapter/inbound/web/pagination/FilterFieldCatalog.java 79 1c81d2396816f186b6076edac156ac2699e1a9d7 main/java/dev/caskeleton/adapter/inbound/web/pagination/FilterFingerprint.java 70 6db733f43e6d5c4d60ff2c817cadc450a7a71766 main/java/dev/caskeleton/adapter/inbound/web/pagination/FilterOperatorCatalog.java 87 27eae3065306329009718da083bd540d523b758f main/java/dev/caskeleton/adapter/inbound/web/pagination/HmacWebCursorCodec.java 135 697cf50ac78f44a30e24df14cb0be66659a10971 main/java/dev/caskeleton/adapter/inbound/web/pagination/InMemoryCollectionQueryCatalog.java 93 95e0fbace4444ef787309c8cbdc16e74c22d5638 main/java/dev/caskeleton/adapter/inbound/web/pagination/PageParams.java 46 a0dad859e1433f17004f0bce2767410742c14ca3 main/java/dev/caskeleton/adapter/inbound/web/pagination/PageValidationException.java 29 cd71b9d3acb102ce9278bd74b59dd23088a5f70f main/java/dev/caskeleton/adapter/inbound/web/pagination/ProjectionProfileCatalog.java 55 3e56c25c0fa268cf2e59f157220af5628d58d22b main/java/dev/caskeleton/adapter/inbound/web/pagination/SortField.java 29 bb559644e11d9a0a7917725e51e0079d835bc0d7 main/java/dev/caskeleton/adapter/inbound/web/pagination/SortFieldCatalog.java 66 90149f3df64b5e6b812f39fd21b7e8ec875fa2a9 main/java/dev/caskeleton/adapter/inbound/web/pagination/SortParam.java 56 34d281c8750d7be823b9f0254335622c1cef1cd5 main/java/dev/caskeleton/adapter/inbound/web/pagination/UnsupportedQueryVocabularyException.java 30 b220a509ac2e5e2bf612e71af057502b85f0c680 main/java/dev/caskeleton/adapter/inbound/web/pagination/WebCollectionRequest.java 55 2d1bf71b8405769cf0d74440fea11c04b4f920ae main/java/dev/caskeleton/adapter/inbound/web/pagination/WebCollectionRequestParser.java 135 8bfea2f9fba90a1c33eee3d03a1cb5333371860c main/java/dev/caskeleton/adapter/inbound/web/pagination/WebCursorCodec.java 29 43e9006f24cdebdf033b00394c3458e2973cbd4f main/java/dev/caskeleton/adapter/inbound/web/pagination/WebCursorException.java 19 afb1197b753691f132465d1844a72cfe71b7af6b main/java/dev/caskeleton/adapter/inbound/web/pagination/WebCursorKeyRing.java 77 0344224965ee1f264bb7a2429bb9a8a0c6833fb8 main/java/dev/caskeleton/adapter/inbound/web/pagination/WebCursorPayload.java 79 46c0f5b97088991aa79a12c2607615b42aeefbbd main/java/dev/caskeleton/adapter/inbound/web/pagination/WebPageSizePolicy.java 61 4a54c62d1f0acd30cebb1f33cfaa14471cb744e2 main/java/dev/caskeleton/adapter/inbound/web/versioning/ApiDeprecationPolicy.java 60 db1397377b3cf9dd6699739f64bd9f490e43f6f1 main/java/dev/caskeleton/adapter/inbound/web/versioning/ApiVersionCatalog.java 66 161119cf579a5986eca24b7719c6f77ed0e24ac4 main/java/dev/caskeleton/adapter/inbound/web/versioning/DeprecatedRoute.java 51 2a99176fdffcb03047da758c875e910e8860b5fb main/java/dev/caskeleton/adapter/inbound/web/versioning/DeprecationHeaderWriter.java 70 b442226238e93850b2b2b4e02e2b2c9079fc389d main/java/dev/caskeleton/adapter/inbound/web/versioning/PathApiVersionResolver.java 60 582bc80f24caeeb01699e95ce3859844ff8e2009 main/java/dev/caskeleton/adapter/inbound/web/versioning/SunsetViolationException.java 18 9dd5f14e03bc8c14c946bc66ee55a8ab8bc0b78d main/java/dev/caskeleton/adapter/inbound/web/versioning/UnsupportedApiVersionException.java 39 26adae975785892947b14a2b090828c183bc45ba test/java/dev/caskeleton/adapter/inbound/web/cache/WebCachePolicyTest.java 143 3063ff9bf667b0596c85a97c8fedbf2dfc68ae41 test/java/dev/caskeleton/adapter/inbound/web/conditional/ConditionalReadEvaluatorTest.java 88 ca4ee2a080a1c9b4ba13ca8c47ec82b887a11548 test/java/dev/caskeleton/adapter/inbound/web/conditional/ETagsTest.java 54 3449f0cf36ff50de2b6ed038f0867ac57aa173fb test/java/dev/caskeleton/adapter/inbound/web/conditional/MutationPreconditionEvaluatorTest.java 99 17818d69f9261bc514adeaa3d27c66ba40a1a81a test/java/dev/caskeleton/adapter/inbound/web/cursor/CursorCodecTest.java 57 af94a22156b65312fea18626299972f384d76f35 test/java/dev/caskeleton/adapter/inbound/web/pagination/CollectionQueryCatalogTest.java 137 22fc35f5e84c63304d849452911fd4a3c999c3e4 test/java/dev/caskeleton/adapter/inbound/web/pagination/HmacWebCursorCodecTest.java 170 e35baf04168287731a6e3a22a6865d9cc5260941 test/java/dev/caskeleton/adapter/inbound/web/pagination/PageParamsTest.java 67 355be4b68c4037d8bae2977bbac44613c6d537d4 test/java/dev/caskeleton/adapter/inbound/web/pagination/SortParamTest.java 51 194055a24da33f232cb574de64e3d5b9fda50763 test/java/dev/caskeleton/adapter/inbound/web/pagination/WebCollectionRequestParserTest.java 156 c9b51117a9f8e308717cc9155d942e2513d663d2 test/java/dev/caskeleton/adapter/inbound/web/versioning/DeprecationHeaderWriterTest.java 107 9fe7484c631e85db319564b63cd1eb0b3d991ea4 test/java/dev/caskeleton/adapter/inbound/web/versioning/PathApiVersionResolverTest.java 82 file_count=54 === 8.1 도달성: 라이브러리 타입의 소비자 === HmacWebCursorCodec 5 test CursorCodec 2 test WebCollectionRequestParser 1 test ConditionalReadEvaluator 1 test MutationPreconditionEvaluator 1 test ETags 3 sample 12 test WebCachePolicyCatalog 10 test WebVaryPolicy 4 main 8 test ApiVersionCatalog 4 test DeprecationHeaderWriter 1 test PathApiVersionResolver 2 test WebPageSizePolicy 1 test === 8.2 조건 형제 비교: 두 개의 캐시 정책 메커니즘 === $ cat adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/filter/CacheControlFilter.java | grep -vE '^import|^package' /** * Applies the skeleton's default HTTP cache policy: every response gets {@code Cache-Control: * no-store} and a {@code Vary: Accept, Accept-Encoding, Authorization} header. Defaults are set * before the chain so a cacheable endpoint can opt in by returning a {@code ResponseEntity} whose * {@code Cache-Control} header overwrites the default. See README for the design rationale. */ @Component @Order(Ordered.HIGHEST_PRECEDENCE + 20) public class CacheControlFilter extends OncePerRequestFilter { static final String DEFAULT_CACHE_CONTROL = "no-store"; static final String DEFAULT_VARY = "Accept, Accept-Encoding, Authorization"; @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { response.setHeader(ApiHeaders.CACHE_CONTROL, DEFAULT_CACHE_CONTROL); response.setHeader(ApiHeaders.VARY, DEFAULT_VARY); chain.doFilter(request, response); } } exit=0 $ grep -rn 'web\.cache\.' --include=*.java adapter/inbound/web/src app-bootstrap/src sample-portfolio/src | grep -v '/cache/' | wc -l 0 exit=0 === 8.3 no-store 와 조건부 읽기의 충돌 === $ grep -n 'IF_NONE_MATCH\|NOT_MODIFIED\|eTag(' sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/adapter/inbound/web/controller/WorkLogController.java 141: @RequestHeader(value = ApiHeaders.IF_NONE_MATCH, required = false) String ifNoneMatch) { 145: return ResponseEntity.status(HttpStatus.NOT_MODIFIED).eTag(etag).build(); 147: return ResponseEntity.ok().eTag(etag).body(WorkLogWebMapper.toResponse(workLog)); 159: return ResponseEntity.ok().eTag(etagOf(created)).body(WorkLogWebMapper.toResponse(created)); 174: return ResponseEntity.ok().eTag(etagOf(updated)).body(WorkLogWebMapper.toResponse(updated)); exit=0 $ grep -rn 'CacheControl\|CACHE_CONTROL' --include=*.java sample-portfolio/src/main | wc -l 0 exit=0 === 8.4 예외 타입의 throw 지점과 handler === PageValidationException main throw sites=10 CursorException main throw sites=7 UnsupportedQueryVocabularyException main throw sites=6 UnsupportedApiVersionException main throw sites=0 SunsetViolationException main throw sites=3 WebCursorException main throw sites=10 $ grep -n 'ExceptionHandler(PageValidationException\|ExceptionHandler(CursorException\|ExceptionHandler(PreconditionFailedException' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/GlobalExceptionHandler.java 157: @ExceptionHandler(PreconditionFailedException.class) 169: @ExceptionHandler(PageValidationException.class) 180: @ExceptionHandler(CursorException.class) exit=0