MSC中的Model层:数据模型与数据访问层设计

MSC中的Model层:数据模型与数据访问层设计

发布日期:2025-07-11 分类:后端架构 标签:#MSC #SQLAlchemy #数据模型 #Repository模式

编辑摘要

在MSC架构中,Model层承担着数据定义和数据访问的核心职责。本文深入探讨SQLAlchemy ORM的高级特性应用,详解数据模型设计原则,并通过用户服务系统实战演示Repository模式的实现。掌握这些技能将帮助开发者构建更加健壮、可维护的数据访问层。

前言

在MSC架构系列的第一篇文章中,我们了解了MSC架构的基本概念和优势。现在,让我们深入探讨MSC架构的基础层------Model层。

Model层作为MSC架构的数据基础,其设计质量直接影响整个系统的稳定性和可维护性。与传统MVC架构中Model层承担业务逻辑不同,MSC架构中的Model层职责更加单一和清晰:专注于数据定义和数据访问,不包含任何业务逻辑

本文将以Flask + SQLAlchemy为基础,通过用户服务系统的实际开发,详细讲解如何设计高质量的Model层。

Model层的核心职责

职责边界明确

在MSC架构中,Model层的职责包括:

应该承担的职责

  • 数据模型定义(表结构、字段类型、约束等)
  • 数据关系映射(外键、一对多、多对多等)
  • 基础数据访问方法(CRUD操作)
  • 数据序列化和反序列化
  • 数据验证规则(数据层面的约束)

不应该承担的职责

  • 业务逻辑处理
  • 业务规则验证
  • 复杂的数据处理流程
  • 与外部服务的交互

与其他层的交互

python 复制代码
# 正确的层间交互示例
class UserService:
    @staticmethod
    def register_user(username, email, password):
        # Service层处理业务逻辑
        if User.query.filter_by(username=username).first():
            raise BusinessException('用户名已存在')
        
        # 调用Model层进行数据操作
        user = User(username=username, email=email)
        user.set_password(password)
        db.session.add(user)
        db.session.commit()
        
        return user

# 错误的示例:Model层包含业务逻辑
class User(db.Model):
    # ... 数据定义
    
    def register(self, password, email):
        # 错误:业务逻辑不应该在Model层
        if self.check_business_rules():
            self.send_welcome_email()
            return self.save()

SQLAlchemy ORM高级特性

数据模型设计原则

python 复制代码
# app/models/user.py
from app import db
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy import Index
from sqlalchemy.ext.hybrid import hybrid_property

class User(db.Model):
    """用户模型 - 遵循单一职责原则"""
    __tablename__ = 'users'
    
    # 主键设计
    id = db.Column(db.Integer, primary_key=True)
    
    # 基础字段 - 注意约束和索引
    username = db.Column(db.String(80), unique=True, nullable=False, index=True)
    email = db.Column(db.String(120), unique=True, nullable=False, index=True)
    password_hash = db.Column(db.String(255), nullable=False)
    
    # 状态字段
    is_active = db.Column(db.Boolean, default=True, nullable=False)
    is_verified = db.Column(db.Boolean, default=False, nullable=False)
    
    # 时间戳字段
    created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, 
                          onupdate=datetime.utcnow, nullable=False)
    last_login_at = db.Column(db.DateTime)
    
    # 软删除字段
    deleted_at = db.Column(db.DateTime)
    
    # 外键关系
    profile_id = db.Column(db.Integer, db.ForeignKey('user_profiles.id'))
    
    # 关系映射
    profile = db.relationship('UserProfile', backref='user', lazy='select')
    roles = db.relationship('Role', secondary='user_roles', 
                           backref='users', lazy='subquery')
    
    # 复合索引
    __table_args__ = (
        Index('idx_user_status', 'is_active', 'deleted_at'),
        Index('idx_user_created', 'created_at'),
    )
    
    # 数据访问方法
    def set_password(self, password):
        """设置密码哈希"""
        self.password_hash = generate_password_hash(password)
    
    def check_password(self, password):
        """验证密码"""
        return check_password_hash(self.password_hash, password)
    
    # 混合属性 - 计算属性
    @hybrid_property
    def is_deleted(self):
        return self.deleted_at is not None
    
    @is_deleted.expression
    def is_deleted(cls):
        return cls.deleted_at.isnot(None)
    
    # 序列化方法
    def to_dict(self, include_email=True):
        """转换为字典格式"""
        data = {
            'id': self.id,
            'username': self.username,
            'is_active': self.is_active,
            'is_verified': self.is_verified,
            'created_at': self.created_at.isoformat(),
            'last_login_at': self.last_login_at.isoformat() if self.last_login_at else None
        }
        
        if include_email:
            data['email'] = self.email
            
        return data
    
    # 类方法 - 数据查询
    @classmethod
    def find_by_username(cls, username):
        """根据用户名查找用户"""
        return cls.query.filter_by(username=username, deleted_at=None).first()
    
    @classmethod
    def find_by_email(cls, email):
        """根据邮箱查找用户"""
        return cls.query.filter_by(email=email, deleted_at=None).first()
    
    @classmethod
    def find_active_users(cls, limit=None):
        """查找活跃用户"""
        query = cls.query.filter_by(is_active=True, deleted_at=None)
        if limit:
            query = query.limit(limit)
        return query.all()
    
    def __repr__(self):
        return f'<User {self.username}>'

