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层的设计原则、业务逻辑封装策略、事务管理和异常处理,以及如何通过依赖注入实现松耦合的设计。

参考资料

相关推荐
seabirdssss2 分钟前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续11 分钟前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
Lei活在当下2 小时前
【业务场景架构实战】1. 多模块 Hilt 使用原则和环境搭建
性能优化·架构·客户端
OC溥哥9992 小时前
Flask论坛与个人中心页面开发教程完整详细版
后端·python·flask·html
迷知悟道4 小时前
java面向对象四大核心特征之抽象---超详细(保姆级)
java·后端
歪歪1004 小时前
Qt Creator 打包应用程序时经常会遇到各种问题
开发语言·c++·qt·架构·编辑器
Aurora_NeAr4 小时前
对比Java学习Go——程序结构与变量
后端
AntBlack4 小时前
每周学点 AI:ComfyUI + Modal 的一键部署脚本
人工智能·后端·aigc
5大大大大雄5 小时前
docker容器日志处理
后端
我是哪吒5 小时前
分布式微服务系统架构第170集:Kafka消费者并发-多节点消费-可扩展性
后端·面试·github