译《领域驱动设计之PHP实现》架构风格(上)

为了构建复杂应用,一个关键点就是得有一个适合应用需求的架构设计。领域驱动设计的一个优势就是不必绑定到任何特定的架构风格之上。相反的,我们可以根据每个核心域内的限界上下文自由选择最佳的架构,限界上下文同时为每个特定领域问题提供了丰富多彩的架构选择。

例如,一个订单系统可以使用事件源(Event Sourcing)来追踪所有不同订单的操作;一个产品目录服务可以使用CQRS来暴露产品细节给不同客户端;一个内容管理系统可以使用一般的六边形架构来暴露如博客,静态页等服务。

从传统守旧派的 PHP 代码到更复杂先进的架构,本章将跟随这些历史来对 PHP 圈子内每个相关的架构风格做一些介绍。请注意尽管已经有许多其它存在的架构风格,例如数据网络架构(Data Fabric)或者面向服务架构(SOA),但我们发现从 PHP 的视角介绍它们还是有一些复杂的。

美好的旧时光

在 PHP4 发布之前 ,PHP还没有拥抱面向对象模式。那时候,写应用的普遍方法就是用面向过程和全局状态。像关注点分离(SoC)和模型-视图-控制器(MVC)的概念是与当时的 PHP 社区相抵触的。

下面的例子就是用传统方式写的一个由许多混合了 HTML 代码前端控制器构成的应用。在那个时代,基础设施层,表现层,UI,及领域层代码都交织在一起:

