RPC 전송과 도메인 Repository 추출 경계 — A01
- 대상: #1288, v0.18.0 / Phase 1 Step 4 / A01
- 기준: main
16b8e99684a4cb152b2ff5d89b5aea10a7f055e0, G03CONTRACTS_CORE_VERSION = 1 - 계약: G03 §16–18, 계층 허용표, 현재 화면 RPC, 쓰기 원천
- 후속: 총괄 A01 카드, A02–A08, A15/A16, B01, 최종 R01
1. 현재 연결
barbelicApi.ts의 기존 함수 이름·입력·반환은 유지한다. domains/servicePorts.ts가 G03의 feature port 17개를 실제 함수로 조립하며 satisfies로 계약을 검사한다. G03 본문에 적힌 16개와 달리 ports.fixture.ts의 실제 객체는 17개다. 반환을 unknown으로 넓혀 기존 소비자를 깨뜨리지 않고, 현재 반환 추론은 그대로 유지한다.
서비스는 domains/repositoryPorts.ts에서 자기 영역의 메서드만 받는다. 이 파일은 기존 Repository의 명시적인 임시 어댑터다. 다른 도메인의 전체 namespace를 서비스에 넘기지 않는다. 새 실제 구현은 domains/repositories/*Repository.ts에 있고, 기존 Repository의 composition root가 필요한 helper만 주입한다. 같은 함수의 복사본은 두지 않는다.
예: 공개 followUser → servicePorts.socialWrites → socialDomain → repositoryPorts.socialRepositoryPort → 조립된 createSocialRepository의 followUser → mutation adapter → executeRpc → 인증이 연결된 client. port 연결은 함수 참조를 보존하며 별도의 재시도·캐시·타이머를 만들지 않는다.
2. 전송 계약과 책임
rpcTransport.ts의 createRpcTransport(client).callRpc(name, args, options)가 G03 TransportPort 구현이다. RPC 이름과 인자는 기존 SupabaseRpcName/SupabaseRpcArgsMap의 조합이고 성공 결과는 unknown이다. 응답 codec은 호출자가 소유한다.
| 책임 | 구현 위치 | 보존하는 의미 |
|---|---|---|
| 인증 전달 | supabaseAuth가 만든 client / authSessionGuard의 guarded fetch | 동일 client 사용. 토큰을 읽어 별도로 저장하거나 임의 재로그인하지 않음. 로그인 준비 오류의 사라진 code만 같은 오류 객체에 복원 |
| 실제 요청 한 번 | executeRpc | 요청 인자·mutation ID·hash·source를 재생성하지 않음. 전송 재시도 0회 |
| timeout | TransportCallOptions.timeoutMs | 양의 유한 값이 주어진 경우만 적용. 생략 시 기존 상위 timeout 정책 유지. RpcTimeoutError/ETIMEDOUT/408은 기존 쓰기 분류에서 retryable |
| 취소 | caller signal → executeRpc → SDK abortSignal | 시작 전 취소는 요청 0회. 진행 중 취소는 AbortError. SDK가 취소를 지원하지 않아도 늦은 완료·거부가 결과를 바꾸지 않음. timer/listener 정리 |
| 전송 진단 | 선택 observe callback | RPC 이름·단계·소요시간만. payload/토큰을 전달하지 않음. 진단 callback 실패가 요청 결과를 바꾸지 않음 |
| 기존 쓰기 오류 보고 | rpcMutationAdapter.ts | 전체 응답·error·status의 참조 보존. report:false와 severity 유지. 재시도·disposition 판정 없음 |
| 화면 응답 검증·기기 사본·부팅 캐시 | 기존 barbelicRepository.callScreenRpc의 임시 query adapter | 같은 strict codec, 원래 계정의 사본 키, 기존 사본 fallback 정책. transport 내부에 캐시/통계 정책 없음 |
| durable 재시도·hold/block·영수증 적용 | dispatcher / pendingWorkoutMutationFlush, directWorkoutWrite | 기존 요청 identity·충돌·영수증 정책 유지. transport가 재시도를 중첩하지 않음 |
| 기존 특수 재시도 | Repository의 retryTransientFetch, stats refresh 정책 | A01에서 삭제/이동하지 않음. A02/A06/S05–S06에서 dispatcher와 함께 정리할 대상. 새로운 retry 추가 없음 |
executeRpc는 전송 Promise의 envelope를 변형하지 않는 primitive다. TransportPort.callRpc는 그 결과의 error를 던지고 data를 반환한다. 기존 mutation 소비자는 envelope가 필요하므로 rpcMutationAdapter를 통해 같은 primitive를 사용한다. mutation adapter는 예전처럼 SDK가 반환한 error envelope를 보고하며, SDK Promise 자체의 reject는 그대로 전파한다.
v5 실패 시 v4나 raw table로 돌아가는 경로를 추가하지 않았다. timeout/취소는 서버의 commit 취소 증거가 아니다. dispatcher는 같은 identity로 기존 영수증·재시도 절차를 따른다.
3. 도메인 목적지와 추출 순서
아래 경로는 모두 src/react/services/domains/repositories/ 아래다. A01에서 실제 옮긴 함수와 A02–A06의 남은 범위를 구분한다.
| 후속 담당 / 파일 | A01에서 실제 이전한 함수 | 다음 추출 |
|---|---|---|
A02 / workoutRepository.ts | loadManualRecordsRows | A02 완료(2026-09-07, #1329) — 아래 3-1 참조. 남은 것: 재수출 제거(A01/R01)·codec 기존 any 제거(A16)·processCurrentUserStatsRefresh 이식(A06) |
A02 / planRepository.ts | loadPlannedSessionDetailRows | A02 완료 — 아래 3-1 참조. 남은 것: 재수출 제거(A01/R01)·1회 재시도 주입 제거(S05) |
A03 / catalogRepository.ts | setOwnCustomExerciseActive | 완료(2026-09-07, #1330) — 조회 2·커스텀 3·관리자 카탈로그 5·표기 5·별칭/세부 6·외부 매핑 2 = 23개 이전, 검증·변환·해석은 domains/catalogCodec.ts. 옛 Repository 의 catalog 본문 0 |
A03 / profileRepository.ts | loadProfileWorkspace | 완료(2026-09-07, #1330) — 현재 프로필·workspace·온보딩 조회/완료·신체 지표·이름·사진·InBody 접점 8개 이전, codec 은 domains/profileCodec.ts, 서명 helper 는 domains/profileAvatarSigning.ts, 공통 입력 검사기는 services/currentInputContract.ts(인입·운동 기록 쓰기와 공유) |
A04 / socialRepository.ts | followUser, unfollowUser | following 화면 조회·댓글·차단/신고, lifecycle 취소 의미 유지 |
A04 / groupRepository.ts | respondGroupInvite | group 조회·보드 저장·댓글/채팅, group/source identity 유지 |
A05 / importRepository.ts | importMotraWorkouts | 완료(2026-09-09, #1406) — Wodup 업로드·시작·조회 + Motra 기입 4개 이전, codec 은 domains/codecs/wodupImportCodec.ts(옛 services/wodupImportBatchAdapter.ts 이동 + 엣지 시작 응답 decoder + 잡 단계 변환표)·motraImportCodec.ts. 옛 Repository 타입 참조 0. 아래 3-3 참조 |
A05 / adminRepository.ts | loadAdminContentReports | 완료(2026-09-09, #1406) — 신고 3·유저 검색·운영 점검표(엣지 배포 확인 포함) 이전, codec 은 domains/codecs/adminModerationCodec.ts. 조립은 domains/adminComposition.ts(관리자 셸 그래프 전용), 파사드는 services/adminApi.ts. 본인 데이터 내보내기는 domains/userDataExportDomain.ts 로 분리(일반 앱). 아래 3-3 참조 |
A06 / statsRepository.ts | loadCalendarDaySummaryRows | 나머지 화면 RPC query, strict codec, screen query adapter |
각 factory의 dependencies 타입은 자기 함수가 쓰는 RPC literal과 최소 helper만 요구한다. 후속 담당은 자신의 파일에서 실제 구현·codec을 옮기고 factory 반환에 추가한다. 마지막 공개 export와 legacy helper 제거 패치는 A01 통합 담당에게 넘긴다. 공유 Repository/controller를 각 작업자가 동시에 수정하는 방식으로 진행하지 않는다.
3-1. A02 이전 결과 — workout·plan repository와 codec (2026-09-07, #1329)
서버 응답(RPC 행·영수증)을 앱 모양으로 바꾸거나 앱 입력을 서버 문 모양으로 바꾸는 자리는 src/react/services/domains/codecs/ 아래 파일뿐이다. 거대 barbelicRepository.ts·barbelicMappers.ts는 본문 없이 이름만 재수출한다(기존 테스트·repositoryPorts가 namespace 로 부르므로). 기록: A02 작업 기록.
| 파일 | 소유하는 것 | 출처 |
|---|---|---|
repositories/workoutRepository.ts | createCompletedSession·updateCompletedSession·deleteCompletedSession, loadSessionDetailRows, loadManualRecordsRows, 직접 입력 4종(saveManualPr·deleteManualPr·saveManualRecordMetric·deleteManualRecordMetric), 계획→완료 전이 대상 조회 | Repository |
repositories/planRepository.ts | loadPlannedSessionDetailRows, savePlan, deletePlannedSession | Repository |
codecs/completedWorkoutWriteCodec.ts | 초안→행(buildWorkoutRowsFromDraft·completedSessionRowsFromSession), 행→서버 payload(encodeCompletedSession{Create,Update,Delete}·completedSessionWirePayload·validateCompletedWorkoutPayload), WORKOUT_WRITE_LIMITS | Mappers + Repository |
codecs/workoutReceiptCodec.ts | 영수증 v1/v2 검증(validateWorkoutMutationReceipt·children·set_scores), CompletedWorkoutRpcError·completedWorkoutRpcFailure. workoutMutationContractError 는 A03 정본 completedWorkoutWriteContract.ts 를 재수출 | Repository |
codecs/planWriteCodec.ts | 계획 입력 검사·payload 조립(encodePlanSave·encodePlannedSessionDelete·planSessionWirePayload) | Repository |
codecs/manualRecordWriteCodec.ts | 직접 입력 4종의 인자 검사·인코딩. 공통 입력 검사기 4개는 A03 정본 services/currentInputContract.ts 를 쓴다(A02 사본은 main 합류 때 제거) | Repository |
codecs/sessionDetailCodec.ts | 완료 기록 상세·요약(buildCompletedSession·buildCompletedSessionSummary) — 결과는 G03 WritableCopy(writeSource detail), 쓰기 자격 가드 isWritableCompletedSessionCopy | Mappers |
codecs/planDetailCodec.ts | 계획 상세(buildPlannedSession·titleFromPlannedSets) | Mappers |
factory 의존은 callScreenRpc·callMutationRpc·callRpc(전이 대상 조회 1곳)·deleteOwnDataReplica·retryTransientFetch·createDurableMutationIdentity(계획)뿐이다. 서버 id 발급·online/durable 정책·한도 값·v4/raw fallback 없음은 옮기기 전과 같다. port 반환은 contracts/ports/writes.ts의 CompletedSessionWriteResult·CompletedSessionDeleteResult·PlanSaveResult·PlanDeleteResult·ManualRecordsDto로 좁혔고 직접 입력 저장·삭제 4종의 반환만 unknown으로 남는다(응답 codec 은 A09).
3-2. A05 이전 결과 — import·admin repository와 codec, 관리자 파사드 분리 (2026-09-09, #1406)
기록: A05 작업 기록. 인입은 일반 앱 기능(프로필 화면의 Wodup 러너)이라 옛 파일의 composition root 가 importRepository 를 조립하고 lazyFeatureDomain 이 지연 로드한다. 관리자는 독립 셸 전용이라 조립(domains/adminComposition.ts)·파사드(services/adminApi.ts) 모두 관리자 그래프에만 있다 — 앱 진입점의 모듈 그래프와 빌드 산출물에 관리자 RPC 이름이 없다(tests/react/adminBundleBoundary.test.mjs).
| 파일 | 소유하는 것 | 출처 |
|---|---|---|
repositories/importRepository.ts | uploadWodupJsonlOriginal(배치 행 → 파일 → 상태 전이 RPC), startWodupImportBatch(엣지 wodup-start-import 1회), loadWodupImportBatch(배치 행 1건, owner 좁힘), importMotraWorkouts(동기 RPC 1회) | Repository |
codecs/wodupImportCodec.ts | 배치 행 decoder(옛 services/wodupImportBatchAdapter.ts 이동), 엣지 시작 응답 decoder({error, detail} → WodupImportStartError), 서버 status → 잡 단계 변환표(wodupImportJobPhase)·잡 DTO(wodupImportJob) | Repository + 신설 |
codecs/motraImportCodec.ts | 페이로드 모양 검사, import_motra_workouts_v1 결과 → MotraImportResultDto(camelCase, kind: "completed") | 신설(종전엔 화면 컨트롤러가 snake_case 를 직접 읽음) |
repositories/adminRepository.ts | 신고 큐 읽기·처리·정지, admin_search_users_v1, 운영 점검표(get_admin_operations_checklist + 엣지 배포 확인 fetch) | Repository(+ 유저 검색은 adminMappingPageRepository 에서) |
codecs/adminModerationCodec.ts | 상태 필터·페이지 상한·조치·메모·uuid 입력 검사, 신고 큐·처리·정지 응답 decoder, 유저 검색 행 정리 | Repository |
domains/adminComposition.ts | 옛 파일의 helper 묶음(repositoryRuntime)·점검표 검증기·배포 매니페스트로 adminRepository 조립 — 앱 진입점 그래프 밖(소비자: e2e CASE-018, 앞으로의 Barbelic-docs/admin) | 신설 |
domains/adminDomain.ts | 본인 데이터 내보내기만 남음(원시 표 읽기 2개를 userDataExportRepositoryPort 로). 관리자 함수 8개는 일반 파사드·지연 로더에서 제거 — 관리자 브라우저 화면은 v0.17.7(#1463)부터 Barbelic-docs/admin 소유라 앱에 관리자 파사드를 두지 않는다(2026-09-09 의 services/adminApi.ts 는 main 반영 때 제거) | 축소 |
port 반환은 contracts/ports/importDto.ts(WodupImportJobDto·WodupImportStartResult·MotraImportResultDto)와 ports/adminDto.ts·adminPorts.ts 로 좁혔다. 완료 판정은 잡 DTO 의 phase/terminal 이 한다 — 컨트롤러·프로필 러너·앱 셸의 status 문자열 목록 3곳을 없앴다. 폴링 간격·상한·안내 문구·서버 재시도 정책(없음)은 옮기기 전과 같다.
4. 임시 어댑터 장부와 공용 wiring 소유권
| 임시 경계 | 실제 소비자 | 제거 담당 / 조건 |
|---|---|---|
repositoryPorts.ts의 workout port | workoutDomain.ts | A02 완료: workout·plan 멤버 10개는 조립된 factory 인스턴스(Legacy.workoutRepository·Legacy.planRepository)를 가리킨다. legacy 참조는 processCurrentUserStatsRefresh(통계 재계산 관측) 하나 — A06 이식 |
A02 재수출: Repository의 workout/plan 함수 12개·Mappers의 buildWorkoutRowsFromDraft·completedSessionRowsFromSession·WORKOUT_WRITE_LIMITS·상세 codec 3개 | 기존 테스트 12파일(BarbelicRepository.*·Mappers.* namespace 호출), buildGymData | 이름만 재수출(본문 없음). Repository 쪽 제거는 A01 최종 export 정리(R01 확인), Mappers 쪽은 A16 |
buildGymData 안의 상세 codec 호출과 stats port의 loadSessionDetailRows | statsDomain.loadSessionDetailData·loadPlannedSessionDetailData | 조회 함수는 workout factory 소유, stats port 는 같은 참조. A06 이 query adapter 를 만들 때 port 행만 옮기고 codec 은 A02 파일을 그대로 import |
retryTransientFetch 주입(계획 저장·삭제, 완료 기록 삭제의 1회 재시도) | plan/workout factory | 옮기기 전과 같은 동작. S05 가 dispatcher 로 통합할 때 주입 제거 |
| 같은 파일의 catalog/authProfile port | catalogDomain.ts, authProfileDomain.ts | A03 이식 완료(#1330): 두 port 의 멤버 31개는 이제 factory 결과를 옛 파일의 composition root 가 같은 이름으로 재수출한 것이다(본문 0). 어댑터 파일 자체의 제거는 A02–A06 전부 끝난 뒤 A01/A16 |
| social/group port | socialDomain.ts, groupDomain.ts | A04가 화면/UGC 경로 이식 완료 |
| import/admin port | importDomain.ts, adminDomain.ts | A05 완료(2026-09-09, #1406): 두 port 행은 repositoryPorts.ts 에서 제거. importDomain 은 옛 파일의 composition root 가 조립한 importRepository 인스턴스를, adminDomain 은 domains/adminComposition.ts 가 옛 파일의 helper 묶음(repositoryRuntime)으로 조립한 adminRepository 를 직접 받는다. 남은 좁은 port 는 본인 데이터 내보내기의 원시 표 읽기 2개(userDataExportRepositoryPort) — 원시 export 가 서버 RPC/S11 사본 manifest 로 바뀔 때 제거. Wodup/Motra 의 lazyFeatureDomain 지연 로딩 유지, 관리자 함수는 일반 파사드에서 제거(관리자 브라우저 화면은 Barbelic-docs/admin, 앱 쪽 저장소는 adminComposition 이 앱 그래프 밖에서 조립) |
| stats port | statsDomain.ts | A06 query·codec 이식, A08 cache 소유 연결 완료 |
Repository의 callScreenRpc | 현행 화면 로더와 workout/plan/stats factory | A06가 검증/query adapter를, A08가 캐시를 소유하면 각 factory에 새 port 주입 |
callLegacyReadRpc/rpcMutationAdapter와 factory helper 주입 | profile/admin 및 기존 mutation 함수, factory 9개 | A02–A06가 typed transport+codec으로 전환하면 해당 주입 제거. Motra의 타입 전용 Repository 참조는 A05 에서 제거 완료(#1406). A05 가 옛 파일에서 내보내는 helper 묶음 repositoryRuntime(callRpc·callMutationRpc·진단 6개)은 관리자 조립 전용이며 옛 파일의 composition root 가 해체될 때(A16/R01) 정본을 따라 옮긴다 |
Repository의 기존 공개 export / barbelicApi / servicePorts | controller·서비스·기존 테스트 및 feature binding | 최종 연결 A01 담당, 잔여 any/행 모양 A16, 얇은 shell A15. R01이 legacy 참조 0과 어댑터 제거를 최종 확인 |
새 모듈은 transport나 자기 domain helper만 import한다. 다른 domain 구현을 직접 import하거나 거대 Repository를 runtime으로 재수입하지 않는다. domainPortWiring.test.mjs가 이 경계를 검사한다. core 계약 변경, package/lock/Vite/workflow 변경은 이 작업에 없으며 필요하면 기존 HQ 통합 슬롯으로 전달한다.
5. Owner와 응답 적용
transport는 계정 상태를 소유하지 않는다. 기존 screenRpcDocumentLifecycleSignal은 query adapter가 전달하고, A07는 owner가 떠날 때 abort할 signal을 제공한다. A08/A15의 적용 단계는 G03 OwnerScope{userId, epoch}와 isSameOwnerScope를 비교해야 한다. 같은 사용자의 재로그인도 새로운 epoch다.
A01은 전달된 signal로 취소한 뒤 늦게 도착한 응답을 거부한다. 앱 전체의 owner lifecycle 이식을 완료했다는 뜻은 아니다. 기존 consumer의 owner guard는 그대로 유지한다. G02 owner-switch fixture로 A의 요청 → B로 전환 → A signal 취소 → 늦은 응답 거부를 검증한다. 서버 commit·기기 저장물 보존 판단은 signal만으로 바꾸지 않는다.
controller의 DB/RPC 행 타입 직접 import는 3파일에서 0으로 줄었다. 부팅 사본은 bootReadModelAdapters.ts의 검증과 기존 codec을 통과하고, 수동 기록 상태는 실제 API 반환 타입에서 추론한다. contractsImportBoundaries 기준선은 빈 목록으로 고정한다.
6. 검증 인계
rpcTransport.test.mjs: 동일 요청·응답/오류 참조, auth 전달·만료·준비 실패, retry/fallback 없음, timeout 경계, 취소·늦은 응답, timer/listener 해제.domainRepositoryDestinations.test.mjs: factory 9개를 최소 의존으로 독립 실행해 validation·RPC 인자·결과·오류·진단 보존 확인.domainPortWiring.test.mjs: 실제 공개 API ↔ feature port 함수 참조, legacy port의 제한된 표면, transport·domain import 경계.- 기존 mutation/screen-abort/v5 저장 테스트와 G03 type/boundary fixture를 유지한다. 이동 때문에 깨진 소스 위치 단언은 행동/공개 export 검사로 전환하며 감사 명부 등재분만 신고한다.
- DB schema·migration·서버 함수·UI 표현 변경은 없다. 실 DB·브라우저·기기 검증 여부와 CI 결과는 연결 PR 및 작업 기록에 구분한다.