LoopBack3.0最佳实践(三)——面向Model编程
1. Model的继承关系
虽然我们在定义一个Model时,只需要配置一些属性,但LoopBack会将这些Model转换为一个Class。在LoopBack中有三种类型的Model Class,一个用户定义的Model被转换成哪种Class取决于它继承了哪一种父类:
- 基类(Base Model Class):这是所有Model的父类,地位类似于Java里面的Object。这个类里面封装了REST API的全部相关功能。所以这意味着任何一个LoopBack的Model天生就可以是RESTful的。
Model配置文件
中的base
属性设定为Model
时,会继承该类,开发者需要手动编写所有的API方法。 - 数据持久类(PersistedModel Class):连接数据源进行数据持久化的类。在基类的基础上自带了数据的增删改查方法,这些方法直接可以暴露为REST API。
Model配置文件
中的base
属性设定为PersistedModel
时继承该类(或者不设定,默认情况下继承该类),这是最常用的Model类型。 - 内置类(Built-in Model):包括User、Role和ACL等。用户可以直接在
model-config.json
中直接引用这些LoopBack提供的内置类,来实现用户认证和权限控制等相关功能。当然这些内置类也可以被继承。
下图是官方给出的Model继承关系图
在上一篇文章中我们提到
在Loopback的世界里,一个Model不仅仅是Property的集合,还可以提供REST API Endpoint方法,并且集成ORM功能。开发者仅需要定义Property和配置参数,Loopback会自动集成API和数据持久化方法。
这种可以直接打通API层到数据持久层的逻辑的杀手锏,就是数据持久类PersistedModel
。不用写一行业务逻辑代码,它就把Java程序员熟悉的Controller和DAO的基本功能全部完成了。
这确实会提高开发效率,但也容易引发开发者关于代码架构的困惑。传统的Web开发的分层架构也许不再那么适用于LoopBack,业务逻辑代码可能要更多地围绕着Model去实现,可以说需要“面向Model编程”。在讨论这个话题之前,我们不妨先将Model的API功能与ORM功能剥离开,看一下LoopBack是怎么支持复杂业务逻辑开发的。
2. ORM功能
支持多种数据源
PersistedModel通过Datasource可以连接多种数据源,除了各种数据库之外,甚至连Email服务都可以成为数据源
丰富的CRUD方法
LoopBack为PersistedModel集成了下面这些CRUD方法,既有类方法(Static Method)也有实例方法(Instance Method),常用功能全覆盖。
通过这些方法我们可以轻松实现对数据库的访问:
1 | // 这些CURD方法有callback和promise两种调用方式: |
支持建立Model间的关系
LoopBack支持以下几种关系:
- BelongsTo
- HasOne
- HasMany
- HasManyThrough
- HasAndBelongsToMany
- Polymorphic
- Embedded (EmbedsOne/EmbedsMany/EmbedsMany with belongsTo)
- ReferenceMany
定义一个Model的Relation可以使用交互命令lb relation
,或者直接修改Model配置文件
,以belongsTo为例:
1 | { |
Model间的关系通过外键关联,可实现关联查询
1 | // 查找所有的Review记录,并返回其关联的coffeeShop的信息 |
更多关于Model关系的用法,敬请期待本系列的后续文章。
数据校验
LoopBack针对Model实例数据的校验提供了validation方法:
validatesAbsenceOf: 检查Model实例是否不包含某些属性
validatesExclusionOf: 检查Model实例的某一个属性是否不等于某些值
validatesFormatOf: 检查Model实例的某一个属性是否符合一个正则表达式的格式
validatesInclusionOf: 检查Model实例的某一个属性是否等于某些值
validatesLengthOf: 校验Model实例的某属性的长度
validatesNumericalityOf: 校验Model实例的某属性是否为数值格式
validatesPresenceOf: 检查Model实例是否包含某些属性
validatesUniquenessOf: 校验Model实例某属性的唯一性
validatesDateOf: 校验Model实例的某属性是否为日期格式
在Model定义文件
中调用这些校验方法后方可生效:
1 | module.exports = function(CoffeeShop) { |
默认情况下,这些校验方法会在Model实例创建或更新之前被自动调用,保证了合法数据才能被持久化。下面看在新增一个CoffeShop实例时,非法数据的例子:
1 | var CoffeeShop = app.models.CoffeeShop; |
请求数据中,city这个属性的值Shijiazhuang
不符合validatesInclusionOf
的规则,抛出异常:
1 | Error: |
3. REST API
Remote Method
上文我们提到LoopBack会把PersistedModel的CRUD方法自动暴露为REST API,但如果我们要自定义一个API,则需要用到Remote Method。分为注册和定义两步:
1 | module.exports = function(CoffeeShop) { |
除了callback的方式外,Remote Method也支持以promise的方式返回结果
1 | CoffeeShop.status = function(id) { // 直接return一个promise |
正确请求API时的返回结果
1 | curl -X GET http://localhost:3000/api/CoffeeShop/1/status |
错误请求的结果
1 | curl -X GET http://localhost:3000/api/CoffeeShop/4/status |
API参数校验
上文中我们用validation方法实现了对Model实例数据的检验。但如果要利用这个功能实现对API请求参数的校验,则可以定义一个专用的Request Model:
1 | { |
在api-request-model.js
里面加入一些validation方法:
1 | module.exports = function(APIRequestModel) { |
那么如何利用APIRequestModel
对参数进行校验?第一步,在注册Remote Method时将API的请求参数的类型设置为APIRequestModel
,然后API在被请求时,LoopBack会自动把请求数据转换为APIRequestModel
的实例。第二步,在Remote Method中调用该实例的isValid
方法,触发数据校验:
1 | APIModel.remoteMethod('testRequestValidation', { |
4. 面向Model编程
通过上面的介绍我们可以看到,LoopBack里的一切功能皆围绕着Model展开,Model承担着传统Web应用分层架构中Controller和DAO两种角色。在实际项目中使用LoopBack框架时,如果API的请求/返回数据的格式和数据库的Schema比较接近,可以允许Model同时实现API逻辑和ORM逻辑。但对于数据模型比较复杂的Web应用,如果对不加以区分,可能会导致代码的耦合。所以我们要考虑如何组织应用程序中的Model,使得代码架构更加合理。
一种思路是,将Model在逻辑上区分为“API Model”和“Data Model”,前者并不绑定数据源,只负责暴露API方法,后者连接数据源,负责CRUD。“API Model”在实现时,可以同时辅以“API Request Model”和”API Response Model“,规范和校验API的请求和返回数据。“Data Model”也可以在逻辑上进行进一步区分,将那些连接第三方服务的Model称为“Service Data Model”,以区别于用于持久化数据到数据库的“DB Data Model”:
在大部分应用场景下,一切皆可为Model,因为Model在本质上讲就是Class。当业务逻辑和代码架构都围绕着Model展开时,就是在“面向Model编程”。
当然这也是一家之言,欢迎留言讨论。另外,本文涉及的代码可以到Github项目loopback-hello-world下载。