关系映射最佳实践

python 复制代码
# app/models/user_profile.py
class UserProfile(db.Model):
    """用户详细信息"""
    __tablename__ = 'user_profiles'
    
    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(50))
    last_name = db.Column(db.String(50))
    phone = db.Column(db.String(20))
    avatar_url = db.Column(db.String(255))
    bio = db.Column(db.Text)
    birth_date = db.Column(db.Date)
    gender = db.Column(db.Enum('male', 'female', 'other'), default='other')
    
    # 地址信息
    country = db.Column(db.String(50))
    city = db.Column(db.String(50))
    address = db.Column(db.String(255))
    postal_code = db.Column(db.String(20))
    
    # 一对一关系(反向引用在User模型中定义)
    def get_full_name(self):
        """获取全名"""
        if self.first_name and self.last_name:
            return f"{self.first_name} {self.last_name}"
        return self.first_name or self.last_name or ""

# app/models/role.py
class Role(db.Model):
    """角色模型"""
    __tablename__ = 'roles'
    
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True, nullable=False)
    description = db.Column(db.Text)
    is_active = db.Column(db.Boolean, default=True)
    
    # 多对多关系
    permissions = db.relationship('Permission', secondary='role_permissions',
                                 backref='roles', lazy='subquery')
    
    def has_permission(self, permission_name):
        """检查是否拥有指定权限"""
        return any(p.name == permission_name for p in self.permissions)

# app/models/permission.py
class Permission(db.Model):
    """权限模型"""
    __tablename__ = 'permissions'
    
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True, nullable=False)
    resource = db.Column(db.String(50), nullable=False)  # 资源类型
    action = db.Column(db.String(50), nullable=False)    # 操作类型
    description = db.Column(db.Text)
    
    # 复合索引
    __table_args__ = (
        Index('idx_permission_resource_action', 'resource', 'action'),
    )

# 关联表定义
user_roles = db.Table('user_roles',
    db.Column('user_id', db.Integer, db.ForeignKey('users.id'), primary_key=True),
    db.Column('role_id', db.Integer, db.ForeignKey('roles.id'), primary_key=True),
    db.Column('created_at', db.DateTime, default=datetime.utcnow)
)

role_permissions = db.Table('role_permissions',
    db.Column('role_id', db.Integer, db.ForeignKey('roles.id'), primary_key=True),
    db.Column('permission_id', db.Integer, db.ForeignKey('permissions.id'), primary_key=True),
    db.Column('created_at', db.DateTime, default=datetime.utcnow)
)

数据验证与约束

python 复制代码
# app/models/validators.py
from sqlalchemy import event
from sqlalchemy.orm import validates
import re

