资讯专栏INFORMATION COLUMN

Swoft| Swoft 框架组件化改造

desdik / 660人阅读

摘要:框架组件化改造框架从单体应用到组件化改造的架构升级之路经过一年多的开发框架功能越来越完善也越来越复杂初创时期的单体应用已经无法支撑项目的快速发展于是开发组在年前为版制定了组件化改造的重构方案内容速览组件化原理包管理基础知识组件化方案来

date: 2018-3-21 13:22:16
title: Swoft| Swoft 框架组件化改造
description: Swoft 框架从单体应用到组件化改造的架构升级之路

经过一年多的开发, Swoft 框架功能越来越完善, 也越来越复杂. 初创时期的 单体应用, 已经无法支撑项目的快速发展, 于是开发组在年前为 1.0-beta 版制定了 组件化改造 的重构方案.

内容速览:

组件化原理: PHP 包管理基础知识

组件化方案: 来自 laravel/symfony 等成熟框架的组件化实现方案

Swoft 框架组件化实现

组件化原理

编程始终要解决的一个问题: 代码复用. 好的代码, 基本要求是 正确, 能拿到预期的结果, 少 bug. 语言层的代码复用解决方案, 通常称之为 包管理(或者 依赖管理). 流行的编程语言, 都提供了很好的工具链对包管理的支持:

一个命令行工具, 用来 获取/管理 包, 比如 php 的 composer, python 的 pip, js 的 npm, java 的 maven, go 的 go get

一个包管理的配置文件, 用来说明需要用到(依赖)的包, 比如 PHP 中 composer 使用 composer.josn, js 的 npm 使用 package.json

一个浏览包的网站, 用来查看包的信息, 比如 php 的 packgist, python 的 pypi 等

这样, 当我们需要不同功能的时候, 就可以去查看是否有包已经提供了类似功能, 不用重复造轮子, 站在巨人的肩膀上.

回到 PHP 中, PHP中的包管理是如何实现的呢?

命名空间

首先需要知道的一个基础概念, 是 命名空间. 引入命名空间是为了 解决同名冲突 -- 2 个包中有名字相同的类, 同时使用时就会出现类重复定义的提示. 使用命名空间后, 因为不同的包有不同的命名空间, 就不会出现冲突.

// 如果需要在同一个文件中使用相同名字的类, 使用别名
use AFar;
use BFar as BFar;

自动加载 & PSR4

第二个需要知道的基础概念, 是 自动加载. PHP 中最基础(或者说最原始)的复用代码的方法: include/include_one/require/require_once. 不过得益于 PHP 的 SPL库 中的 spl_autoload_register() 方法, 现在有了更优雅的方式来复用代码 -- 自动加载. 自动加载的规范也经历了一段时间的升级与打磨, 最新的是 PSR4标准.

关于自动加载, 有一个很好的教程:  5-1 SPL使用spl_autoload_register函数装载类 (10:03)
composer 中的包管理

了解了基础知识后, 就可以来掌握工具怎么用了. composer 中的包管理 根据 composer.json 文件中的 autoload / require / require-dev 配置项来管理.

autoload 定义自动加载, 项目自身的代码, 也应该按照包管理的规范, 进行组织, 比如 Swoft 的 composer.json 配置文件:

...
    "autoload": {
        "psr-4": {
            "App": "app/"
        },
        "files": [
            "app/Swoft.php"
        ]
    },
...

composer 支持多种方式的自动加载方式, 这里面有一定的历史原因, 因为需要兼容一些 陈旧 的代码. 现在比较常用的 2 种方式:

psr-4: PSR4 标准, 优先推荐的方式

files: 直接加载文件, 通常用来加载 帮助函数, 类似于 PHP 的 require 语法来代码复用

require 标识需要依赖的包, 格式是 包名 - 版本限制 的键值对:

...
    "require": {
        "php": ">=7.0",
        "swoft/framework": "^1.0",
        "swoft/rpc": "^1.0",
        "swoft/rpc-server": "^1.0",
        "swoft/rpc-client": "^1.0",
        "swoft/http-server": "^1.0",
        "swoft/http-client": "^1.0",
        "swoft/task": "^1.0",
        "swoft/http-message": "^1.0",
        "swoft/view": "^1.0",
        "swoft/db": "^1.0",
        "swoft/cache": "^1.0",
        "swoft/redis": "^1.0",
        "swoft/console": "^1.0",
        "swoft/devtool": "^1.0",
        "swoft/session": "^1.0",
        "swoft/i18n": "^1.0",
        "swoft/process": "^1.0",
        "swoft/memory": "^1.0",
        "swoft/service-governance": "^1.0"
    },
...

关于 版本控制 的知识, 以及 >= ^ ~ 等特殊字符, alpha beta dev dev-master 等标识, 只是约定俗成的一些定义, 了解清楚即可.

require-dev 标识开发环境需要依赖的包, 即正式环境不需要使用到的包, 比如单元测试等:

...
    "require-dev": {
        "eaglewu/swoole-ide-helper": "dev-master",
        "phpunit/phpunit": "^5.7"
    },
