前言
上一篇我们聚焦商品子域,讲解了如何使用事件风暴做领域建模。接下来,我们聊一聊DDD的分层架构。
对于构架一个复杂的应用,设计一个适合应用需求的,同时具备”高内聚,低耦合”理念的分层架构,能够是的各层的边界清晰而职责分明。而DDD 的分层架构又是怎么样的呢?
从两层架构到DDD分层架构
软件架构发展大体可以分为三个阶段:单体两层架构 -> 集中式三层架构 -> 分布式微服务架构
第一阶段的单体两层架构,分层方式是表现层和数据库两层,常常采用的是面向过程的设计方法。
两层架构 – 旧日的大泥球时代
早期的PHPer,在PHP4发布之前,还没使用面向对象模式,表现层、业务逻辑和数据库访问都是交织在一起的。我们来看看下面的例子1-1:
<?php
$conn = mysql_connect('localhost', 'username', 'password');
if (!$conn) {
die('Could not connect: ' . mysql_error());
}
mysql_set_charset('utf8', $conn);
mysql_select_db('databaseName', $conn);
$result = mysql_query('SELECT id, name, img_url FROM product', $conn);
?>
<html>
<head></head>
<body>
<table>
<thead>
<tr>
<th>商品ID</th>
<th>商品名称</th>
<th>商品图片</th>
</tr>
</thead>
<tbody>
<?php while ($product = mysql_fetch_assoc($result)) : ?>
<tr>
<td><?php echo $product['id']; ?></td>
<td><?php echo $product['name']; ?></td>
<td>
<img src="<?php echo $product['img_url']; ?>" />
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</body>
</html>
<?php mysql_close($conn); ?>
稍微好一点的开源项目(比如大名鼎鼎的ECShop),也只是单纯地利用include引入一些公共的基础设施文件做模块化复用。这种两层的分层架构风格,就是大泥球风格,这样分层方式的应用的会造成维护和开发成本持续增长。
后来随着Web应用的发展,PHP也因为有类似Zend Framework、Yii、ThinkPHP等框架的兴起流行,大家渐渐熟悉了MVC(模型-视图-控制器)模式,开发效率显著提升,维护成本也随之下降。
再来看看下面这个例子1-2:
1 2 3 4 5 6 7
| class ProductModel extends \yii\db\ActiveRecord { public static function tableName() { return 'product'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class ProductController extends \yii\web\Controller { public function actionSaveProduct() { // 业务接入 $id = \Yii::$app->request->post('id'); $title = \Yii::$app->request->post('title'); if (empty($title)) { throw new Exception('标题不能为空'); } \Yii::$app->response->format = Response::FORMAT_JSON;
$product = new ProductModel();
// 业务逻辑 if (strlen($title) > 10) { $product->title = substr($title, 0, 10); }
// 数据库访问 if ($product->save()) { return $this->render('save-product-success'); } return $this->render('save-product-fail'); } }
|
虽然有了MVC模式,上面代码依然是两层架构,因为业务接入、业务逻辑处理和数据库访问让仍然交织在一个controller的action方法中,久而久之也会发展成一个大泥球controller。
集中式的三层架构
早期的MVC思想只能解决表现层与业务逻辑耦合的问题,仍然无法解决两层模式导致软件发展成大泥球。于是,渐渐有了集中式的三层架构模式。
我们来看看下面例子2-1
1 2 3 4 5 6 7
| class ProductModel extends \yii\db\ActiveRecord { public static function tableName() { return 'product'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class ProductRepository { public function add($title, $imgUrl) { $product = new ProductModel(); $product->title = $title; $product->img_url = $imgUrl; return $product->save(); }
public function update($id, $title, $imgUrl) { $product = $this->getById($id); $product->title = $title; $product->img_url = $imgUrl; return $product->update(); }
public function getById($id) { return ProductModel::findOne($id); }
// ... }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class ProductService { /** * @ProductRepository $productRepository */ protected $productRepository; public function __construct(ProductRepository $productRepository) { $this->productRepository = $productRepository; } public function saveProduct($id, $title, $imgUrl) { if (strlen($title)) { $title = substr($title, 0, 10); } $product = $this->productRepository->getById($id); if ($product == null) { return $this->productRepository->add($title, $imgUrl); } return $this->productRepository->update($id, $title, $imgUrl); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class ProductController extends \yii\web\Controller { public function actionSaveProduct() { $id = \Yii::$app->request->post('id'); $title = \Yii::$app->request->post('title'); $imgUrl = \Yii::$app->request->post('img_url');
$productService = new ProductService(); if ($productService->saveProduct($id, $title, $imgUrl)) { return $this->render('ok'); } return $this->render('false'); }
}
|
集中式的三层架构,分别为:业务接入层、业务逻辑层和数据库访问层。
业务接入层用于处理异常、逻辑跳转控制、页面渲染模型等,又被称为mvc层(Model View Controller)。
服务层 用于对应用业务逻辑处理;
数据访问层 用于定义数据访问接口,实现对真实数据库的访问服务;
目前大多数流行的PHP框架都采用这种集中式的三层架构进行开发,也就是controller/service/repository
三者的调用关系为controller调用service完成业务逻辑处理,service调用repository完成数据访问,是不是似曾相识,我想你一定很熟悉了。
但是这种三层架构弊端在于,随着业务发展,Service层会越来越臃肿且业务耦合严重,在仓储模式下,Repository层会实现大量的各业务耦合一起的数据库访问的方法,拆分难,扩展性差,同时会导致实体的失血模型。
因为一旦更换数据库,就可能需要重写大部分的代码,这对应用来说是致命的
DDD 的分层架构
在单机和集中式架构这两种模式下,软件无法快速响应需求和业务的迅速变化,最终错失发展良机,这时候分布式微服务架构登场。
分布式微服务架构也有很多种,包括命令查询职责分离(CQRS)、六边形架构、洋葱架构、整洁架构以及DDD 分层架构等,各种架构的理念都是为了设计出”高内聚、低耦合”的架构。
DDD的分层架构包括:用户接口层、应用层、领域层和基础层
一层对应的职责:
1.用户接口层
用户接口层负责向用户显示信息和解释用户指令。这里的用户可能是:用户、程序、自动化测试和批处理脚本等等,有点类似Controller的作用。
2.应用层
应用层是很薄的一层,理论上不应该有业务规则或逻辑,主要面向用例和流程相关的操作。在领域层智商,协调聚合服务组合和编排。
3.领域层
实现领域的核心业务逻辑。这一层聚集了领域模型的聚合、聚合根、实体、值对象、领域服务和事件等领域对象,以及它们组合所形成的业务能力。
4.基础层
贯穿所有层,为各层提供基础资源服务。这一层聚集了各种底层资源相关的服务和能力,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。
DDD 分层架构有一个重要的原则:每层只能与位于其下方的层发生耦合。
相比较三层架构模式,DDD 分层架构对三层架构的业务逻辑层进行了更清晰的划分,改善了三层架构核心业务逻辑混乱,代码改动相互影响大的情况。
基础层的依赖倒置实现
传统的软件分层思想中,上层代码依赖于下层代码,当下层出现变动时,上层代码也要相应变化,维护成本较高。而依赖倒置的核心思想是上层定义接口,下层实现这个接口,从而使得下层依赖于上层,降低耦合度,提高整个系统的弹性。这是一种经实践证明的有效策略。
依赖倒置的原则
高层次模型不应该依赖于低层次模型。它们都应该依赖于抽象。
抽象不应该依赖于细节,细节应该依赖于抽象。
– Robert C.Martin
继续例子2-1的示例代码,我们把ProductRepository改造成接口,按照”依赖于抽象”的原则,接口本身就是一种抽象。
1 2 3 4 5 6
| interface ProductRepository { public function getById($id); public function add($title, $imgUrl); public function udpate($id, $title, $imgUrl); }
|
这个ProductRepository接口,暴露了有关商品的一些方法。现在来为它添加适配器,用于实现商品的一下具体操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class PDOProductRepository implements ProductRepository { public function add($title, $imgUrl) { $product = new ProductModel(); $product->title = $title; $product->img_url = $imgUrl; return $product->save(); }
public function update($id, $title, $imgUrl) { $product = $this->getById($id); $product->title = $title; $product->img_url = $imgUrl; return $product->update(); }
public function getById($id) { return ProductModel::findOne($id); } }
|
只要我们定义了接口和实现,对于ProductService,通过依赖注入(Dependency Injection)来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13
| class ProductService { /** * @ProductRepository $productRepository */ protected $productRepository; public function __construct(ProductRepository $productRepository) { $this->productRepository = $productRepository; } // ... }
|
从上面代码可以看出,如果当商品有必要更换存储方式,或者其他场景的仓储,只需要在初始化ProductService时(一般地在controller的action中)构造函数传入特定的ProductRepository接口实现,即可更换切换。
可见,通过使用依赖倒置原则,基础层 现在用户接口层,应用层和领域层这些高层次。于是依赖被倒置了,这样做可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。
总结一下
本篇介绍了从两层架构到DDD 分层架构的软件分层架构演进,利用代码体验了不同分层架构的优点,最后利用PHP代码实现了DDD分层架构的基础层依赖导致的原理。下一期将进入DDD的战术设计,看看DDD指导我们的分层架构落地后的代码模型。
本文作者:CIO之家的朋友 来源:CIO之家的朋友们
CIO之家 www.ciozj.com 微信公众号:imciow