class User(db.Model):
    # ... 字段定义
    
    @validates('email')
    def validate_email(self, key, email):
        """邮箱格式验证"""
        if not email:
            raise ValueError('邮箱不能为空')
        
        email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(email_pattern, email):
            raise ValueError('邮箱格式不正确')
        
        return email.lower()
    
    @validates('username')
    def validate_username(self, key, username):
        """用户名验证"""
        if not username:
            raise ValueError('用户名不能为空')
        
        if len(username) < 3 or len(username) > 20:
            raise ValueError('用户名长度必须在3-20个字符之间')
        
        if not re.match(r'^[a-zA-Z0-9_]+$', username):
            raise ValueError('用户名只能包含字母、数字和下划线')
        
        return username
    
    @validates('password_hash')
    def validate_password_hash(self, key, password_hash):
        """密码哈希验证"""
        if not password_hash:
            raise ValueError('密码不能为空')
        return password_hash

# 数据库事件监听
@event.listens_for(User, 'before_insert')
def set_default_values(mapper, connection, target):
    """插入前设置默认值"""
    if not target.created_at:
        target.created_at = datetime.utcnow()
    if not target.updated_at:
        target.updated_at = datetime.utcnow()

@event.listens_for(User, 'before_update')
def update_timestamp(mapper, connection, target):
    """更新前设置时间戳"""
    target.updated_at = datetime.utcnow()

Repository模式实现

基础Repository类

python 复制代码
# app/repositories/base_repository.py
from abc import ABC, abstractmethod
from typing import List, Optional, Dict, Any
from sqlalchemy.orm import Query
from app import db

class BaseRepository(ABC):
    """基础仓库类 - 定义通用数据访问接口"""
    
    def __init__(self, model):
        self.model = model
        self.session = db.session
    
    def create(self, **kwargs) -> Any:
        """创建记录"""
        instance = self.model(**kwargs)
        self.session.add(instance)
        self.session.commit()
        return instance
    
    def get_by_id(self, id: int) -> Optional[Any]:
        """根据ID获取记录"""
        return self.session.query(self.model).get(id)
    
    def get_all(self, limit: Optional[int] = None) -> List[Any]:
        """获取所有记录"""
        query = self.session.query(self.model)
        if limit:
            query = query.limit(limit)
        return query.all()
    
    def update(self, id: int, **kwargs) -> Optional[Any]:
        """更新记录"""
        instance = self.get_by_id(id)
        if not instance:
            return None
        
        for key, value in kwargs.items():
            if hasattr(instance, key):
                setattr(instance, key, value)
        
        self.session.commit()
        return instance
    
    def delete(self, id: int) -> bool:
        """删除记录"""
        instance = self.get_by_id(id)
        if not instance:
            return False
        
        self.session.delete(instance)
        self.session.commit()
        return True
    
    def exists(self, **kwargs) -> bool:
        """检查记录是否存在"""
        return self.session.query(self.model).filter_by(**kwargs).first() is not None
    
    def count(self, **kwargs) -> int:
        """统计记录数量"""
        return self.session.query(self.model).filter_by(**kwargs).count()
    
    def find_by(self, **kwargs) -> Optional[Any]:
        """根据条件查找单个记录"""
        return self.session.query(self.model).filter_by(**kwargs).first()
    
    def find_all_by(self, **kwargs) -> List[Any]:
        """根据条件查找所有记录"""
        return self.session.query(self.model).filter_by(**kwargs).all()
    
    def paginate(self, page: int = 1, per_page: int = 10, **kwargs) -> Dict[str, Any]:
        """分页查询"""
        query = self.session.query(self.model)
        
        if kwargs:
            query = query.filter_by(**kwargs)
        
        pagination = query.paginate(
            page=page, per_page=per_page, error_out=False
        )
        
        return {
            'items': pagination.items,
            'total': pagination.total,
            'pages': pagination.pages,
            'current_page': page,
            'per_page': per_page,
            'has_next': pagination.has_next,
            'has_prev': pagination.has_prev
        }

用户Repository实现

