***
_我发现,我越是努力,就越发幸运。 -- Thomas Jefferson_
##2.17.1 微服务
![a pic](http://7qnay5.com1.z0.glb.clouddn.com/qq_20150607110405.jpg)
Martin Fowler(我喜欢和敬仰的大师)曾发表了上面这一段话。这段话也出现在了2015年QCon分享会上,并加了一张PPT“什么是微服务”加以说明。
![a pic](http://7qnay5.com1.z0.glb.clouddn.com/qcon_20150607.png)
里面提到了 **微服务** 这个概念,在PhalApi框架中即对应我们的Api接口服务层,只是我们不是称之为微服务,而是接口服务。
不管何种说法,我们都应该关注里面提及到的这几点重要特质:
+ 小,且专注于做一件事情
+ 独立的进程中
+ 轻量级的通信机制
+ 松耦合、独立部署
这里不过多地讨论微服务相关的分享,而是重温接口服务层Api与领域驱动和单元测试之间的关系,以及如何开发一个优雅、稳定又简单的接口。
##2.17.2 层级调用的顺序
整体上讲根据从Api接口层、Domain领域层再到Model数据源层的顺序进行开发。
在开发过程中,需要注意不能 **越层调用** 也不能 **逆向调用** ,即不能Api调用Model。而应该是 **上层调用下层,或者同层级调用** ,也就是说,我们应该:
+ Api层调用Domain层
+ Domain层调用Domain层
+ Domain层调用Model层
+ Model层调用Model层
如果用一张图来表示,则是:
![a pic](http://7qnay5.com1.z0.glb.clouddn.com/201506071124.png)
为了更明确调用的关系,以下调用是 **错误** 的:
+ 错误的做法1:Api层直接调用Model层
+ 错误的做法2: Domain层调用Api层,也不应用将Api层对象传递给Domain层
+ 错误的做法3: Model层调用Domain层
这样的约定,便于我们形成统一的开发规范,降低学习维护成本。
比如需要添加缓存,我们知道应该定位到Model层数据源进行扩展;若发现业务规则处理不当,则应该进入Domain层探其究竟;如果需要对接口的参数进行调整,即使是新手也知道应该找到对应的Api文件进行改动。
##2.17.3 接口服务的定义
现实项目开发过程中,绝大部分我们编写的接口是给别人使用的,或许给Android客户端同学使用,或者给iOS客户端同学使用,抑或提供给其他后台系统的同学使用。
为了提高并行开发的速度,我们不能等待接口完全开发完成后才提供接口文档,而且他们也不能忍受这么漫长的等待。
所以,客户端同学时常会问我们:什么时候可以提供接口文档?
我们提倡“接口先行”,如果有时不能很好地做到这一点(毕竟多变的需求促发多变的情境),我们可以快速提供接口的定义。
这有点像规约层对接口的定义一样,在PhalApi中定义一个接口,再具体一点即:
+ 1、创建一个接口服务类和声明函数
+ 2、配置接口参数规则
+ 3、提供接口返回格式的注释
简单来说,就是创建一个类,写个函数,定义参数和返回结果。
下面以 [开发实战3:一个简单的小型项目开发(奔跑吧兄弟投票活动)](http://git.oschina.net/dogstar/PhalApi-Demo-Vote) 中的团队参赛接口为例,说明这三步操作的过程。
###(1)创建一个接口服务类和声明函数
```javascript
//$ vim ./Vote/Api/Act.php
<?php
class Api_Act extends PhalApi_Api {
public function joinIn() {
}
}
```
###(2)配置接口参数规则
```javascript
<?php
class Api_Act extends PhalApi_Api {
public function getRules() {
return array(
'joinIn' => array(
'teamName' => array('name' => 'team_name', 'require' => true, 'min' => 1, 'max' => 100),
),
);
}
public function joinIn() {
}
}
```
###(3)提供接口返回格式的注释
```javascript
<?php
class Api_Act extends PhalApi_Api {
public function getRules() {
return array(
'joinIn' => array(
'teamName' => array('name' => 'team_name', 'require' => true, 'min' => 1, 'max' => 100),
),
);
}
/**
* 团队参赛接口
*
* @return int code 0,参赛成功;1,队名已存在
* @return int team_id 新建的团队ID
*/
public function joinIn() {
}
}
```
###(4)在线查看一下效果
在完成上面的动作后,我们可以通过在线工具来看下实时的效果,在浏览打开后访问:
```
http://api.vote.phalapi.com/vote/checkApiParams.php?service=Act.JoinIn
```
可以看到:
![a pic](http://7qnay5.com1.z0.glb.clouddn.com/201506071302.jpg)
到了这里,即使我们未完成接口的开发,也未提供更完善的接口文档,但接口客户端同学也可以根据这个在线的接口参数进行开发了。
##2.17.4 在ATDD下讲述故事
我们一直推崇测试驱动开发,但在对于Api接口开发,更准确来说是ATDD,即:验收测试驱动开发(Acceptance Test Driven Development)。
在前面Domain层文档中,我们提到了Api层是讲述故事的场景。因此,为了验证我们的业务场景是否正确,我们应该事先编写好单元测试,以不断引导我们前往正确的目的地。
我们可以使用脚本来快速生成测试骨架:
```javascript
$ pwd
$ /path/to/api.vote.phalapi.com/Vote/Tests/Api
$ phalapi-buildtest ../../Api/Act.php Api_Act ../test_env.php > Api_Act_Test.php
```
然后,稍微修改完善测试场景:
```javascript
/**
* @group testJoinIn
*/
public function testJoinIn()
{
//Step 1. 构建请求URL
$url = 'service=Act.JoinIn';
$params = array(
'sign' => 'phalapi',
'team_name' => 'test team name',
'user_id' => '1',
'token' => '193CE82D1F4588A9A168BDE6E6B83868B1464F523D16C05206F308E51EB91731',
);
DI()->notorm->team->where('team_name', $params['team_name'])->delete();
//Step 2. 执行请求
$rs = PhalApiTestRunner::go($url, $params);
//var_dump($rs);
//Step 3. 验证
$this->assertNotEmpty($rs);
$this->assertArrayHasKey('code', $rs);
$this->assertArrayHasKey('team_id', $rs);
$this->assertEquals(0, $rs['code']);
$this->assertGreaterThan(0, $rs['team_id']);
//create again
$rs = PhalApiTestRunner::go($url, $params);
$this->assertEquals(1, $rs['code']);
}
```
从上面测试的代码可以看出,我们先后进行了两次报名。明显地,第一次报名应该是成功的,第二次则应该提示不能重复报名。
单元测试的好处,不但在于可以引导我们做正确的事情,还可以提高我们的关注点,不致于在开发过程中被各种事务(如临时性的会议)打断后回来却不知刚才开发到哪了。
然而,更多的是为后期的维护、扩展提供可验证的业务场景。这点是很重要的。因为每一个测试场景,都保存了对应场景的模拟信息,这样不仅仅在后面的扩展,还是突如其来的BUGFIXED,我们都可以快速证明我们的修改是正确的,至少不会影响到原来的业务流程。
试想一下,如果原来可以下单支付的接口,突然被影响到而导致支付不成功,这是何等的损失!
我现在慢慢地,每当需要修改别人的代码时,我都会看下有没有对应的单元测试。如果没有,我会先补回,这样能增强我修改别人代码的信心。
##2.17.5 精益接口开发
传统的接口开发,由于没有很好的分层结构,而且热衷于在一个文件里面完成绝大部分事情,最终导致了臃肿漫长的代码,也就是通常所说的意大利面条式的代码。
在PhalApi中,我们针对接口领域开发,提供了新的分层思想:ADM(Api - Domain - Model)。
即便这样,如果项目在实际开发中,仍然使用原来的做法,纵使使用再好的接口开发框架,也还是会退化到原来的局面。
为了能让大家更为明确Api接口层的职责所在,我们建议:
###(1)Api接口层应该做:
+ 应该:对用户登录态进行必要的检测
+ 应该:控制业务场景的主流程,创建领域业务实例,并进行调用
+ 应该:进行必要的日志纪录
+ 应该:返回接口结果
###(2)Api接口层不应该做:
+ 不应该:进行业务规则的处理或者计算
+ 不应该:关心数据是否使用缓存,或进行缓存相关的直接操作
+ 不应该:直接操作数据库
+ 不应该:将多个接口合并在一起
在明确了上面应该做的和不应该做的,并且也完成了接口的定义,还有验收测序驱动开发的场景准备后,相信这时,即使是新手也可以编写出高质量的接口代码。
因为他会受到约束,他知道他需要做什么,主要他按照限定的开发流程和约定稍加努力即可。
如果真的这样,相信我们也就慢慢能体会到 **精益开发** 的乐趣。
最后,让我们一起来看下上述团队参赛接口开发的代码实现:
```javascript
/**
* 团队参赛接口
*
* @return int code 0,参赛成功;1,队名已存在
* @return int team_id 新建的团队ID
*/
public function joinIn() {
$rs = array('code' => 0, 'team_id' => 0);
DI()->userLite->check(true);
$domain = new Domain_Team();
if ($domain->isExists($this->teamName)) {
$rs['code'] = 1;
return $rs;
}
$teamId = $domain->joinIn($this->teamName);
$rs['team_id'] = $teamId;
return $rs;
}
```
可以看出,上面的代码短小达意,简单清晰。
- 欢迎使用PhalApi!
- 接口,从简单开始!
- [1.1]-下载与安装
- [1.2]-创建一个自己的项目
- [1.3]-在线体验
- [1.4]-文档、帮助和官网
- [1.10]-对PhalApi框架的抉择
- [1.11]-快速入门(backup)
- [1.12]-参数规则:接口参数规则配置
- [1.13]-统一的接口请求方式:_sevice=XXX.XXX
- [1.14]-统一的返回格式和结构:ret-data-msg
- [1.15]-数据库操作:基于NotORM的使用及优化
- [1.16]-配置读取:内外网环境配置的完美切换
- [1.17]-日记纪录:简化版的日记接口
- [1.18]-快速函数:人性化的关怀
- [1.19]-DI服务速查:各资源服务一览表
- [1.20]-DB操作:数据库基本操作速查
- [1.21]-类的自动加载:遵循PEAR包的命名规范
- [1.22]-签名验证:自定义签名规则
- [1.23]-请求和响应:GET和POST两者皆可得及超越JSON格式返回
- [1.24]-缓存策略:更灵活地可配置化的多级缓存
- [1.25]-国际化翻译:为走向国际化提前做好翻译准备
- [1.26]-数据安全:数据对称加密方案
- [1.27]-精益开发:更富表现力的Model层和重量级数据获取的应对方案
- [1.28]-COOKIE:对COOKIE原生态的支持及记忆加密升级版
- [1.29]-开放与封闭:多入口和统一初始化
- [1.30]-保持的力量:接口开发最佳实践
- [1.31]-新型计划任务:以接口形式实现的计划任务
- [2.11]-核心思想:DI依赖注入-让资源更可控
- [2.12]-海量数据:可配置的分库分表
- [2.13]-接口调试:在线SQL语句查看与性能优化
- [2.14]-测试驱动开发:意图导向编程下的接口开发
- [2.15]-演进:新型计划任务续篇
- [2.16]-领域驱动设计:应对复杂领域业务的Domain层
- [2.17]-微服务:Api接口服务层
- [2.18]-定制化:资源服务的再实现
- [2.19]-扩展库:可重用的扩展类库
- [2.20]-约定编程:架构明显的编程风格
- [2.21]-服务器统一部署方案简明版:CentOs---Nginx---php-fpm---MySql-[--Memcached]
- [2.22]-更多工具:精益项目和团队建设
- [3.1]-扩展类库:微信开发
- [3.2]-扩展类库:代理模式下phprpc协议的轻松支持
- [3.3]-扩展类库:基于PHPMailer的邮件发送
- [3.4]-扩展类库:优酷开放平台接口调用
- [3.5]-扩展类库:七牛云存储接口调用
- [3.6]-扩展类库:新型计划任务
- [3.8]-扩展类库:用户、会话和第三方登录集成
- [3.9]-扩展类库:swoole支持下的长链接和异步任务实现
- [3.11]-扩展类库:基于FastRoute的快速路由
- [4.2]-开发实战2:模拟优酷开放平台接口项目开发
- [4.3]-开发实战3:一个简单的小型项目开发(奔跑吧兄弟投票活动)
- [5.1]-架构与思想:PhalApi核心设计和思想解读
- [5.2]-杂谈:扯一些PhalApi的前世和今生
- [5.3]-框架总结:术语表和PHP开发建议
- [5.4]-许可
- [5.5]-联系和加入我们
- [5.6]-更新日记
- [5.8]-致框架贡献者:加入PhalApi开源指南
- [6.1]-基于接口查询语言的SDK包
- [6.2]-SDK包(JAVA版)
- [6.3]-SDK包(PHP版)
- [6.4]-SDK包(Objective-C版)
- [6.5]-SDK包(javascript版)
- [6.6]-SDK包(Ruby版)
- [8.1]-PhalApi视频教程
- 附录1:接口文档参考模板