医生预约系统
医生预约系统
作为一名病人,我想要从系统中得知指定日期内我熟悉的医生是否具有空闲时间,以便于我向该医生预约就诊。
传统的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"} ] }