python 复制代码
# app/repositories/user_repository.py
from typing import List, Optional, Dict, Any
from sqlalchemy import and_, or_, desc
from app.models.user import User
from app.models.role import Role
from app.repositories.base_repository import BaseRepository
from datetime import datetime, timedelta

class UserRepository(BaseRepository):
    """用户数据访问层"""
    
    def __init__(self):
        super().__init__(User)
    
    def find_by_username(self, username: str) -> Optional[User]:
        """根据用户名查找用户"""
        return self.session.query(User).filter(
            and_(
                User.username == username,
                User.deleted_at.is_(None)
            )
        ).first()
    
    def find_by_email(self, email: str) -> Optional[User]:
        """根据邮箱查找用户"""
        return self.session.query(User).filter(
            and_(
                User.email == email,
                User.deleted_at.is_(None)
            )
        ).first()
    
    def find_by_username_or_email(self, identifier: str) -> Optional[User]:
        """根据用户名或邮箱查找用户"""
        return self.session.query(User).filter(
            and_(
                or_(
                    User.username == identifier,
                    User.email == identifier
                ),
                User.deleted_at.is_(None)
            )
        ).first()
    
    def find_active_users(self, limit: Optional[int] = None) -> List[User]:
        """查找活跃用户"""
        query = self.session.query(User).filter(
            and_(
                User.is_active == True,
                User.deleted_at.is_(None)
            )
        )
        
        if limit:
            query = query.limit(limit)
        
        return query.all()
    
    def find_users_by_role(self, role_name: str) -> List[User]:
        """根据角色查找用户"""
        return self.session.query(User).join(User.roles).filter(
            and_(
                Role.name == role_name,
                User.deleted_at.is_(None)
            )
        ).all()
    
    def search_users(self, keyword: str, page: int = 1, per_page: int = 10) -> Dict[str, Any]:
        """搜索用户"""
        search_filter = f"%{keyword}%"
        query = self.session.query(User).filter(
            and_(
                or_(
                    User.username.like(search_filter),
                    User.email.like(search_filter)
                ),
                User.deleted_at.is_(None)
            )
        )
        
        pagination = query.paginate(
            page=page, per_page=per_page, error_out=False
        )
        
        return {
            'items': pagination.items,
            'total': pagination.total,
            'pages': pagination.pages,
            'current_page': page,
            'per_page': per_page,
            'has_next': pagination.has_next,
            'has_prev': pagination.has_prev
        }
    
    def get_user_statistics(self) -> Dict[str, int]:
        """获取用户统计信息"""
        total_users = self.session.query(User).filter(User.deleted_at.is_(None)).count()
        active_users = self.session.query(User).filter(
            and_(
                User.is_active == True,
                User.deleted_at.is_(None)
            )
        ).count()
        
        # 最近30天注册的用户
        thirty_days_ago = datetime.utcnow() - timedelta(days=30)
        recent_users = self.session.query(User).filter(
            and_(
                User.created_at >= thirty_days_ago,
                User.deleted_at.is_(None)
            )
        ).count()
        
        return {
            'total_users': total_users,
            'active_users': active_users,
            'inactive_users': total_users - active_users,
            'recent_users': recent_users
        }
    
    def soft_delete(self, user_id: int) -> bool:
        """软删除用户"""
        user = self.get_by_id(user_id)
        if not user:
            return False
        
        user.deleted_at = datetime.utcnow()
        user.is_active = False
        self.session.commit()
        return True
    
    def restore_user(self, user_id: int) -> bool:
        """恢复已删除的用户"""
        user = self.session.query(User).filter(User.id == user_id).first()
        if not user or user.deleted_at is None:
            return False
        
        user.deleted_at = None
        user.is_active = True
        self.session.commit()
        return True
    
    def update_last_login(self, user_id: int) -> bool:
        """更新最后登录时间"""
        user = self.get_by_id(user_id)
        if not user:
            return False
        
        user.last_login_at = datetime.utcnow()
        self.session.commit()
        return True
    
    def batch_update_status(self, user_ids: List[int], is_active: bool) -> int:
        """批量更新用户状态"""
        count = self.session.query(User).filter(
            and_(
                User.id.in_(user_ids),
                User.deleted_at.is_(None)
            )
        ).update({User.is_active: is_active}, synchronize_session=False)
        
        self.session.commit()
        return count

