-
이코에코(Eco²) Clean Architecture #0: Auth 리팩토링.MD이코에코(Eco²)/Clean Architecture Migration 2025. 12. 31. 01:10

작성일: 2025-12-31
Opus 4.5와의 문답, 자료조사를 거치며 디벨롭한 문서이며 리팩토링 전 과정의 초안이 됩니다.
1. 개요
1.1 목표
현재
domains/auth/구조를 Clean Architecture 원칙에 따라apps/auth/로 리팩토링합니다.1.2 주요 변경사항
항목 현재 목표 루트 경로 domains/auth/apps/auth/아키텍처 혼합된 레이어드 Clean Architecture 의존성 방향 양방향 단방향 (안쪽으로만) ORM 결합 Entity = ORM 모델 Entity/ORM 분리
2. 현재 기능 분석
2.1 AuthService 메서드 → Use Case 매핑
현재 메서드 목표 Use Case 타입 파일 authorize()OAuthAuthorizeInteractor Command commands/oauth_authorize.pylogin_with_provider()OAuthCallbackInteractor Command commands/oauth_callback.pyrefresh_session()RefreshTokensInteractor Command commands/refresh_tokens.pylogout()LogoutInteractor Command commands/logout.pyget_current_user()ValidateTokenQueryService Query queries/validate_token.py2.2 현재 서비스 → 목표 위치
현재 서비스 목표 위치 역할 TokenServiceapplication/common/ports/token_service.py(Port)인터페이스 TokenServiceinfrastructure/security/jwt_token_service.py(Adapter)구현체 OAuthStateStoreapplication/common/ports/state_store.py(Port)인터페이스 OAuthStateStoreinfrastructure/persistence_redis/state_store_redis.py(Adapter)구현체 TokenBlacklistapplication/common/ports/token_blacklist.py(Port)인터페이스 TokenBlacklistinfrastructure/persistence_redis/token_blacklist_redis.py(Adapter)구현체 UserTokenStoreapplication/common/ports/user_token_store.py(Port)인터페이스 UserTokenStoreinfrastructure/persistence_redis/user_token_store_redis.py(Adapter)구현체 ProviderRegistryinfrastructure/oauth/registry.pyOAuth 프로바이더 관리 2.3 현재 Repository → 목표 Gateway
현재 Repository 목표 Port 목표 Adapter UserRepositoryUserCommandGatewayuser_data_mapper_sqla.pyUserRepositoryUserQueryGatewayuser_reader_sqla.pyLoginAuditRepositoryLoginAuditGatewaylogin_audit_mapper_sqla.py
3. 목표 폴더 구조
apps/auth/ │ ├── domain/ # 🔵 Domain Layer (순수 Python) │ ├── __init__.py │ │ │ ├── entities/ │ │ ├── __init__.py │ │ ├── base.py # Entity[T] 베이스 │ │ ├── user.py # User 엔티티 │ │ ├── user_social_account.py # UserSocialAccount │ │ └── login_audit.py # LoginAudit │ │ │ ├── value_objects/ │ │ ├── __init__.py │ │ ├── base.py # ValueObject 베이스 │ │ ├── user_id.py # UserId │ │ ├── email.py # Email │ │ ├── provider_user_id.py # ProviderUserId │ │ └── token_payload.py # TokenPayload │ │ │ ├── enums/ │ │ ├── __init__.py │ │ ├── oauth_provider.py # OAuthProvider │ │ └── token_type.py # TokenType │ │ │ ├── ports/ │ │ ├── __init__.py │ │ ├── token_generator.py # TokenGenerator Protocol │ │ └── user_id_generator.py # UserIdGenerator Protocol │ │ │ ├── services/ │ │ ├── __init__.py │ │ └── user_service.py # 사용자 생성, 소셜 계정 연동 │ │ │ └── exceptions/ │ ├── __init__.py │ ├── base.py # DomainError │ ├── user.py # UserNotFoundError │ └── auth.py # InvalidTokenError │ ├── application/ # 🟢 Application Layer │ ├── __init__.py │ │ │ ├── commands/ │ │ ├── __init__.py │ │ ├── oauth_authorize.py # OAuthAuthorizeInteractor │ │ ├── oauth_callback.py # OAuthCallbackInteractor │ │ ├── logout.py # LogoutInteractor │ │ ├── refresh_tokens.py # RefreshTokensInteractor │ │ └── revoke_all_tokens.py # RevokeAllTokensInteractor │ │ │ ├── queries/ │ │ ├── __init__.py │ │ └── validate_token.py # ValidateTokenQueryService │ │ │ └── common/ │ ├── __init__.py │ │ │ ├── ports/ │ │ ├── __init__.py │ │ ├── user_command_gateway.py │ │ ├── user_query_gateway.py │ │ ├── social_account_gateway.py │ │ ├── login_audit_gateway.py │ │ ├── token_service.py │ │ ├── state_store.py │ │ ├── token_blacklist.py │ │ ├── user_token_store.py │ │ ├── outbox_gateway.py │ │ ├── flusher.py │ │ └── transaction_manager.py │ │ │ ├── services/ │ │ ├── __init__.py │ │ └── oauth_client.py │ │ │ ├── dto/ │ │ ├── __init__.py │ │ ├── auth.py │ │ └── token.py │ │ │ └── exceptions/ │ ├── __init__.py │ ├── base.py │ └── auth.py │ ├── infrastructure/ # 🟠 Infrastructure Layer │ ├── __init__.py │ │ │ ├── adapters/ │ │ ├── __init__.py │ │ ├── user_data_mapper_sqla.py │ │ ├── user_reader_sqla.py │ │ ├── social_account_mapper_sqla.py │ │ ├── login_audit_mapper_sqla.py │ │ ├── main_flusher_sqla.py │ │ ├── main_transaction_manager_sqla.py │ │ ├── user_id_generator_uuid.py │ │ └── types.py │ │ │ ├── persistence_postgres/ │ │ ├── __init__.py │ │ ├── session.py │ │ ├── registry.py │ │ └── mappings/ │ │ ├── __init__.py │ │ ├── all.py │ │ ├── user.py │ │ ├── user_social_account.py │ │ └── login_audit.py │ │ │ ├── persistence_redis/ │ │ ├── __init__.py │ │ ├── client.py │ │ ├── state_store_redis.py │ │ ├── token_blacklist_redis.py │ │ ├── user_token_store_redis.py │ │ └── outbox_redis.py │ │ │ ├── security/ │ │ ├── __init__.py │ │ ├── jwt_token_service.py │ │ └── jwt_processor.py │ │ │ ├── oauth/ │ │ ├── __init__.py │ │ ├── base.py │ │ ├── google.py │ │ ├── kakao.py │ │ ├── naver.py │ │ └── registry.py │ │ │ └── exceptions/ │ ├── __init__.py │ ├── base.py │ ├── gateway.py │ └── oauth.py │ ├── presentation/ # 🔴 Presentation Layer │ ├── __init__.py │ │ │ └── http/ │ ├── __init__.py │ │ │ ├── controllers/ │ │ ├── __init__.py │ │ │ │ │ ├── auth/ │ │ │ ├── __init__.py │ │ │ ├── authorize.py │ │ │ ├── callback.py │ │ │ ├── logout.py │ │ │ ├── refresh.py │ │ │ ├── revoke.py │ │ │ └── router.py │ │ │ │ │ ├── general/ │ │ │ ├── __init__.py │ │ │ ├── health.py │ │ │ ├── metrics.py │ │ │ └── router.py │ │ │ │ │ ├── api_v1_router.py │ │ └── root_router.py │ │ │ ├── auth/ │ │ ├── __init__.py │ │ ├── cookie_params.py │ │ ├── dependencies.py │ │ └── middleware.py │ │ │ ├── errors/ │ │ ├── __init__.py │ │ ├── handlers.py │ │ └── translators.py │ │ │ └── schemas/ │ ├── __init__.py │ ├── auth.py │ └── common.py │ ├── setup/ # 🟣 Setup Layer │ ├── __init__.py │ ├── config/ │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── database.py │ │ ├── redis.py │ │ ├── security.py │ │ └── oauth.py │ ├── dependencies.py │ ├── logging.py │ ├── tracing.py │ └── constants.py │ ├── workers/ # ⚙️ Workers │ ├── __init__.py │ └── consumers/ │ └── blacklist_relay.py │ ├── tests/ │ ├── __init__.py │ ├── conftest.py │ ├── unit/ │ │ ├── domain/ │ │ ├── application/ │ │ └── factories/ │ └── integration/ │ ├── main.py ├── Dockerfile ├── Dockerfile.relay ├── requirements.txt └── README.md
4. 기능 검증 매트릭스
4.1 엔드포인트 매핑
Method Endpoint 현재 목표 GET/{provider}/authorizeAuthService.authorize()OAuthAuthorizeInteractorGET/{provider}/callbackAuthService.login_with_provider()OAuthCallbackInteractorPOST/logoutAuthService.logout()LogoutInteractorPOST/refreshAuthService.refresh_session()RefreshTokensInteractorPOST/revoke(신규) RevokeAllTokensInteractorGET/healthhealth.pygeneral/health.pyGET/metricsmetrics.pygeneral/metrics.py4.2 기능 체크리스트
기능 현재 위치 목표 위치 상태 OAuth 인증 URL 생성 AuthService.authorize()commands/oauth_authorize.py✅ OAuth 콜백 (로그인/회원가입) AuthService.login_with_provider()commands/oauth_callback.py✅ 토큰 갱신 AuthService.refresh_session()commands/refresh_tokens.py✅ 로그아웃 AuthService.logout()commands/logout.py✅ 토큰 검증 get_current_user dependencyqueries/validate_token.py✅ JWT 발급/검증 TokenServiceinfrastructure/security/✅ OAuth 상태 관리 OAuthStateStorepersistence_redis/state_store_redis.py✅ 토큰 블랙리스트 TokenBlacklistpersistence_redis/token_blacklist_redis.py✅ 사용자 토큰 저장 UserTokenStorepersistence_redis/user_token_store_redis.py✅ Outbox 패턴 RedisOutboxpersistence_redis/outbox_redis.py✅ Blacklist Relay workers/blacklist_relay.pyworkers/consumers/blacklist_relay.py✅ 사용자 생성/조회 UserRepositoryadapters/user_*.py✅ 로그인 감사 LoginAuditRepositoryadapters/login_audit_mapper_sqla.py✅ 쿠키 관리 AuthService._*_cookie*()presentation/http/auth/cookie_params.py✅
5. 리팩토링 단계
Phase 1: 기반 구조 생성
# 1.1 새 디렉토리 구조 생성 mkdir -p apps/auth/{domain,application,infrastructure,presentation,setup,workers,tests} # 1.2 Domain Layer 기초 mkdir -p apps/auth/domain/{entities,value_objects,enums,ports,services,exceptions} # 1.3 Application Layer 기초 mkdir -p apps/auth/application/{commands,queries,common} mkdir -p apps/auth/application/common/{ports,services,dto,exceptions} # 1.4 Infrastructure Layer 기초 mkdir -p apps/auth/infrastructure/{adapters,persistence_postgres,persistence_redis,security,oauth,exceptions} mkdir -p apps/auth/infrastructure/persistence_postgres/mappings # 1.5 Presentation Layer 기초 mkdir -p apps/auth/presentation/http/{controllers,auth,errors,schemas} mkdir -p apps/auth/presentation/http/controllers/{auth,general} # 1.6 Setup Layer 기초 mkdir -p apps/auth/setup/config # 1.7 Workers mkdir -p apps/auth/workers/consumers # 1.8 Tests mkdir -p apps/auth/tests/{unit,integration} mkdir -p apps/auth/tests/unit/{domain,application,factories}Phase 2: Domain Layer 구현
순서 파일 내용 2.1 domain/entities/base.pyEntity 베이스 클래스 2.2 domain/value_objects/base.pyValueObject 베이스 클래스 2.3 domain/enums/*.pyOAuthProvider, TokenType 2.4 domain/value_objects/*.pyUserId, Email, TokenPayload 2.5 domain/entities/*.pyUser, UserSocialAccount, LoginAudit 2.6 domain/exceptions/*.pyDomainError, UserNotFoundError 2.7 domain/ports/*.pyTokenGenerator, UserIdGenerator 2.8 domain/services/user_service.py순수 도메인 로직 Phase 3: Application Layer 구현
순서 파일 내용 3.1 application/common/ports/*.py모든 Port 인터페이스 정의 3.2 application/common/dto/*.pyAuthorizeRequest, TokenPairDTO 3.3 application/common/exceptions/*.pyApplicationError, AuthenticationError 3.4 application/commands/oauth_authorize.pyOAuthAuthorizeInteractor 3.5 application/commands/oauth_callback.pyOAuthCallbackInteractor 3.6 application/commands/logout.pyLogoutInteractor 3.7 application/commands/refresh_tokens.pyRefreshTokensInteractor 3.8 application/queries/validate_token.pyValidateTokenQueryService Phase 4: Infrastructure Layer 구현
순서 파일 내용 4.1 infrastructure/persistence_postgres/session.pyAsyncSession 설정 4.2 infrastructure/persistence_postgres/registry.pymapper_registry 4.3 infrastructure/persistence_postgres/mappings/*.pyORM 매핑 4.4 infrastructure/adapters/user_*.pyUserCommandGateway, UserQueryGateway 구현 4.5 infrastructure/persistence_redis/*.pyRedis 기반 Port 구현 4.6 infrastructure/security/*.pyJWT 토큰 서비스 4.7 infrastructure/oauth/*.pyOAuth 프로바이더 4.8 infrastructure/adapters/main_*.pyFlusher, TransactionManager Phase 5: Presentation Layer 구현
순서 파일 내용 5.1 presentation/http/schemas/*.pyHTTP Request/Response 스키마 5.2 presentation/http/auth/*.py쿠키, 의존성, 미들웨어 5.3 presentation/http/errors/*.py에러 핸들러, 변환기 5.4 presentation/http/controllers/auth/*.pyAuth 컨트롤러들 5.5 presentation/http/controllers/general/*.pyHealth, Metrics 5.6 presentation/http/controllers/*_router.py라우터 통합 Phase 6: Setup & 통합
순서 파일 내용 6.1 setup/config/*.py설정 클래스들 6.2 setup/dependencies.pyFastAPI DI 설정 6.3 setup/logging.py로깅 설정 6.4 main.pyFastAPI 앱 엔트리포인트 6.5 workers/consumers/blacklist_relay.pyWorker 마이그레이션 Phase 7: 테스트 & 검증
순서 작업 내용 7.1 Unit Tests Domain, Application 레이어 테스트 7.2 Integration Tests 전체 플로우 테스트 7.3 E2E Tests 실제 OAuth 플로우 검증 7.4 기존 테스트 마이그레이션 tests/구조 업데이트
6. 주요 코드 변환 예시
6.1 현재 AuthService → 목표 OAuthCallbackInteractor
현재:
# domains/auth/application/services/auth.py class AuthService: def __init__( self, session: AsyncSession = Depends(get_db_session), token_service: TokenService = Depends(TokenService), ... ): self.session = session self.user_repo = UserRepository(session) async def login_with_provider(self, provider_name: str, payload: OAuthLoginRequest, ...): # 모든 로직이 한 곳에 혼합 provider = self._get_provider(provider_name) tokens = await provider.exchange_code(...) profile = await provider.fetch_profile(...) user, _ = await self.user_repo.upsert_from_profile(profile) token_pair = self.token_service.issue_pair(...) await self.session.commit() self._apply_session_cookies(response, ...) return User.model_validate(user)목표:
# apps/auth/application/commands/oauth_callback.py from dataclasses import dataclass from typing import Protocol @dataclass(frozen=True, slots=True) class OAuthCallbackRequest: provider: str code: str state: str redirect_uri: str | None user_agent: str | None ip_address: str | None @dataclass(frozen=True, slots=True) class OAuthCallbackResponse: user_id: UUID access_token: str refresh_token: str access_expires_at: int refresh_expires_at: int class OAuthCallbackInteractor: def __init__( self, user_service: UserService, # Domain Service user_command_gateway: UserCommandGateway, # Port social_account_gateway: SocialAccountGateway, # Port login_audit_gateway: LoginAuditGateway, # Port token_service: TokenService, # Port state_store: StateStore, # Port user_token_store: UserTokenStore, # Port oauth_client: OAuthClientService, # Application Service flusher: Flusher, # Port transaction_manager: TransactionManager, # Port ) -> None: self._user_service = user_service self._user_command_gateway = user_command_gateway self._social_account_gateway = social_account_gateway self._login_audit_gateway = login_audit_gateway self._token_service = token_service self._state_store = state_store self._user_token_store = user_token_store self._oauth_client = oauth_client self._flusher = flusher self._transaction_manager = transaction_manager async def execute(self, request: OAuthCallbackRequest) -> OAuthCallbackResponse: """ :raises AuthenticationError: 상태 검증 실패 :raises OAuthProviderError: Provider API 오류 :raises DataMapperError: DB 오류 """ # 1. 상태 검증 state_data = await self._state_store.consume(request.state) if not state_data or state_data.provider != request.provider: raise AuthenticationError("Invalid or expired state") # 2. OAuth 토큰 교환 & 프로필 조회 profile = await self._oauth_client.fetch_profile( provider=request.provider, code=request.code, state=request.state, redirect_uri=request.redirect_uri or state_data.redirect_uri, code_verifier=state_data.code_verifier, ) # 3. 사용자 생성/조회 user = await self._user_service.upsert_from_profile( profile=profile, user_gateway=self._user_command_gateway, social_account_gateway=self._social_account_gateway, ) # 4. 토큰 발급 token_pair = self._token_service.issue_pair( user_id=user.id_, provider=request.provider, ) # 5. 토큰 저장 await self._user_token_store.register( user_id=user.id_, jti=token_pair.refresh_jti, expires_at=token_pair.refresh_expires_at, device_id=state_data.device_id, user_agent=request.user_agent, ) # 6. 로그인 감사 self._login_audit_gateway.add(LoginAudit( user_id=user.id_, provider=request.provider, jti=token_pair.access_jti, ip_address=request.ip_address, user_agent=request.user_agent, )) # 7. 커밋 await self._flusher.flush() await self._transaction_manager.commit() return OAuthCallbackResponse( user_id=user.id_.value, access_token=token_pair.access_token, refresh_token=token_pair.refresh_token, access_expires_at=token_pair.access_expires_at, refresh_expires_at=token_pair.refresh_expires_at, )6.2 현재 User 모델 → Domain Entity + ORM 매핑 분리
현재 (혼합):
# domains/auth/domain/models/user.py from sqlalchemy.orm import Mapped, mapped_column from domains.auth.infrastructure.database.base import Base class User(Base): # ORM과 결합 __tablename__ = "users" id: Mapped[uuid.UUID] = mapped_column(...) username: Mapped[str] = mapped_column(...)목표 - Domain Entity (순수 Python):
# apps/auth/domain/entities/user.py from apps.auth.domain.entities.base import Entity from apps.auth.domain.value_objects.user_id import UserId from apps.auth.domain.value_objects.email import Email class User(Entity[UserId]): def __init__( self, *, id_: UserId, username: str | None, nickname: str | None, profile_image_url: str | None, phone_number: str | None, created_at: datetime, updated_at: datetime, last_login_at: datetime | None, ) -> None: super().__init__(id_=id_) self.username = username self.nickname = nickname self.profile_image_url = profile_image_url self.phone_number = phone_number self.created_at = created_at self.updated_at = updated_at self.last_login_at = last_login_at목표 - ORM 매핑 (Infrastructure):
# apps/auth/infrastructure/persistence_postgres/mappings/user.py from sqlalchemy import Table, Column, String, DateTime from sqlalchemy.dialects.postgresql import UUID from apps.auth.infrastructure.persistence_postgres.registry import mapper_registry from apps.auth.domain.entities.user import User from apps.auth.domain.value_objects.user_id import UserId users_table = Table( "users", mapper_registry.metadata, Column("id", UUID(as_uuid=True), primary_key=True), Column("username", String(120)), Column("nickname", String(120)), Column("profile_image_url", String(512)), Column("phone_number", String(32), index=True), Column("created_at", DateTime(timezone=True), nullable=False), Column("updated_at", DateTime(timezone=True), nullable=False), Column("last_login_at", DateTime(timezone=True)), schema="auth", ) def start_user_mapper() -> None: mapper_registry.map_imperatively( User, users_table, properties={ "id_": users_table.c.id, }, )
7. 의존성 주입 설정
7.1 FastAPI Depends 기반 (Dishka 미사용)
# apps/auth/setup/dependencies.py from functools import lru_cache from fastapi import Depends from sqlalchemy.ext.asyncio import AsyncSession from apps.auth.infrastructure.persistence_postgres.session import get_db_session from apps.auth.infrastructure.persistence_redis.client import get_redis from apps.auth.infrastructure.adapters.user_data_mapper_sqla import SqlaUserDataMapper from apps.auth.infrastructure.security.jwt_token_service import JwtTokenService from apps.auth.application.commands.oauth_callback import OAuthCallbackInteractor # Gateway Providers async def get_user_command_gateway( session: AsyncSession = Depends(get_db_session), ) -> UserCommandGateway: return SqlaUserDataMapper(session) async def get_user_query_gateway( session: AsyncSession = Depends(get_db_session), ) -> UserQueryGateway: return SqlaUserReader(session) # Service Providers @lru_cache def get_token_service() -> TokenService: return JwtTokenService(...) # Use Case Providers async def get_oauth_callback_interactor( user_command_gateway: UserCommandGateway = Depends(get_user_command_gateway), token_service: TokenService = Depends(get_token_service), ... ) -> OAuthCallbackInteractor: return OAuthCallbackInteractor( user_command_gateway=user_command_gateway, token_service=token_service, ... )
8. 위험 요소 및 대응
위험 영향 대응 ORM 매핑 분리 시 관계 매핑 복잡성 중 Imperative Mapping 테스트 철저히 기존 API 호환성 상 HTTP 스키마 동일하게 유지 테스트 커버리지 감소 중 단계별 테스트 작성 병행 배포 중 다운타임 상 Canary 이미지로 패키징, 현 배포 클러스터 이미지와 태그로 분리
9. 배포 전략 (Canary)
9.1 개요
리팩토링된 코드는 Canary 배포로 안전하게 검증합니다.
참조:
docs/blogs/deployment/01-canary-deployment-strategy.md9.2 배포 흐름
┌─────────────────────────────────────────────────────────────────────┐ │ Canary 배포 아키텍처 │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ Client │ │ │ │ │ ├─────────────────────────────────────────┐ │ │ │ X-Canary: true │ (no header) │ │ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ Istio │ │ Istio │ │ │ │ Gateway │ │ Gateway │ │ │ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ ▼ VirtualService ▼ VirtualService │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ auth-api-canary │ │ auth-api │ │ │ │ (리팩토링 버전) │ │ (Stable) │ │ │ │ version: v2 │ │ version: v1 │ │ │ └─────────────────┘ └─────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘9.3 Canary 이미지 태그
# Stable 이미지 (현재) image: docker.io/mng990/eco2:auth-api-dev-latest # Canary 이미지 (리팩토링) image: docker.io/mng990/eco2:auth-api-dev-canary9.4 테스트 방법
# 1. Stable 버전 테스트 (기존 코드) curl https://api.example.com/api/v1/auth/health # 2. Canary 버전 테스트 (리팩토링 코드) curl -H "X-Canary: true" https://api.example.com/api/v1/auth/health9.5 배포 단계
단계 작업 검증 1 리팩토링 코드 → canary브랜치로컬 테스트 2 CI/CD가 auth-api-dev-canary이미지 빌드이미지 푸시 확인 3 ArgoCD가 deployment-canary.yaml동기화Pod Running 확인 4 X-Canary: true헤더로 E2E 테스트모든 엔드포인트 검증 5 성공 시 canary→develop머지Stable 이미지 업데이트 6 Canary Deployment 제거 또는 축소 리소스 정리 9.6 롤백
문제 발생 시 즉시 롤백 가능:
# Canary Pod 스케일 다운 (트래픽 차단) kubectl scale deployment auth-api-canary -n auth --replicas=0 # 또는 Canary 헤더 없이 요청하면 자동으로 Stable로 라우팅
10. 참고 문서
docs/foundations/16-fastapi-clean-example-analysis.md- 아키텍처 상세 분석 (포스팅 완료)docs/foundations/15-dependency-injection-comparison.md- DI 비교 (내부 문서)docs/blogs/deployment/01-canary-deployment-strategy.md- Canary 배포 전략 (내부 문서)- fastapi-clean-example - 참조 프로젝트
'이코에코(Eco²) > Clean Architecture Migration' 카테고리의 다른 글
이코에코(Eco²) Clean Architecture #5: Message Consumer (1) 2025.12.31 이코에코(Eco²) Clean Architecture #4 Auth Persistence Offloading (0) 2025.12.31 이코에코(Eco²) Clean Architecture #3: Auth 버전 분리, 점진적 배포 (0) 2025.12.31 이코에코(Eco²) Clean Architecture #2: Auth Clean Architecture 구현 초안 (0) 2025.12.31 이코에코(Eco²) Clean Architecture #1: Auth 문제사안 도출, 리팩토링 전략 (0) 2025.12.31