一个模块化服务上下文框架,帮助模块化组织各种服务逻辑,让服务间调用方式更加统一。
Ctx 是一个模块化服务上下文框架,帮助模块化组织各种服务逻辑,让服务间调用方式更加统一。
平时调用其他服务的方式一般是 new XyzService()
或则采用依赖注入
的方式实例化服务,前者会导致一个服务被实例化多次,服务提供方不能更好的进行控制,后者则会存在服务互相依赖的时候造成困扰,如果服务要单例的时候你还需要在服务提供者privider的地方声明服务为单例。
Ctx 提供了一种新的选择,每个模块都只会被实例化一次,每个模块的服务只会提供唯一的入口暴露给调用方,模块不支持多实例,这样可以让模块提供者可以更容易进行服务的控制和维护,所有的模块之间调用方式一致,如:
$this->ctx->模块名->方法()
同时Ctx提供了很方便的方式将模块方法rpc化,方便单独部署某些接口,比如某个接口频率特别高需要单独优化部署等或则这个接口需要进行保密,如加密算法等。
-
模块化开发:不同的模块只能操作自己的数据(包括数据库和缓存等),需要其他模块数据,只能让对应模块的开发人员提供接口,这样每个模块的人只需要了解自己模块的实现,减少复杂度和新人入职加入模块开发维护的难度(只需要了解他负责的模块),同时减少错误的其他模块数据操作,因为有的模块采用异步或则定时任务或则缓存的方式,直接操作对应模块,可能会因为不了解业务而导致数据操作遗漏带来脏数据。
-
Ctx单独成为项目进行部署:减少不同的项目共用逻辑的时候拷贝代码,如果是每个都采用微服务减少拷贝代码又会增加网络开销,所以只需要把ctx发布到需要的项目即可。所有的项目依赖共同的ctx服务,每个项目只负责参数的获取和ctx服务的调用组装实现业务逻辑,每个项目实现参数获取和响应输出,具体业务逻辑都要依赖 ctx 进行实现。为了方便ctx模块中的方法的共用,不建议直接把
$request
这样的请求对象作为参数传递给 ctx模块的方法,也就是不要在controller之外的地方进行输入参数的获取,而是用参数的方式传递给service。项目关系大概组织为:web项目文件夹 api项目文件夹 脚本文件夹 admin项目文件夹 ctx文件夹 (供上边所有项目共用)
composer require jetea/ctx -vvv
同时在
composer.json
中声明 ctx 服务项目目录 psr4 规则
"autoload": {
"psr-4": {
"Ctx\\": "ctx/"
},
},
在调用服务之前需要先编写服务,以下将描述一个服务的编写过程。源码参考参考:https://github.com/jetea/ctx/tree/master/tests/ctx
参考目录树结构如:
ctx 根文件夹
├── Ctx.php Ctx入口类
├── Basic/ 文件夹
│ └── Ctx.php 各个Ctx服务基类
└── Service/ Service文件夹,包括所有的服务模块
└── Example/ 服务模块1:Example模块
└── Ctx.php 服务模块入口
├── Child/ 模块子类文件夹
│ └── Demo.php 模块子类
└── Example2/ 服务模块2:Example2模块
├── Child/
│ └── Demo.php
└── Ctx.php
-
新建根文件夹,名称随意,如
ctx
-
编写Ctx入口类:
Ctx.php
,为Ctx服务唯一入口,所有的模块调度都要通过此类实现,此类为单例实现。参考https://github.com/jetea/ctx/blob/master/tests/ctx/Ctx.php<?php namespace Ctx; use Jetea\Ctx\Ctx as BasicCtx; /** * Class Ctx * @property \Ctx\Service\Example\Ctx $Example */ class Ctx extends BasicCtx { /** * ctx 实例 */ protected static $ctxInstance; //ctx 命名空间 protected $ctxNamespace = 'Ctx'; }
Note: 必须提供
$ctxInstance
静态属性和$ctxNamespace
属性,访问类型必须为protected
,目录下所有的类遵循PSR-4
标准,$ctxNamespace
决定了文件夹下的命名空间,$ctxInstance
和$ctxNamespace
这两个属性是为了支持多个ctx服务,只要确保ctx命名空间$ctxNamespace
值不同,如:ctx ctx根文件夹 ├── Ctx.php Ctx入口类 ├── Basic/ 文件夹 └── Service/ Service文件夹,包括所有的服务模块 ctx_1 ctx_1根文件夹 ├── Ctx.php Ctx入口类 ├── Basic/ 文件夹 └── Service/ Service文件夹,包括所有的服务模块
不过建议一个团队内尽量放到一个ctx服务中按照模块进行开发。
-
新增
Basic文件夹
,包含 所有的服务模块类的基类Ctx
,也可以放入公共的服务模块异常Exception
类等。 -
编写所有的服务模块类的基类
Basic/Ctx.php
,方便所有的模块类继承实现公共逻辑处理和复用,如所有的服务的rpc实现等,参考https://github.com/jetea/ctx/blob/master/tests/ctx/Basic/Ctx.php<?php namespace Ctx\Basic; use Jetea\Ctx\Basic\Ctx as BasicCtx; /** * Class Ctx * @package Ctx\Basic * * @property \Ctx\Ctx $ctx */ abstract class Ctx extends BasicCtx { protected function invokeRpc($method, $args) { // $this->rpc['host'], // $this->getModName(), // $method, // var_export($args, true) } }
所有的Service模块入口类都必须继承此类,模块子类可以选择性继承此类,所有继承了此类的服务类都会拥有
$ctx
属性,从而能在服务内简单的通过$this->ctx->模块->方法()
这样的方式来调用其他模块的方法。如果需要采用rpc将个别服务独立部署和优化等,可以在此类中通过实现invokeRpc
方法来实现具体的rpc调用逻辑,这样所有继承了此基类的方法均很容易实现rpc调用。 -
新建
Service文件夹
,此文件夹用于包含所有的模块具体实现。 -
新建模块文件夹,如
Example
,一般模块名跟业务有关,如User
表示用户服务模块,Payment
表示支付服务模块,此文件夹下将存放所有的此模块的具体实现。 -
编写Service模块入口类,如
Service/Example/Ctx.php
,此类为Example
模块入口,此类为单例实现,所有调用该模块的方法都要走此类进行调度当前模块下的方法的子类的方法,参考https://github.com/jetea/ctx/blob/master/tests/ctx/Service/Example/Ctx.php<?php namespace Ctx\Service\Example; use Ctx\Basic\Ctx as BasicCtx; /** * 模块接口声明文件 * 备注:文件命名跟模块中的其他类不同,因为模块入口类只能被实例化一次 * 也就是只能用ctx->模块 来实例化,不能用loadC来实例化更多 */ class Ctx extends BasicCtx { }
调用ctx服务指调用Service模块服务下的方法,只能调用到模块的入口类方法,这样的调用方式是为了更好的限制了所有服务调用都要走调用的模块下的入口类,方便统一进行处理,调用ctx服务分服务外部调用和服务内部调用:
-
Service模块服务内调用
$this->ctx->模块->方法()
-
Service模块服务外调用
\Ctx\Ctx::getInstance();
- 如果ctx服务的实例为
$ctx
$ctx->模块->方法()
- 如果ctx服务的实例为
-
如果服务类加载失败,多半是因为composer中没有声明Ctx服务模块需要的命名空间
-
所有的Service模块入口类都不允许实现
__construct
方法,如果需要初始化模块只能实现init
方法,所有的模块入口初始化的时候都会执行init方法,如$this->ctx->Example
将会实例User模块的入口类\Ctx\Service\Example\Ctx
,同时会调用其中的init
方法。 -
Ctx服务实例 和 模块入口类实例 都只会 有一个,即单例,方便模块进行更好的处理。
-
所有继承了 所有的服务模块类的基类 的类,都会拥有:
ctx
属性,此属性为ctx服务实例,方便在服务内调用其他的模块方法- 如果基类实现了
invokeRpc
方法,则所有继承的类都会拥有rpc实现。 loadC
方法,模块内加载其他类,模块类所有非入口类都需要放到Child
文件夹下通过模块内的loadC
方法进行实例化
-
所有模块内的非入口类无论是否继承 所有的服务模块类的基类,都能实现
__construct
方法,但是继承了基类的话一定会在实例化后,被调用init
方法。 -
模块入口类或模块子类rpc实现:
-
所有的服务模块类的基类 实现
invokeRpc
方法 -
模块入口类或模块子类继承 所有的服务模块类的基类
-
模块入口类或模块子类重载属性
$rpc
,属性访问方式为protected
,属性为数组,拥有两个字段host
和method
,其中host
表示rpc方法的远程host,method
表示允许执行的rpc方法数组。/** * rpc配置 */ protected $rpc = [ 'host' => '', //网关地址 'method' => [], //方法名 减少无用的远程调用 ];
-
# 用Ctx实现服务模块化组织
### 一般的代码组织
随便写,没有模块,不方便复用,大量的拷贝
### 传统的模块化的代码组织
* 写很多模块类,提高了代码复用
### 传统的模块类实例化方式
* 直接 new
* 依赖注入
> 调用方自己去实例化。。一个类被多次实例化,或则都要实现单例等
### ctx是什么
* Ctx 只是组织服务模块,不是业务框架,可以无缝引入到所有的框架中
* 服务模块化的组织,提高代码重用性
* Ctx 对ide友好,增加了代码的可维护性
* 每个服务入口统一且为单例,这样方便对应服务的开发者进行重构和逻辑调整,入口统一,对外统一,永远单例,支持child子模块非单例,子模块只能被模块内直接调用,从而避免直接被外部其他模块直接操作
* 支持rpc,只需要简单的配置,即可将方法切换为远程rpc调用,调用rpc就跟调用服务模块内的其他方法一样,调用方无感知,方便模块开发者随时抽离本地代码为远程调用的服务和多个节点部署进行扩容,
### 如何平滑接入
### 如何采用ctx平滑重构现有代码库