角色Repository实现

python 复制代码
# app/repositories/role_repository.py
from typing import List, Optional, Dict, Any
from sqlalchemy import and_
from app.models.role import Role
from app.models.permission import Permission
from app.repositories.base_repository import BaseRepository

class RoleRepository(BaseRepository):
    """角色数据访问层"""
    
    def __init__(self):
        super().__init__(Role)
    
    def find_by_name(self, name: str) -> Optional[Role]:
        """根据名称查找角色"""
        return self.session.query(Role).filter(Role.name == name).first()
    
    def find_active_roles(self) -> List[Role]:
        """查找活跃角色"""
        return self.session.query(Role).filter(Role.is_active == True).all()
    
    def assign_permission(self, role_id: int, permission_id: int) -> bool:
        """为角色分配权限"""
        role = self.get_by_id(role_id)
        permission = self.session.query(Permission).get(permission_id)
        
        if not role or not permission:
            return False
        
        if permission not in role.permissions:
            role.permissions.append(permission)
            self.session.commit()
        
        return True
    
    def remove_permission(self, role_id: int, permission_id: int) -> bool:
        """移除角色权限"""
        role = self.get_by_id(role_id)
        permission = self.session.query(Permission).get(permission_id)
        
        if not role or not permission:
            return False
        
        if permission in role.permissions:
            role.permissions.remove(permission)
            self.session.commit()
        
        return True
    
    def get_role_permissions(self, role_id: int) -> List[Permission]:
        """获取角色权限列表"""
        role = self.get_by_id(role_id)
        return role.permissions if role else []
    
    def find_roles_with_permission(self, permission_name: str) -> List[Role]:
        """查找拥有指定权限的角色"""
        return self.session.query(Role).join(Role.permissions).filter(
            Permission.name == permission_name
        ).all()

数据库迁移策略

迁移文件管理

