PHP中的领域驱动设计:如何将混乱变为有序
\n\n你是否曾感觉自己的PHP项目像雪球一样越滚越大?一开始一切都很简单:几个控制器、一个模型、几次数据库查询。但半年后,你陷入了意大利面条式代码,业务逻辑分散在各个服务中,一个“简单”的需求变更就要花费一周时间。
\n\n熟悉吗?欢迎加入这个俱乐部。但好消息是:领域驱动设计(DDD)——并非Eric Evans厚书中的抽象理论。它是一个实用的工具,能真正帮助你编写对业务和开发人员都易于理解的PHP代码。
\n\n在本文中,我们将结合实际案例,深入探讨DDD的实践应用。没有废话,只有PHP的鲜活示例。你将了解如何在不伤脑筋、不重写所有代码的情况下,将DDD引入你的项目。开始吧!
\n\n什么是领域驱动设计,PHP开发者为何需要它?
\n\n想象一下,你正在编写一个在线商店。有折扣、购物车、订单。业务逻辑是:“折扣不与优惠码叠加”、“订单发货后不可取消”。如果你将这些逻辑存储在控制器中或分散在ORM模型中,迟早会陷入混乱。
\n\nDDD是一种将业务领域(你的问题域)置于开发核心的方法。与其考虑表和SQL查询,不如思考实体、值和事件。
\n\n这对PHP尤为重要。这门语言很灵活,但如果没有清晰的架构,项目很快就会变成“遗留系统”。DDD提供了一个框架,即使一年后代码依然可读。
\n\n战略设计 vs 战术设计
\n\nDDD分为两大部分:
\n- \n
- 战略设计——项目的蓝图。你识别出限界上下文,构建通用语言。例如,在“目录”上下文中,商品只是一组属性;而在“订单”上下文中,它是一个带有价格和数量的单位。 \n
- 战术设计——构建块:实体、值对象、聚合、仓库、领域服务、领域事件。我们将重点关注这些。 \n
PHP中的战术设计:DDD的构建块
\n\n让我们看看DDD的主要模式在PHP中是如何呈现的。忘掉Laravel或Symfony中的ActiveRecord——这里我们构建一个独立于框架的模型。
\n\n1. 实体
\n实体是一个具有唯一标识符且随时间变化的对象。例如,User或Order。实体通过ID进行比较,而非字段。
<?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Domain\\Entity;\n\nuse App\\Domain\\ValueObject\\UserId;\nuse App\\Domain\\ValueObject\\Email;\n\nclass User\n{\n public function __construct(\n private UserId $id,\n private string $name,\n private Email $email\n ) {}\n\n public function getId(): UserId\n {\n return $this->id;\n }\n\n public function changeEmail(Email $newEmail): void\n {\n // 这里可以包含业务逻辑:检查邮箱是否已被占用\n $this->email = $newEmail;\n }\n\n public function getEmail(): Email\n {\n return $this->email;\n }\n}\n\n\n2. 值对象
\n值对象是一个不可变对象,由其属性而非标识符定义。两个值对象如果所有字段都相同,则相等。经典例子:Money、Email、Address。
<?php\n\ndeclare(strict_types=1);\n\nnamespace App\\Domain\\ValueObject;\n\nclass Email\n{\n private string $value;\n\n public function __construct(string $value)\n {\n if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {\n throw new \\InvalidArgumentException('Invalid email address');\n }\n $thi