导言

模型-View控制器(MVC)模式几十年来一直是网络应用开发的基石,然而,随着应用程序的复杂性和用户需求的增加,许多团队发现,他们的模型——负责数据和业务逻辑的层——迅速成为瓶颈,结构不完善的模型导致紧密的组合,重复的逻辑,以及一个抵制变化的代码库. 实现可扩展性需要周密的,有纪律的模型设计,这篇文章为MVC应用模型的结构化提供了一套全面的最佳做法,借鉴了经过验证的建筑模式和生产经验.

理解 MVC 模式

MVC模式将一个应用程序分离为三个互联组件:

  • 模型:管理数据、商业规则和持久性逻辑。它是应用程序域的单一真理源。
  • View: 渲染用户界面,一般通过从模型读取数据(或以演示文稿为主的表达).
  • 控制器:[ 处理用户输入,调节模型和视图之间的相互作用,并相应更新状态.

虽然视图和控制器很重要,但模型是大多数智力复杂性所居之处. 结构完善的模型使得应用程序能够适应新的要求,处理增加的流量,并支持多个接口(如网络,API,移动)而不连带变化.

可扩展模型的核心原则

在进入特定模式之前,必须把一些基本原则内化:

  • 单一责任:[ 每个模型或类都应该有一个明确的理由来改变,例如,将数据访问与业务验证分开.
  • 消除关切: 应用的不同方面(持久性、验证性、通知性等)应当以不同的、松散的层次实施。
  • 不要重复自己(DRY): 多重模型或控制器中的重复逻辑会导致维护噩梦。相反,提取共同行为为可重复使用的服务或特征。
  • 依赖性反演:[ 高层次模块应该依赖于抽象(界面),而不是具体的执行,这样可以互换数据库,缓存提供者,或者外部服务而不重写业务逻辑.

域驱动设计( DDD)

Eric Evans的域设计仍然是模型可扩展性最有效的方法之一。 DDD鼓励开发者围绕核心商业领域而不是技术关切来组织模型。

乌比齐特语Name

建立开发者、域专家和利害关系方共同使用的词汇。在代码、文档和对话中使用相同的术语。例如,电子商务应用程序应当有一个反映现实世界秩序行为的类,而不是一个通用的。

边界背景

大型应用由多个子域组成. DDD 建议在上下文之间划定明确的界限—— 例如, 订单管理、 库存和运输的单独模式。 在每一个限定的上下文中, 模型可以优化, 而不跨越边界泄露概念。 这种隔离是独立地扩大开发团队的关键 。

合计数

集合是一组被作为单一单位处理的域对象。 根实体保证一致性。 例如, 集合 [[FLT: 2]] 可能包括 和 实体, 全部通过命令根访问。 此模式会减少复杂的关系, 简化交易 。

对于更深的潜水,请参考马丁·福勒对DDD的介绍.

层状结构

分层结构通过将模型组织成不同的逻辑层次,进一步将关注分开:

  • 域层:包含商业实体,值对象,和域服务。此层对基础设施没有依赖性。
  • 应用层: 管弦乐团使用大小写,协调域对象,管理交易,取决于域层.
  • 基础设施层: 执行持久性,消息,外部API调用,以及其他技术关切,取决于域和应用层.
  • 介绍层:[]通过接口与应用程序层相互作用的控制器和视图.

这种分离可以确保数据库技术、缓存策略或UI框架的改变不会通过核心业务逻辑波及。它也使单位测试更加容易——域逻辑可以不嘲弄数据库而进行测试。

仓库和服务

两种模式对于保持模型清洁和可扩展性特别有价值:

仓库模式

寄存器封装数据访问逻辑, 向域对象提供类似存储的接口。 您不在整个控制器中搜索数据库, 而是拨打 [[FLT: 5]] 。 这种抽象可以将数据源( 例如从 MySQL 到 PostgreSQL 或甚至一个存储存储器进行测试) 互换, 影响最小 。

服务层

服务包含着不自然属于单一实体的业务逻辑。 例如, 可以在下订单时协调验证、定价和库存检查。服务依赖于寄存器和域实体,但依然无法对数据库进行不可知化。这种分离还有利于控制器、背景工作和API的重复使用。

进一步阅读,见 弗勒的存储图案描述[.

数据传输对象( DTO) 和查看模型

将您的完整域模型向视图层或外部 API 客户端曝光, 会产生紧密的组合, 并经常暴露不必要的内部细节。 相反, 使用 DTO 来准确塑造所需的数据 。 好处包括:

  • 解析: 域实体的更改不会自动打破API客户端.
  • 安全:]敏感字段(如内部ID,审计时间戳)可以省略.
  • 性能:[] DTO可以定制,只包括特定端点所需的场,减小有效载荷大小.

查看模型对演示文稿层也有类似的目的,只包含视图需要提供的数据(通常与格式化日期或计算总数等显示逻辑同时进行).

优化可扩展性数据库访问

如果数据库访问效率低下,即使最干净的模型架构也会失败。

索引

分析查询模式,并在、和条款中使用的列上创建索引。过度索引可以慢写、测量和监视。

查询缓存

使用 Redis 或 Memcached 等内存来缓存昂贵查询的结果。 执行适合您的域名的缓存无效( 时间、 事件驱动或手动) 。

铺设和懒惰加载

永远不要将大型数据集装入内存。 使用光标或抵消页码。 在ORMs 中, 允许懒惰的加载用于孩子关系, 但当需要时, 使用急速加载( 如 Active Record 或 [ [FLT: 11] ] 在 SQL 中)。

懒惰加载对易耗加载

选择正确的装载策略对性能至关重要:

  • Lazy Loging: 相关数据只有在访问时才会被载入。这对单实体操作有效,但可以在循环中降解性能(可怕的N+1问题).
  • Eager 载入 : 在单个查询中装入所有必要的关系。 当您知道视图或服务需要相关数据时使用。 许多ORM支持明确的急速装入或预测 。

一个实用的方法是默认急切地加载已知路径, 并且只使用懒惰加载很少访问的关联。 配置您数据库的查询时要以现实的加载来找到正确的平衡 。

水平缩放规划

当您的应用程序成长到单个服务器之外时,模型层必须支持分布:

  • 无状态模型:在模型实例中避免存储用户会话或请求特定数据. 使用依赖性注射来提供无国籍服务.
  • 有效序列化: 将跨网络运行的模型(例如通过JSON API)应当为快速序列化/去序列化设计. 使用DTO而不是带有循环引用的复杂对象图.
  • Database Sharding: 对于极其庞大的数据集,分区数据跨越多个数据库。您的寄存层应该抽象变硬逻辑,最好是基于聚合根的路由策略.
  • Evental Constitution: 在分布式系统中,避免将资源锁定在服务之间的分布式交易,而是使用事件和消息队列等事件驱动的规律来接受最终的一致性.

其他最佳做法

依赖性注射

使用依赖性注射容器来解决寄存器和服务依赖性。这种将模型构建与混凝土执行脱钩,使得将组件交换出来进行测试或缩放变得微不足道。

不可变易性

只要有可能, 设计值对象为不可变值。 不可变值类会减少与别名和货币有关的错误。 此外, 不可变值模型更容易测试和缓存 。

隔离测试

单位测试服务和域逻辑不需要数据库或框架拖曳。使用模拟存储器或内存执行。整合测试可以对照一个真正的数据库验证持久性行为,但保持目标。

反腐败层

在与遗留系统或外部API整合时,构建一个反腐败层,在您的模型和外部系统模型之间翻译。这可以防止外部变化渗入您的域中。

文件和守则审查

模型结构往往随着时间的推移变得不透明。 维护架构决策记录,并通过代码审查强制实施一致性。 一个记录齐全的模型在新团队成员上任或几个月后再次访问模块时会产生红利。

结论

构建MVC模式中可扩展性模型并不是一次性设计,而是持续进行中的学科。 通过坚持诸如分离关注、应用DDD和分层架构、明智地使用寄存器、服务和DTO等原则,您创建了能够随应用而增长的模型层。优化数据访问、选择正确的加载策略以及规划水平的扩展,确保您的应用仍然在负荷中运行。 记住每个建筑决策都涉及权衡-保持务实、衡量结果和节奏。

进一步探索时,请考虑研究[ Evans的域域设计书再分辨模式[。 这些资源为本文所讨论的模式提供了更深入的见解。