python 复制代码
# migrations/versions/001_create_users_table.py
"""创建用户表

Revision ID: 001
Revises: 
Create Date: 2025-07-11 10:00:00.000000

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers
revision = '001'
down_revision = None
branch_labels = None
depends_on = None

def upgrade():
    # 创建用户表
    op.create_table('users',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('username', sa.String(length=80), nullable=False),
        sa.Column('email', sa.String(length=120), nullable=False),
        sa.Column('password_hash', sa.String(length=255), nullable=False),
        sa.Column('is_active', sa.Boolean(), nullable=False),
        sa.Column('is_verified', sa.Boolean(), nullable=False),
        sa.Column('created_at', sa.DateTime(), nullable=False),
        sa.Column('updated_at', sa.DateTime(), nullable=False),
        sa.Column('last_login_at', sa.DateTime(), nullable=True),
        sa.Column('deleted_at', sa.DateTime(), nullable=True),
        sa.PrimaryKeyConstraint('id'),
        sa.UniqueConstraint('email'),
        sa.UniqueConstraint('username')
    )
    
    # 创建索引
    op.create_index('idx_user_username', 'users', ['username'])
    op.create_index('idx_user_email', 'users', ['email'])
    op.create_index('idx_user_status', 'users', ['is_active', 'deleted_at'])
    op.create_index('idx_user_created', 'users', ['created_at'])

def downgrade():
    # 删除索引
    op.drop_index('idx_user_created', 'users')
    op.drop_index('idx_user_status', 'users')
    op.drop_index('idx_user_email', 'users')
    op.drop_index('idx_user_username', 'users')
    
    # 删除表
    op.drop_table('users')

数据迁移脚本

python 复制代码
# scripts/migrate_data.py
from app import create_app, db
from app.models.user import User
from app.models.role import Role
from app.repositories.user_repository import UserRepository
from app.repositories.role_repository import RoleRepository

def create_default_roles():
    """创建默认角色"""
    role_repo = RoleRepository()
    
    default_roles = [
        {'name': 'admin', 'description': '管理员'},
        {'name': 'user', 'description': '普通用户'},
        {'name': 'guest', 'description': '访客'},
    ]
    
    for role_data in default_roles:
        if not role_repo.find_by_name(role_data['name']):
            role_repo.create(**role_data)
            print(f"创建角色: {role_data['name']}")

def create_admin_user():
    """创建管理员用户"""
    user_repo = UserRepository()
    role_repo = RoleRepository()
    
    if not user_repo.find_by_username('admin'):
        # 创建管理员用户
        admin_user = User(
            username='admin',
            email='admin@example.com',
            is_active=True,
            is_verified=True
        )
        admin_user.set_password('admin123')
        
        # 分配管理员角色
        admin_role = role_repo.find_by_name('admin')
        if admin_role:
            admin_user.roles.append(admin_role)
        
        db.session.add(admin_user)
        db.session.commit()
        print("创建管理员用户成功")

def main():
    """主函数"""
    app = create_app()
    with app.app_context():
        create_default_roles()
        create_admin_user()
        print("数据迁移完成")

if __name__ == '__main__':
    main()

数据模型测试

模型单元测试

python 复制代码
# tests/test_models/test_user.py
import unittest
from app import create_app, db
from app.models.user import User
from app.models.role import Role
from datetime import datetime

class TestUserModel(unittest.TestCase):
    def setUp(self):
        self.app = create_app('testing')
        self.app_context = self.app.app_context()
        self.app_context.push()
        db.create_all()
    
    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.app_context.pop()
    
    def test_password_hashing(self):
        """测试密码加密"""
        user = User(username='testuser', email='test@example.com')
        user.set_password('password123')
        
        self.assertTrue(user.check_password('password123'))
        self.assertFalse(user.check_password('wrongpassword'))
    
    def test_user_creation(self):
        """测试用户创建"""
        user = User(
            username='testuser',
            email='test@example.com'
        )
        user.set_password('password123')
        
        db.session.add(user)
        db.session.commit()
        
        self.assertIsNotNone(user.id)
        self.assertEqual(user.username, 'testuser')
        self.assertEqual(user.email, 'test@example.com')
        self.assertTrue(user.is_active)
        self.assertFalse(user.is_verified)
    
    def test_user_validation(self):
        """测试用户数据验证"""
        # 测试邮箱验证
        with self.assertRaises(ValueError):
            user = User(username='testuser', email='invalid_email')
            db.session.add(user)
            db.session.commit()
        
        # 测试用户名验证
        with self.assertRaises(ValueError):
            user = User(username='a', email='test@example.com')  # 用户名太短
            db.session.add(user)
            db.session.commit()
    
    def test_user_role_relationship(self):
        """测试用户角色关系"""
        # 创建角色
        role = Role(name='test_role', description='测试角色')
        db.session.add(role)
        
        # 创建用户
        user = User(username='testuser', email='test@example.com')
        user.set_password('password123')
        user.roles.append(role)
        
        db.session.add(user)
        db.session.commit()
        
        # 验证关系
        self.assertEqual(len(user.roles), 1)
        self.assertEqual(user.roles[0].name, 'test_role')
        self.assertEqual(len(role.users), 1)
        self.assertEqual(role.users[0].username, 'testuser')
    
    def test_user_serialization(self):
        """测试用户序列化"""
        user = User(
            username='testuser',
            email='test@example.com',
            is_active=True,
            is_verified=False
        )
        
        data = user.to_dict()
        
        self.assertEqual(data['username'], 'testuser')
        self.assertEqual(data['email'], 'test@example.com')
        self.assertTrue(data['is_active'])
        self.assertFalse(data['is_verified'])
    
    def test_find_by_username(self):
        """测试根据用户名查找"""
        user = User(username='testuser', email='test@example.com')
        user.set_password('password123')
        db.session.add(user)
        db.session.commit()
        
        found_user = User.find_by_username('testuser')
        self.assertIsNotNone(found_user)
        self.assertEqual(found_user.username, 'testuser')
        
        # 测试软删除用户不被查找
        user.deleted_at = datetime.utcnow()
        db.session.commit()
        
        found_user = User.find_by_username('testuser')
        self.assertIsNone(found_user)

Repository测试

python 复制代码
# tests/test_repositories/test_user_repository.py
import unittest
from app import create_app, db
from app.models.user import User
from app.repositories.user_repository import UserRepository
from datetime import datetime

class TestUserRepository(unittest.TestCase):
    def setUp(self):
        self.app = create_app('testing')
        self.app_context = self.app.app_context()
        self.app_context.push()
        db.create_all()
        
        self.user_repo = UserRepository()
    
    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.app_context.pop()
    
    def test_create_user(self):
        """测试创建用户"""
        user = self.user_repo.create(
            username='testuser',
            email='test@example.com',
            password_hash='hashed_password'
        )
        
        self.assertIsNotNone(user.id)
        self.assertEqual(user.username, 'testuser')
    
    def test_find_by_username(self):
        """测试根据用户名查找"""
        # 创建用户
        user = self.user_repo.create(
            username='testuser',
            email='test@example.com',
            password_hash='hashed_password'
        )
        
        # 查找用户
        found_user = self.user_repo.find_by_username('testuser')
        self.assertIsNotNone(found_user)
        self.assertEqual(found_user.id, user.id)
        
        # 测试不存在的用户
        not_found = self.user_repo.find_by_username('nonexistent')
        self.assertIsNone(not_found)
    
    def test_search_users(self):
        """测试搜索用户"""
        # 创建测试用户
        users = [
            self.user_repo.create(username='alice', email='alice@example.com', password_hash='hash1'),
            self.user_repo.create(username='bob', email='bob@example.com', password_hash='hash2'),
            self.user_repo.create(username='charlie', email='charlie@example.com', password_hash='hash3')
        ]
        
        # 搜索测试
        result = self.user_repo.search_users('alice')
        self.assertEqual(len(result['items']), 1)
        self.assertEqual(result['items'][0].username, 'alice')
        
        # 邮箱搜索
        result = self.user_repo.search_users('bob@example.com')
        self.assertEqual(len(result['items']), 1)
        self.assertEqual(result['items'][0].username, 'bob')
    
    def test_soft_delete(self):
        """测试软删除"""
        user = self.user_repo.create(
            username='testuser',
            email='test@example.com',
            password_hash='hashed_password'
        )
        
        # 软删除
        result = self.user_repo.soft_delete(user.id)
        self.assertTrue(result)
        
        # 验证软删除
        deleted_user = self.user_repo.get_by_id(user.id)
        self.assertIsNotNone(deleted_user.deleted_at)
        self.assertFalse(deleted_user.is_active)
        
        # 验证查找不到已删除用户
        found_user = self.user_repo.find_by_username('testuser')
        self.assertIsNone(found_user)
    
    def test_user_statistics(self):
        """测试用户统计"""
        # 创建测试用户
        active_user = self.user_repo.create(
            username='active', email='active@example.com', 
            password_hash='hash', is_active=True
        )
        
        inactive_user = self.user_repo.create(
            username='inactive', email='inactive@example.com', 
            password_hash='hash', is_active=False
        )
        
        # 获取统计
        stats = self.user_repo.get_user_statistics()
        
        self.assertEqual(stats['total_users'], 2)
        self.assertEqual(stats['active_users'], 1)
        self.assertEqual(stats['inactive_users'], 1)

性能优化策略

查询优化

python 复制代码
# app/repositories/optimized_queries.py
from sqlalchemy.orm import joinedload, selectinload
from app.models.user import User
from app.models.role import Role

class OptimizedUserRepository(UserRepository):
    """优化的用户仓库"""
    
    def get_users_with_roles(self, user_ids: List[int]) -> List[User]:
        """获取用户及其角色信息 - 使用joinedload优化"""
        return self.session.query(User).options(
            joinedload(User.roles)
        ).filter(User.id.in_(user_ids)).all()
    
    def get_users_with_profile(self, limit: int = 10) -> List[User]:
        """获取用户及其详细信息 - 使用selectinload优化"""
        return self.session.query(User).options(
            selectinload(User.profile)
        ).limit(limit).all()
    
    def get_user_dashboard_data(self, user_id: int) -> Dict[str, Any]:
        """获取用户仪表板数据 - 单次查询优化"""
        user = self.session.query(User).options(
            joinedload(User.roles).joinedload(Role.permissions),
            joinedload(User.profile)
        ).filter(User.id == user_id).first()
        
        if not user:
            return None
        
        return {
            'user': user.to_dict(),
            'roles': [role.name for role in user.roles],
            'permissions': [perm.name for role in user.roles for perm in role.permissions],
            'profile': user.profile.to_dict() if user.profile else None
        }

缓存策略

python 复制代码
# app/repositories/cached_user_repository.py
from functools import wraps
from flask_caching import Cache
from app.repositories.user_repository import UserRepository

cache = Cache()

def cache_result(timeout=300):
    """缓存装饰器"""
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            cache_key = f"{f.__name__}:{str(args)}:{str(kwargs)}"
            result = cache.get(cache_key)
            if result is None:
                result = f(*args, **kwargs)
                cache.set(cache_key, result, timeout=timeout)
            return result
        return decorated_function
    return decorator

class CachedUserRepository(UserRepository):
    """带缓存的用户仓库"""
    
    @cache_result(timeout=600)
    def get_user_statistics(self) -> Dict[str, int]:
        """获取用户统计 - 缓存10分钟"""
        return super().get_user_statistics()
    
    @cache_result(timeout=300)
    def find_users_by_role(self, role_name: str) -> List[User]:
        """根据角色查找用户 - 缓存5分钟"""
        return super().find_users_by_role(role_name)
    
    def create(self, **kwargs) -> User:
        """创建用户时清除相关缓存"""
        user = super().create(**kwargs)
        # 清除统计缓存
        cache.delete_memoized(self.get_user_statistics)
        return user

总结

Model层作为MSC架构的数据基础,其设计质量直接影响整个系统的稳定性和可维护性。通过本文的学习,我们掌握了:

核心要点

  • Model层职责边界的明确定义
  • SQLAlchemy ORM的高级特性应用
  • Repository模式的实现和优化
  • 数据验证与约束的最佳实践
  • 数据库迁移的管理策略

设计原则

  • 单一职责:Model层只负责数据定义和访问
  • 数据完整性:通过约束和验证确保数据质量
  • 性能优化:合理使用索引、查询优化和缓存
  • 可测试性:编写完整的单元测试

实践建议

  • 严格遵循Model层职责边界
  • 使用Repository模式封装数据访问逻辑
  • 重视数据模型的设计和验证
  • 建立完善的测试体系

在下一篇文章中,我们将深入探讨Service层的设计,了解如何在Service层中封装业务逻辑,处理事务管理,以及实现依赖注入等高级特性。

下期预告:《MSC中的Service层:业务逻辑封装与设计模式》

我们将详细讲解Service层的设计原则、业务逻辑封装策略、事务管理和异常处理,以及如何通过依赖注入实现松耦合的设计。

参考资料

相关推荐
志辉AI编程11 分钟前
别人还在入门,你已经精通!Claude Code进阶必备14招
后端·ai编程
代码老y18 分钟前
Spring Boot项目中大文件上传的高级实践与性能优化
spring boot·后端·性能优化
paishishaba20 分钟前
处理Web请求路径参数
java·开发语言·后端
程序无bug24 分钟前
Java中的8中基本数据类型转换
java·后端
雨落倾城夏未凉28 分钟前
8.Qt文件操作
c++·后端·qt
51731 分钟前
Django中序列化与反序列化
后端·python·django
也许明天y33 分钟前
Spring Cloud Gateway 自定义分布式限流
redis·后端·spring cloud
CodeWithMe38 分钟前
【Note】Linux Kernel 之 内核架构、源码文件、API/ABI 、FHS
linux·运维·架构
谢娘蓝桥1 小时前
[Xmos] Xmos架构
开发语言·架构·xmos
一块plus1 小时前
深度详解 Revive 和 Precompile 技术路径
后端·设计模式·架构