<?phpinclude __DIR__ . '/bootstrap.php';$link = mysql_connect('localhost', 'a_username', '4_p4ssw0rd');if (!$link) {
   die('Could not connect: ' . mysql_error());}mysql_set_charset('utf8', $link);mysql_select_db('my_database', $link);$errormsg = null;if (isset($_POST['submit'] && isValid($_POST['post'])) {
   $post = getFrom($_POST['post']);
   mysql_query('START TRANSACTION', $link);
   $sql = sprintf(
       "INSERT INTO posts (title, content) VALUES ('%s','%s')",
       mysql_real_escape_string($post['title']),
       mysql_real_escape_string($post['content']
       ));
   $result = mysql_query($sql, $link);
   if ($result) {
       mysql_query('COMMIT', $link);
   } else {
       mysql_query('ROLLBACK', $link);
       $errormsg = 'Post could not be created! :(';
   }}$result = mysql_query('SELECT id, title, content FROM posts', $link);?>
   <html>
   <head></head>
   <body>
   <?php if (null !== $errormsg) : ?>
       <div class="alert error"><?php echo $errormsg; ?></div>
   <?php else: ?>
       <div class="alert success">
           Bravo! Post was created successfully!        </div>
   <?php endif; ?>
   <table>
       <thead>
       <tr>
           <th>ID</th>
           <th>TITLE</th>
           <th>ACTIONS</th>
       </tr>
       </thead>
       <tbody>
       <?php while ($post = mysql_fetch_assoc($result)) : ?>
           <tr>
               <td><?php echo $post['id']; ?></td>
               <td><?php echo $post['title']; ?></td>
               <td><?php editPostUrl($post['id']); ?></td>
           </tr>
       <?php endwhile; ?>
       </tbody>
   </table>
   </body>
   </html><?php mysql_close($link); ?>

这种风格的代码就是我们常说的大泥球,在第一章我们也提及过。下面的代码就做一些改进,然而仅仅是通过封装headerfooter到单独的文件内,就可以避免重复及有利于重用:

<?phpinclude __DIR__ . '/bootstrap.php';$link = mysql_connect('localhost', 'a_username', '4_p4ssw0rd');if (!$link) {
   die('Could not connect: ' . mysql_error());}mysql_set_charset('utf8', $link);mysql_select_db('my_database', $link);$errormsg = null;if (isset($_POST['submit'] && isValid($_POST['post'])) {
   $post = getFrom($_POST['post']);
   mysql_query('START TRANSACTION', $link);
   $sql = sprintf(
       "INSERT INTO posts(title, content) VALUES('%s','%s')",
       mysql_real_escape_string($post['title']),
       mysql_real_escape_string($post['content'])
   );
   $result = mysql_query($sql, $link);
   if ($result) {
       mysql_query('COMMIT', $link);
   } else {
       mysql_query('ROLLBACK', $link);
       $errormsg = 'Post could not be created! :(';
   }}$result = mysql_query('SELECT id, title, content FROM posts', $link);?><?php include __DIR__ . '/header.php'; ?><?php if (null !== $errormsg) : ?>
   <div class="alert error"><?php echo $errormsg; ?></div><?php else: ?>
   <div class="alert success">
       Bravo! Post was created successfully!    </div><?php endif; ?>
   <table>
       <thead>
       <tr>
           <th>ID</th>
           <th>TITLE</th>
           <th>ACTIONS</th>
       </tr>
       </thead>
       <tbody>
       <?php while ($post = mysql_fetch_assoc($result)): ?>
           <tr>
               <td><?php echo $post['id']; ?></td>
               <td><?php echo $post['title']; ?></td>
               <td><?php editPostUrl($post['id']); ?></td>
           </tr>
       <?php endwhile; ?>
       </tbody>
   </table><?php include __DIR__ . '/footer.php'; ?>

现今,尽管这种方式令人沮丧,但仍有大量应用使用这种方式编写代码。这种风格的架构主要坏处是没有做到真正的关注点分离 – 维护和开发这样一个应用的持续成本与其它已知和已验证的架构相比急剧增长。

分层架构

从代码的可维护性和可重用性角度来看,使代码更容易维护的最好方式就是拆分的思想,即为每个不同的关注点分层。在我们之前的例子中,非常容易形成不同层次:一个是封装数据访问和操作,另一个是处理基础设施的关注点,最后一个即是封装前两者的编排。分层架构的一个基本原则就是-每一层都必须与其下一层紧密相连,如下图所示:

分层架构真正寻求的是对应用的不同组件进行分离。例如,在前面的例子当中,一个博客帖子的表示必须完全地独立于实体概念的博客帖子。一个博客帖子实体可以与一个或多个表示相关联。这就是通常所说的关注点分离。

另一种寻求相同目的的架构模式就是模型-视图-控制器模式。它最初被认为和广泛用于创建桌面 GUI 应用。现在主要应用于 web 应用。这得益于像 Symfony, Zend FrameworkCodeIgniter这些的流行框架。

模型-视图-控制器

模型-视图-控制器模式将应用划分为三个主要层次,要点描述如下: 1. 模型层:提取和集中所有领域模型的行为。这一层独立管理表现层的所有数据,逻辑及业务规则。所有说模型层是每个MVC应用程序的心脏和灵魂。 2. 控制层:即其他两层之间的抽象编排,主要是触发模型的行为来更新其状态,以及刷新与模型关联的表现层。除此之外,控制层还能发送消息给视图层来改变特定的领域表现形式。 3. 视图层:暴露模型层的不同表现形式,同时提供改变模型状态的一些触发动作。

分层架构的示例

模型层

继续之前的例子,我们注意到不同的关注点需要被分离。为了达到这一点,所有层次都必须从我们这些原始的混乱代码中识别出来。在这个过程中,我们需要特别注意与模型层有关的代码,即应用的核心代码:

class Post{
   private $title;
   private $content;   public static function writeNewFrom($title, $content)
   {
       return new static($title, $content);
   }   private function __construct($title, $content)
   {
       $this->setTitle($title);
       $this->setContent($content);
   }   private function setTitle($title)
   {
       if (empty($title)) {
           throw new RuntimeException('Title cannot be empty');
       }
       $this->title = $title;
   }   private function setContent($content)
   {
       if (empty($content)) {
           throw new RuntimeException('Content cannot be empty');
       }
       $this->content = $content;
   }}class PostRepository{
   private $db;   public function __construct()
   {
       $this->db = new PDO(
           'mysql:host=localhost;dbname=my_database',
           'a_username',
           '4_p4ssw0rd',
           [
               PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4',
           ]
       );
   }   public function add(Post $post)
   {
       $this->db->beginTransaction();
       try {
           $stm = $this->db->prepare(
               'INSERT INTO posts (title, content) VALUES (?, ?)'
           );
           $stm->execute([
               $post->title(),
               $post->content(),
           ]);
           $this->db->commit();
       } catch (Exception $e) {
           $this->db->rollback();
           throw new UnableToCreatePostException($e);
       }
   }}

模型层现在用一个Post类和一个PostRepository类定义。Post类表示一个博客帖子,PostRepository类表示可用博客帖子的整个集合。除此之外,另一层-用来协调和编排这些领域行为-也是模型层内需要的。现在进入应用层:

class PostService{
   public function createPost($title, $content)
   {
       $post = Post::writeNewFrom($title, $content);
       (new PostRepository())->add($post);
       return $post;
   }}

PostService类即我们所说的应用服务,它的目的是编排和组织领域行为。换句话说,应用服务是领域模型的直接客户端,是那些使业务发生的服务。没有其他类型的对象可以直接与模型层内部直接对话。

原文发布于微信公众号 - PHP技术大全(phpgod)

原文发表时间:2018-07-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券