refactor(#777): 修复Domain层依赖Adapters的DDD违规(4处) #789

Merged
xiaoxia merged 1 commits from refactor/fix-domain-adapters-dependency into develop 2026-07-23 23:54:56 +08:00
11 changed files with 242 additions and 63 deletions
+3 -1
View File
@@ -10,6 +10,8 @@ from typing import Optional
import redis
from redis import Redis
from packages.domain.auth.session_store import SessionStorePort
class RedisConfig:
"""Redis 配置"""
@@ -50,7 +52,7 @@ class NoopSessionStore:
return False
class SessionStore:
class SessionStore(SessionStorePort):
"""Session 存储服务"""
def __init__(self, redis_client: Optional[Redis] = None, config: Optional[RedisConfig] = None):
+3 -15
View File
@@ -4,11 +4,12 @@
"""
import smtplib
from dataclasses import dataclass
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from typing import List, Optional
from packages.domain.auth.email_service import EmailConfig, EmailServicePort
class NoopEmailService:
def send_verification_email(self, **kwargs):
@@ -18,20 +19,7 @@ class NoopEmailService:
return False, "Email delivery is disabled"
@dataclass
class EmailConfig:
"""邮件配置"""
smtp_host: str = "smtp.gmail.com"
smtp_port: int = 587
smtp_user: str = ""
smtp_password: str = ""
from_email: str = ""
from_name: str = "小虾 SaaS"
use_tls: bool = True
class EmailService:
class EmailService(EmailServicePort):
"""邮件服务类"""
def __init__(self, config: Optional[EmailConfig] = None):
+3 -1
View File
@@ -6,6 +6,8 @@ from typing import Any, Dict, Optional
import jwt
from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
from packages.domain.auth.jwt_service import JWTServicePort
class JWTConfig:
"""JWT 配置"""
@@ -57,7 +59,7 @@ class TokenType:
REFRESH = "refresh"
class JWTService:
class JWTService(JWTServicePort):
"""JWT 服务类"""
def __init__(self, config: JWTConfig = None):
+5 -3
View File
@@ -3,12 +3,14 @@
使用 bcrypt 安全存储密码
"""
from typing import Optional
from typing import Optional, Tuple
import bcrypt
from packages.domain.auth.password_hasher import PasswordHasherPort, PasswordValidatorPort
class PasswordHasher:
class PasswordHasher(PasswordHasherPort):
"""密码哈希服务"""
def __init__(self, rounds: int = 12):
@@ -98,7 +100,7 @@ class PasswordHasher:
return False
class PasswordValidator:
class PasswordValidator(PasswordValidatorPort):
"""密码强度验证器"""
def __init__(
Regular → Executable
+23 -29
View File
@@ -1,36 +1,30 @@
"""Authentication domain services.
"""领域层认证相关端口接口.
Only pure domain authentication helpers are exported here. Infrastructure-backed
services such as Redis session storage and SMTP email delivery live under
`packages.adapters` and should be injected into use cases.
遵循 DDD 依赖倒置原则:领域层定义端口(抽象接口),
外层(应用层、基础设施层)实现端口。
使用方式:
from packages.domain.auth import EmailServicePort, SessionStorePort
from packages.domain.auth import JWTServicePort, PasswordHasherPort
from packages.domain.auth.sms_service import SmsService
注意:具体实现请从对应的适配器层导入:
- 邮件服务: from packages.adapters.smtp import EmailService
- Session存储: from packages.adapters.redis import SessionStore
- JWT服务: from packages.application.auth.jwt_service import JWTService
- 密码哈希: from packages.application.auth.password_hasher import PasswordHasher
"""
from packages.application.auth.jwt_service import (
JWTConfig,
JWTService,
TokenType,
jwt_service,
)
from packages.application.auth.password_hasher import (
PasswordHasher,
PasswordValidator,
password_hasher,
password_validator,
)
from packages.domain.auth.email_service import EmailConfig, EmailService
from packages.domain.auth.session_store import RedisConfig, SessionStore
from packages.domain.auth.email_service import EmailConfig, EmailServicePort
from packages.domain.auth.jwt_service import JWTServicePort
from packages.domain.auth.password_hasher import PasswordHasherPort, PasswordValidatorPort
from packages.domain.auth.session_store import SessionStorePort
__all__ = [
"JWTService",
"JWTConfig",
"TokenType",
"jwt_service",
"PasswordHasher",
"PasswordValidator",
"password_hasher",
"password_validator",
"SessionStore",
"RedisConfig",
"EmailService",
"EmailConfig",
"EmailServicePort",
"JWTServicePort",
"PasswordHasherPort",
"PasswordValidatorPort",
"SessionStorePort",
]
+58 -6
View File
@@ -1,10 +1,62 @@
"""Compatibility import for SMTP email delivery.
"""邮件服务端口(领域层接口).
Infrastructure implementations live under `packages.adapters`. New code should
import `packages.adapters.smtp.email_service` directly or inject an email-sender
port into the use case.
定义邮件发送服务的抽象接口,具体实现由基础设施层(adapters)提供。
遵循 DDD 依赖倒置原则:领域层定义端口,外层实现端口。
"""
from packages.adapters.smtp.email_service import EmailConfig, EmailService
from __future__ import annotations
__all__ = ["EmailConfig", "EmailService"]
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class EmailConfig:
"""邮件配置(领域层值对象)"""
smtp_host: str = "smtp.gmail.com"
smtp_port: int = 587
smtp_user: str = ""
smtp_password: str = ""
from_email: str = ""
from_name: str = "小虾 SaaS"
use_tls: bool = True
class EmailServicePort(ABC):
"""邮件服务端口(抽象接口)"""
@abstractmethod
def send_email(
self,
to_email: str,
subject: str,
html_body: str,
text_body: Optional[str] = None,
cc: Optional[List[str]] = None,
bcc: Optional[List[str]] = None,
) -> tuple[bool, Optional[str]]:
"""发送邮件"""
...
@abstractmethod
def send_verification_email(
self,
to_email: str,
verification_code: str,
username: str = "",
) -> tuple[bool, Optional[str]]:
"""发送验证邮件"""
...
@abstractmethod
def send_password_reset_email(
self,
to_email: str,
reset_token: str,
reset_url: str = "",
username: str = "",
) -> tuple[bool, Optional[str]]:
"""发送密码重置邮件"""
...
+44
View File
@@ -0,0 +1,44 @@
"""JWT 服务端口(领域层接口).
定义 JWT Token 生成、验证、解析的抽象接口,具体实现由应用层或基础设施层提供。
遵循 DDD 依赖倒置原则:领域层定义端口,外层实现端口。
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any, Dict
class JWTServicePort(ABC):
"""JWT 服务端口(抽象接口)"""
@abstractmethod
def create_access_token(
self,
user_id: str,
role: str = "",
additional_claims: Dict[str, Any] | None = None,
) -> str:
"""创建 access_token"""
...
@abstractmethod
def create_refresh_token(self, user_id: str, session_id: str) -> str:
"""创建 refresh_token"""
...
@abstractmethod
def verify_token(self, token: str) -> Dict[str, Any]:
"""验证任意 Token"""
...
@abstractmethod
def verify_access_token(self, token: str) -> Dict[str, Any]:
"""验证 access_token"""
...
@abstractmethod
def verify_refresh_token(self, token: str) -> Dict[str, Any]:
"""验证 refresh_token"""
...
+38
View File
@@ -0,0 +1,38 @@
"""密码哈希服务端口(领域层接口).
定义密码哈希与验证的抽象接口,具体实现由应用层或基础设施层提供。
遵循 DDD 依赖倒置原则:领域层定义端口,外层实现端口。
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Optional, Tuple
class PasswordHasherPort(ABC):
"""密码哈希服务端口(抽象接口)"""
@abstractmethod
def hash_password(self, password: str) -> str:
"""哈希密码"""
...
@abstractmethod
def verify_password(self, password: str, hashed_password: str) -> bool:
"""验证密码"""
...
@abstractmethod
def needs_rehash(self, hashed_password: str) -> bool:
"""检查哈希是否需要重新计算"""
...
class PasswordValidatorPort(ABC):
"""密码强度验证端口(抽象接口)"""
@abstractmethod
def validate(self, password: str) -> Tuple[bool, Optional[str]]:
"""验证密码强度"""
...
+63 -6
View File
@@ -1,10 +1,67 @@
"""Compatibility import for Redis-backed session storage.
"""Session 存储端口(领域层接口).
Infrastructure implementations live under `packages.adapters`. New code should
import `packages.adapters.redis.session_store` directly or inject a session-store
port into the use case.
定义 Session 存储服务的抽象接口,具体实现由基础设施层(adapters)提供。
遵循 DDD 依赖倒置原则:领域层定义端口,外层实现端口。
"""
from packages.adapters.redis.session_store import RedisConfig, SessionStore
from __future__ import annotations
__all__ = ["RedisConfig", "SessionStore"]
from abc import ABC, abstractmethod
from typing import Optional
class SessionStorePort(ABC):
"""Session 存储端口(抽象接口)"""
@abstractmethod
def save_session(
self,
session_id: str,
user_id: str,
refresh_token: str,
user_agent: str = "",
ip_address: str = "",
expires_in_days: int = 7,
) -> bool:
"""保存 Session"""
...
@abstractmethod
def get_session(self, session_id: str) -> Optional[dict]:
"""获取 Session"""
...
@abstractmethod
def get_session_by_refresh_token(self, refresh_token: str) -> Optional[dict]:
"""通过 refresh_token 查找 Session"""
...
@abstractmethod
def get_refresh_token(self, session_id: str) -> Optional[str]:
"""获取 refresh_token"""
...
@abstractmethod
def update_last_active(self, session_id: str) -> bool:
"""更新最后活跃时间"""
...
@abstractmethod
def delete_session(self, session_id: str) -> bool:
"""删除 Session"""
...
@abstractmethod
def get_user_sessions(self, user_id: str) -> list[dict]:
"""获取用户所有 Session"""
...
@abstractmethod
def delete_all_user_sessions(self, user_id: str) -> int:
"""删除用户所有 Session"""
...
@abstractmethod
def session_exists(self, session_id: str) -> bool:
"""检查 Session 是否存在"""
...
Regular → Executable
+1 -1
View File
@@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
import pytest
from packages.domain.auth.email_service import EmailConfig, EmailService
from packages.adapters.smtp.email_service import EmailConfig, EmailService
class TestEmailService:
Regular → Executable
+1 -1
View File
@@ -7,7 +7,7 @@ from unittest.mock import Mock
import pytest
from packages.domain.auth.session_store import SessionStore
from packages.adapters.redis.session_store import SessionStore
class TestSessionStore: