feat: web, websocket 어댑터 추가 구현

This commit is contained in:
DongHyeonka
2026-08-28 17:01:27 +09:00
parent 0137263441
commit a24ece9cf7
883 changed files with 100584 additions and 2623 deletions
@@ -0,0 +1,66 @@
name: web-advanced-nightly
# Every web Advanced capability is off in production unless a deployment names it, which means none
# of them is exercised by the ordinary PR gate. That is exactly why they need their own nightly: a
# capability nobody runs is a capability nobody notices breaking, and the first person to find out
# is whoever enabled it.
#
# The lane is tagged rather than module-scoped because Advanced lives in the same leaf as Stable.
on:
workflow_dispatch:
schedule:
# 03:30 UTC, after web-nightly. They contend for the same machine when streaming holds
# connections, and a load lane that shares a runner measures the runner.
- cron: '30 3 * * *'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
web-advanced-capabilities:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Run the Advanced capability lane
working-directory: src
run: >-
./gradlew
:adapter:inbound:web:webAdvancedTest
--no-daemon
--stacktrace
- name: Prove Stable behaviour is unchanged with every flag off
# The rollback assertion, run as its own step so a failure names itself. Two of the twelve
# capabilities change requests that do not use them, and this is what catches a third
# acquiring that property by accident.
working-directory: src
run: >-
./gradlew
:adapter:inbound:web:test
--tests '*WebAdvancedReleaseTest*'
--no-daemon
--stacktrace
- name: Publish the test reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
with:
name: web-advanced-nightly-reports
path: src/adapter/inbound/web/build/reports/tests/
if-no-files-found: warn
@@ -0,0 +1,75 @@
name: web-advanced-release
# Promotion evidence for the web Advanced capabilities.
#
# It depends on the Stable gate rather than replacing it: the condition every Advanced capability
# must satisfy is that Stable behaviour is unchanged with the feature off, and that is only
# meaningful against a Stable suite that passed in the same run.
on:
workflow_dispatch:
push:
tags:
- 'v*'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
web-advanced-promotion-evidence:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Establish the Stable baseline
working-directory: src
run: >-
./gradlew
:adapter:inbound:web:test
:adapter:inbound:web:webJettyCompatTest
:adapter:inbound:web:webFluxContractTest
--no-daemon
--stacktrace
- name: Run the Advanced capability lane
working-directory: src
run: >-
./gradlew
:adapter:inbound:web:webAdvancedTest
--no-daemon
--stacktrace
- name: Verify the architecture boundary Stable depends on
# WEB-ARCH-ADV. A feature flag decides whether an Advanced bean is created; it does nothing
# about a Stable class that imports an Advanced type, and one such edge makes the Stable
# platform unbuildable without the Advanced code.
working-directory: src
run: >-
./gradlew
:adapter:inbound:web:test
--tests '*WebArchitectureRulesTest*'
--tests '*WebModuleBoundaryTest*'
verifyCleanArchitectureDependencies
--no-daemon
--stacktrace
- name: Publish the promotion evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
with:
name: web-advanced-release-evidence
path: src/adapter/inbound/web/build/reports/tests/
if-no-files-found: warn
+55
View File
@@ -0,0 +1,55 @@
name: web-nightly
# The gates that are too slow for a pull request and too important to run only at release. Load,
# abuse and graceful shutdown all need a machine that is not simultaneously compiling something
# else, and all three measure behaviour that degrades gradually rather than breaking outright —
# which is exactly the kind of regression a per-PR gate never catches and a nightly one does.
on:
workflow_dispatch:
schedule:
# 03:00 UTC. Late enough that the day's merges are in, early enough that a failure is triaged
# before the next working day starts.
- cron: '0 3 * * *'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
web-load-abuse-and-shutdown:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Run the load, abuse and shutdown lanes on every container
working-directory: src
run: >-
./gradlew
:adapter:inbound:web:test
:adapter:inbound:web:webJettyCompatTest
:adapter:inbound:web:webFluxContractTest
--no-daemon
--stacktrace
- name: Publish the test reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
with:
name: web-nightly-reports
path: src/adapter/inbound/web/build/reports/tests/
if-no-files-found: warn
+114
View File
@@ -0,0 +1,114 @@
name: web-pr
# Every Stable claim the web platform makes is backed by a job here. The lanes are split by what
# they need rather than by what they test: the cross-container matrix needs three source sets, the
# proxy contract needs Docker, and the load gate needs a machine that is not also compiling. A
# single job running everything would attribute every failure to "the web tests".
on:
workflow_dispatch:
pull_request:
paths:
- 'src/adapter/inbound/web/**'
- 'src/application-core/src/**/operation/**'
- 'src/application-core/src/**/idempotency/**'
- 'src/adapter/outbound/persistence-jpa/src/**/operation/**'
- 'docs/web/**'
- '.github/workflows/web-pr.yml'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
web-unit-and-architecture:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Run the web unit, module-boundary and architecture suites
working-directory: src
run: >-
./gradlew
:adapter:inbound:web:test
:application-core:test
verifyCleanArchitectureDependencies
--no-daemon
--stacktrace
# The parity gate depends on all three recording lanes and fails when one is missing, so it runs
# them itself rather than trusting a previous job to have left the recordings behind.
web-cross-stack-parity:
runs-on: ubuntu-latest
timeout-minutes: 40
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Compare the wire contract across Tomcat, Jetty and Reactor Netty
working-directory: src
run: >-
./gradlew
:adapter:inbound:web:webCrossStackParityTest
--no-daemon
--stacktrace
- name: Publish the parity recordings
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
with:
name: web-contract-parity
path: src/adapter/inbound/web/build/web-contract-parity/
if-no-files-found: error
# Docker-gated, and the lane fails rather than skipping when the runtime is missing. A proxy
# contract that quietly passes without a proxy has been certifying nothing since whenever the
# container runtime last broke.
web-nginx-proxy-contract:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Run the proxy, prefix and spoofing contract behind a real Nginx
working-directory: src
run: >-
./gradlew
:adapter:inbound:web:webNginxProxyTest
--no-daemon
--stacktrace
+61
View File
@@ -0,0 +1,61 @@
name: web-release
# The complete Stable gate. Everything the PR and nightly workflows run, plus the checks whose cost
# is only justified when something is about to ship: the public API surface, the environment key
# registry and the whole architecture verification.
#
# It is one workflow rather than a reference to the others because a release gate that depends on
# another workflow having run is a gate whose result depends on scheduling.
on:
workflow_dispatch:
push:
tags:
- 'web-v*'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
web-stable-release-gate:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Run every web lane and the architecture-wide verification
working-directory: src
run: >-
./gradlew
:adapter:inbound:web:webCrossStackParityTest
:adapter:inbound:web:webNginxProxyTest
verifyCleanArchitectureDependencies
verifyPublicPathSnapshot
verifyEnvKeys
:app-bootstrap:test --tests '*CleanArchitectureTest'
--no-daemon
--stacktrace
- name: Publish the release evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
with:
name: web-release-evidence
path: |
src/adapter/inbound/web/build/web-contract-parity/
src/adapter/inbound/web/build/reports/tests/
if-no-files-found: error
@@ -0,0 +1,64 @@
name: websocket-advanced-nightly
# The WebSocket Advanced capabilities are off unless a deployment names them, so nothing a
# production deployment runs exercises them. A capability nobody runs is a capability nobody
# notices breaking, and the first person to find out is whoever enables it.
on:
workflow_dispatch:
schedule:
# 04:00 UTC, after the web lanes. Streaming and connection work contend for the same runner,
# and a load lane sharing one measures the runner.
- cron: '0 4 * * *'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
websocket-advanced-capabilities:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Run the Advanced capability lane
working-directory: src
run: >-
./gradlew
:adapter:inbound:websocket:websocketAdvancedTest
--no-daemon
--stacktrace
- name: Verify the boundary Stable depends on
# WS-ARCH-6. A flag decides whether an Advanced bean is created; it does nothing about a
# Stable class that imports an Advanced type, and one such edge makes Stable unbuildable
# without Advanced.
working-directory: src
run: >-
./gradlew
:adapter:inbound:websocket:test
--tests '*WebSocketArchitectureRulesTest*'
--tests '*WebSocketModuleBoundaryTest*'
--no-daemon
--stacktrace
- name: Publish the test reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
with:
name: websocket-advanced-nightly-reports
path: src/adapter/inbound/websocket/build/reports/tests/
if-no-files-found: warn
+98
View File
@@ -0,0 +1,98 @@
name: websocket-pr
# Every Stable claim the WebSocket platform makes is backed by a job here. The lanes are split by
# what they need: the runtime matrix needs two containers, and the proxy contract needs Docker.
on:
workflow_dispatch:
pull_request:
paths:
- 'src/adapter/inbound/websocket/**'
- 'docs/websocket/**'
- '.github/workflows/websocket-pr.yml'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
websocket-unit-and-architecture:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Run the websocket unit, boundary and runtime suites
working-directory: src
run: >-
./gradlew
:adapter:inbound:websocket:test
verifyCleanArchitectureDependencies
--no-daemon
--stacktrace
websocket-container-matrix:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Run the runtime contract on the second servlet container
working-directory: src
run: >-
./gradlew
:adapter:inbound:websocket:websocketJettyTest
--no-daemon
--stacktrace
# Docker-gated, and the lane fails rather than skipping. Upgrade handling is the single most
# common WebSocket deployment failure and it is invisible from either side alone.
websocket-nginx-contract:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Run the upgrade and forwarded-header contract behind a real Nginx
working-directory: src
run: >-
./gradlew
:adapter:inbound:websocket:websocketNginxTest
--no-daemon
--stacktrace
+56
View File
@@ -0,0 +1,56 @@
name: websocket-release
# The complete Stable gate: every lane plus the architecture-wide verification. One workflow rather
# than a reference to the others, because a release gate that depends on another workflow having run
# is a gate whose result depends on scheduling.
on:
workflow_dispatch:
push:
tags:
- 'websocket-v*'
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
websocket-stable-release-gate:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Run every websocket lane and the architecture-wide verification
working-directory: src
run: >-
./gradlew
:adapter:inbound:websocket:test
:adapter:inbound:websocket:websocketJettyTest
:adapter:inbound:websocket:websocketNginxTest
:adapter:inbound:websocket:websocketTransportQualificationTest
verifyCleanArchitectureDependencies
:app-bootstrap:test --tests '*CleanArchitectureTest'
--no-daemon
--stacktrace
- name: Publish the release evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4.6.2
with:
name: websocket-release-evidence
path: src/adapter/inbound/websocket/build/reports/tests/
if-no-files-found: error