...

类似的, 还有 autoload-dev, 表示测试环境下使用到自动加载.

组件化方案: laravel 与 symfony 使用的方案

参考 symfony 中的 composer.json 配置文件 和 laravel 中的 composer.json 配置文件, 会发现里面有一个配置项: replace.

replace 这个配置项, 在普通项目中很难看到, 却是组件化改造中的重要配置, 它的定义如下:

使用项目中已有的包, 替换需要依赖的包

比如 symfony 中的 composer.json 配置文件:

...
    "replace": {
        "symfony/asset": "self.version",
        "symfony/browser-kit": "self.version",
        "symfony/cache": "self.version",
        "symfony/config": "self.version",
        "symfony/console": "self.version",
        "symfony/css-selector": "self.version",
        "symfony/dependency-injection": "self.version",
        ...

其中 "symfony/asset" 包, 有一个多带带的github 仓库 symfony/asset, symfony 项目 本身也包含 "symfony/asset" 包, 使用 replace, symfony 就可以使用自身包含的包, 不用去多带带获取.

这样带来的好处:

主包包含所有的子包, 使用时使用 replace 配置, 所有的修改和提交都在主包中进行

其他项目依旧可以使用 require, 多带带使用子包; 子包只接受来自主包分发来的代码, 不接受在子包上的更改

Swoft 框架组件化实现

Swoft 在 1.0-beta版中的依赖, Swoft 项目:

...
    "require": {
        "php": ">=7.0",
        "swoft/framework": "^1.0",
        "swoft/rpc": "^1.0",
        "swoft/rpc-server": "^1.0",
        "swoft/rpc-client": "^1.0",
        "swoft/http-server": "^1.0",
        "swoft/http-client": "^1.0",
        "swoft/task": "^1.0",
        "swoft/http-message": "^1.0",
        "swoft/view": "^1.0",
        "swoft/db": "^1.0",
        "swoft/cache": "^1.0",
        "swoft/redis": "^1.0",
        "swoft/console": "^1.0",
        "swoft/devtool": "^1.0",
        "swoft/session": "^1.0",
        "swoft/i18n": "^1.0",
        "swoft/process": "^1.0",
        "swoft/memory": "^1.0",
        "swoft/service-governance": "^1.0"
    },
...

改造后, Swoft 项目, 主项目只用依赖 "swoft/framework":

...
    "require": {
        "php": ">=7.0",
        "swoft/framework": "^1.0"
    },
...

"swoft/framework" 项目, 包含其他子包:

...
    "replace": {
        "swoft/rpc": "self.version",
        "swoft/rpc-server": "self.version",
        "swoft/rpc-client": "self.version",
        "swoft/http-server": "self.version",
        "swoft/http-client": "self.version",
        "swoft/task": "self.version",
        "swoft/http-message": "self.version",
        "swoft/view": "self.version",
        "swoft/db": "self.version",
        "swoft/cache": "self.version",
        "swoft/redis": "self.version",
        "swoft/console": "self.version",
        "swoft/devtool": "self.version",
        "swoft/session": "self.version",
        "swoft/i18n": "self.version",
        "swoft/process": "self.version",
        "swoft/memory": "self.version",
        "swoft/service-governance": "self.version"
    }
...

其中子项目声明到主项目提交修改:

Report issues and send Pull Requests in the main Swoft repository

整个开发流程如下:

在 daydaygo/swoft-framework 项目 新建 component2 分支开发此次组件化改造

修改 Swoft 项目的 composer.json 文件, 快速获取所有 Swoft 组件的 master 分支代码:

    "require": {
        "php": ">=7.0",
        "swoft/framework": "dev-master",
        "swoft/rpc": "dev-master",
        "swoft/rpc-server": "dev-master",
        "swoft/rpc-client": "dev-master",
        "swoft/http-server": "dev-master",
        "swoft/http-client": "dev-master",
        "swoft/task": "dev-master",
        "swoft/http-message": "dev-master",
        "swoft/view": "dev-master",
        "swoft/db": "dev-master",
        "swoft/cache": "dev-master",
        "swoft/redis": "dev-master",
        "swoft/console": "dev-master",
        "swoft/devtool": "dev-master",
        "swoft/session": "dev-master",
        "swoft/i18n": "dev-master",
        "swoft/process": "dev-master",
        "swoft/memory": "dev-master",
        "swoft/service-governance": "dev-master"
    },

复制各个组件的代码到 swoft-framework 项目中, 修改 composer.json 的中的 autoload / replace 配置(具体修改点击链接查看)

Swoft 各组件依赖关系图: http://naotu.baidu.com/file/7...

提交 swoft-framework 代码.

下面以推送 swoft-view 组件到对应仓库中为例:

出于 github 网速的原因, 测试过程使用 gitee 来加速

推送子项目到相应的 github 仓库中, 参考:

dflydev/git-subsplit: 封装 git subtreegit subplite 命令, 方便使用

laravel 中使用 git subsplit 的示例](https://github.com/laravel/fr...

# 建立 gitee.com:daydaygo/swoft-framework 仓库
git remote add gitee git@gitee.com:daydaygo/swoft-framework.git
git push gitee component2

# 拆分
git subsplit init git@gitee.com:daydaygo/swoft-framework.git
# 更多项目, 一次填写即可
git subsplit publish --heads="component2" --no-tags view:git@gitee.com:daydaygo/swoft-view.git
# 清除生成的临时文件
rm .subsplit

这个拆分过程耗时较长, 拆分后的效果: gitee.com/daydaygo/swoft-view, gitee.com/daydaygo/swoft-framework

可以通过添加 github webhook 来做自动化, 具体请参考: dflydev/dflydev-git-subsplit-github-webhook

最后, 测试拆分后的代码:

修改 swoft 项目的 composer.json 文件, 使用新版的 swoft-framework 项目

...
  // 现在只需要依赖 swoft/framework, 版本号要制定分支, composer 会默认给分支名带上 dev- 前缀
  "require": {
    "php": ">=7.0",
    "swoft/framework": "dev-component2"
  },
....
  "repositories": {
    "packagist": {
      "type": "composer",
      "url": "https://packagist.phpcomposer.com"
    },
    // 制定包的地址, 这里指向我的 giee 仓库地址
    "0": {
      "type": "vcs",
      "url": "https://gitee.com/daydaygo/swoft-framework"
    }
  }
...
# 删除以前的依赖
rm -rf composer.lock vendor
# 更新
composer install --no-dev

运行测试, 可以参考项目下 .travis.yml 文件

至此, 大功告成.

写在最后

对项目进行组件化拆分, 推送子包到不同 github 仓库 这样的需求, 也许只有写一个大型框架才会遇到. 但这也是正是动手写一个框架的乐趣所在. PHP 中的包管理的基础知识一直感觉 游刃有余, 直到遇到新的问题, 提出新的挑战, 才发现还有更多的天地. 愿你也能感受到这分技术的乐趣.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28459.html

相关文章

  • Swoft 框架 1.0 正式来袭,首个基于 Swoole 原生协程的 PHP 框架

    摘要:历时年多紧锣密鼓的开发,以及愉快而忙碌的春节假期,期间数从到快破,码云首页推荐,作者和社区的大力支持,正式版终于要和大家见面。此次更新新增了大量特性在易用性代码复用性能方面都有所提升。可以用于构建高性能的系统中间件基础服务等等。 历时 1 年多紧锣密鼓的开发,以及愉快而忙碌的春节假期,期间 github star 数从 500 到快破 1k,码云首页推荐,Swoole作者 Rango ...

    weij 评论0 收藏0
  • 使用 Docker / Docker Compose 部署 Swoft 应用

    摘要:所以呢,为了节省我们的时间,官方提供了一个镜像包,里面包含了运行环境所需要的各项组件我们只需要下载镜像并新建一个容器,这个容器就提供了框架所需的所有依赖和环境,将宿主机上的项目挂载到镜像的工作目录下,就可以继续我们的开发或生产工作了。 Swoft 首个基于 Swoole 原生协程的新时代 PHP 高性能协程全栈框架,内置协程网络服务器及常用的协程客户端,常驻内存,不依赖传统的 PHP-...

    gplane 评论0 收藏0
  • 使用 Docker / Docker Compose 部署 Swoft 应用

    摘要:所以呢,为了节省我们的时间,官方提供了一个镜像包,里面包含了运行环境所需要的各项组件我们只需要下载镜像并新建一个容器,这个容器就提供了框架所需的所有依赖和环境,将宿主机上的项目挂载到镜像的工作目录下,就可以继续我们的开发或生产工作了。 Swoft 首个基于 Swoole 原生协程的新时代 PHP 高性能协程全栈框架,内置协程网络服务器及常用的协程客户端,常驻内存,不依赖传统的 PHP-...

    chaos_G 评论0 收藏0
  • Swoft 系列教程:(1)使用 Docker 安装部署 Swoft

    摘要:即异步非阻塞,,事件驱动。优雅的注解声明,容器,严格遵循规范。镜像的主要用途官方提供了基于的镜像。镜像中已安装配置好运行的所需组件及依赖。修改镜像的使得容器启动时不同时启动服务,这就不需要要求我们挂载的本地项目必须完全安装好依赖了。 之前有写过一篇 Docker 安装部署 Swoft 的文章,但有些冗余混乱,故重写作为教程的开篇。要不读读看? Swoft项目:https://gith...

    宋华 评论0 收藏0
  • Swoft 系列教程:(1)使用 Docker 安装部署 Swoft

    摘要:即异步非阻塞,,事件驱动。优雅的注解声明,容器,严格遵循规范。镜像的主要用途官方提供了基于的镜像。镜像中已安装配置好运行的所需组件及依赖。修改镜像的使得容器启动时不同时启动服务,这就不需要要求我们挂载的本地项目必须完全安装好依赖了。 之前有写过一篇 Docker 安装部署 Swoft 的文章,但有些冗余混乱,故重写作为教程的开篇。要不读读看? Swoft项目:https://gith...

    psychola 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<