凤凰框架笔记之RestFUl优化案例

医生预约系统

医生预约系统
作为一名病人,我想要从系统中得知指定日期内我熟悉的医生是否具有空闲时间,以便于我向该医生预约就诊。

传统的RPC编写方式

  • 医院开放了一个/appointmentService的 Web API,传入日期、医生姓名作为参数,可以得到该时间段该名医生的空闲时间,该 API 的一次 HTTP 调用如下所示:

    POST /appointmentService?action=query HTTP/1.1
    
    {date: "2020-03-04", doctor: "mjones"}
    
  • 然后服务器会传回一个包含了所需信息的回应:

    HTTP/1.1 200 OK
    [
    	{start:"14:00", end: "14:50", doctor: "mjones"},
    	{start:"16:00", end: "16:50", doctor: "mjones"}
    ]
    
  • 得到了医生空闲的结果后,我觉得 14:00 的时间比较合适,于是进行预约确认,并提交了我的基本信息:

    POST /appointmentService?action=confirm HTTP/1.1
    
    {
    	appointment: {date: "2020-03-04", start:"14:00", doctor: "mjones"},
    	patient: {name: icyfenix, age: 30, ……}
    }
    
  • 如果预约成功,那我能够收到一个预约成功的响应:

    HTTP/1.1 200 OK
    
    {
    	code: 0,
    	message: "Successful confirmation of appointment"
    }
    
  • 如果发生了问题,譬如有人在我前面抢先预约了,那么我会在响应中收到某种错误信息:

    HTTP/1.1 200 OK
    
    {
    	code: 1
    	message: "doctor not available"
    }
    

    到此,整个预约服务宣告完成

引入资源的概念

第 0 级是 RPC 的风格,如果需求永远不会变化,也不会增加,那它完全可以良好地工作下去。

  • 但是,如果你不想为预约医生之外的其他操作、为获取空闲时间之外的其他信息去编写额外的方法,或者改动现有方法的接口,那还是应该考虑一下如何使用 REST 来抽象资源。

我们可以吧医生的信息看做资源,每个资源存在一个id。通过拼接url来完成参数的命中:

POST /doctors/mjones HTTP/1.1

{date: "2020-03-04"}

然后服务器传回一组包含了 ID 信息的档期清单,注意,ID 是资源的唯一编号,有 ID 即代表“医生的档期”被视为一种资源:

HTTP/1.1 200 OK

[
	{id: 1234, start:"14:00", end: "14:50", doctor: "mjones"},
	{id: 5678, start:"16:00", end: "16:50", doctor: "mjones"}
]

我还是觉得 14:00 的时间比较合适,于是又进行预约确认,并提交了我的基本信息:

POST /schedules/1234 HTTP/1.1

{name: icyfenix, age: 30, ……}

比起第 0 级,第 1 级的特征是引入了资源,通过资源 ID 作为主要线索与服务交互,但第 1 级至少还有三个问题并没有解决:

  • 一是只处理了查询和预约,如果我临时想换个时间,要调整预约,或者我的病忽然好了,想删除预约,这都需要提供新的服务接口。
  • 二是处理结果响应时,只能靠着结果中的code、message这些字段做分支判断,每一套服务都要设计可能发生错误的 code,这很难考虑全面,而且也不利于对某些通用的错误做统一处理;
  • 三是并没有考虑认证授权等安全方面的内容,譬如要求只有登陆用户才允许查询医生档期时间,某些医生可能只对 VIP 开放,需要特定级别的病人才能预约,等等。

引入统一接口,映射 HTTP 协议

正对于上面的三个问题提出解决方案:

  • REST 的做法是把不同业务需求抽象为对资源的增加、修改、删除等操作来解决第一个问题;
  • 使用 HTTP 协议的 Status Code,可以涵盖大多数资源操作可能出现的异常,而且 Status Code 可以自定义扩展,以此解决第二个问题;
  • 依靠 HTTP Header 中携带的额外认证、授权信息来解决第三个问题,这个在实战中并没有体现,请参考安全架构中的“凭证”相关内容。

按这个思路,获取医生档期,应采用具有查询语义的 GET 操作进行:

GET /doctors/mjones/schedule?date=2020-03-04&status=open HTTP/1.1
  • 然后服务器会传回一个包含了所需信息的回应:
    HTTP/1.1 200 OK
    
    [
    	{id: 1234, start:"14:00", end: "14:50", doctor: "mjones"},
    	{id: 5678, start:"16:00", end: "16:50", doctor: "mjones"}
    ]
    

我仍然觉得 14:00 的时间比较合适,于是又进行预约确认,并提交了我的基本信息,用以创建预约,这是符合 POST 的语义的:

POST /schedules/1234 HTTP/1.1

{name: icyfenix, age: 30, ……}
  • 如果预约成功,那我能够收到一个预约成功的响应:

    HTTP/1.1 201 Created
    
    Successful confirmation of appointment
    
  • 如果发生了问题,譬如有人在我前面抢先预约了,那么我会在响应中收到某种错误信息:

    HTTP/1.1 409 Conflict
    
    doctor not available
    

超文本驱动,进一步解耦

在第二问中我们直接定死了 /schedules/1234 即通过这个url就可以访问指定id下的资源,可以进一步解耦,将这个 url的后缀也存在起来,进而解耦方便修改。

  • 当你输入了查询的指令之后:
    GET /doctors/mjones/schedule?date=2020-03-04&status=open HTTP/1.1
    
  • 服务器传回的响应信息应该包括诸如如何预约档期、如何了解医生信息等可能的后续操作:
    HTTP/1.1 200 OK
    
    {
    	schedules:[
    		{
    			id: 1234, start:"14:00", end: "14:50", doctor: "mjones",
    			links: [
    				{rel: "comfirm schedule", href: "/schedules/1234"}
    			]
    		},
    		{
    			id: 5678, start:"16:00", end: "16:50", doctor: "mjones",
    			links: [
    				{rel: "comfirm schedule", href: "/schedules/5678"}
    			]
    		}
    	],
    	links: [
    		{rel: "doctor info", href: "/doctors/mjones/info"}
    	]
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值