为什么DDD不是枯燥的图表,而是鲜活的架构
\n当C++项目规模超出“玩具级”应用时,开发者常常面临混乱:业务逻辑分散在控制器中,数据库决定类结构,新手不敢触碰遗留代码。领域驱动设计(DDD)不是一套抽象规则,而是帮助驾驭复杂性的实用工具。在本文中,我们将通过具体代码示例,探讨如何在C++中引入DDD,以及为什么它能拯救你的项目免于变成“大泥球”。
\n\n什么是DDD,C++开发者为何需要它?
\n领域驱动设计是一种以领域模型为核心、而非技术细节为重点的方法。在C++中尤其重要:该语言提供了强大的工具(模板、RAII、智能指针),但如果没有正确的架构,项目就会变成单体,修改一个功能就会破坏一切。
\nDDD提供两个层次:
\n- \n
- 战略建模:将系统划分为多个上下文(Bounded Contexts)并配置它们之间的交互。 \n
- 战术建模:构建块——实体(Entity)、值对象(Value Object)、聚合(Aggregate)、仓储(Repository)、领域服务(Domain Service)。 \n
我们将重点放在战术建模上,因为它为C++代码提供了具体的模式。
\n\n分层架构:C++中DDD的基础
\nDDD要求清晰的层次划分。C++项目的典型结构如下:
\n- \n
- 领域层(核心):实体、值对象、聚合、领域服务。不依赖任何框架或外部库。 \n
- 应用层:用例(use cases)、编排。仅依赖领域层。 \n
- 基础设施层:仓储实现(SQL、Redis、文件)、与外部API交互。实现领域层中的接口。 \n
- 表示层:用户界面(控制台、REST API、GUI)。 \n
这不是硬性规则,而是建议。关键是依赖方向向内:外层可以依赖内层,反之则不行。
\n\n示例:错误做法
\n// ❌ 糟糕:业务逻辑与SQL混在一起\nclass UserController {\n void createUser(const std::string& name) {\n // SQL查询直接写在这里\n db.execute("INSERT INTO users (name) VALUES (?)", name);\n // 验证逻辑也在这里\n if (name.empty()) throw std::runtime_error("Name required");\n }\n};\n\n\n示例:DDD风格
\n// ✅ 良好:清晰分离\nclass User : public Entity<User, int> {\n std::string name_;\npublic:\n explicit User(int id, std::string name) : Entity(id), name_(std::move(name)) {\n if (name_.empty()) throw std::invalid_argument("User name cannot be empty");\n }\n const std::string& name() const { return name_; }\n};\n\n// 仓储接口(领域层)\nclass IUserRepository {\npublic:\n virtual ~IUserRepository() = default;\n virtual void save(const User& user) = 0;\n};\n\n// 实现(基础设施层)\nclass SqlUserRepository : public IUserRepository {\n void save(const User& user) override {\n // 仅SQL,无业务逻辑\n }\n};\n\n\nC++中的DDD构建块
\n\n实体(Entity)
\n这是具有唯一标识符的对象,该标识符随时间保持不变。在C++中,可以方便地创建一个基础模板类:
\ntemplate<typename T, typename IdType>\nclass Entity {\n IdType id_;\npublic:\n explicit Entity(IdType id) : id_(std::move(id)) {}\n IdType id() const { return id_; }\n bool operator==(const Entity& other) const { return id_ == other.id_; }\n