Skip to content

skinnyworm/fluent-leancloud

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fluent client api for LeanCloud services

Build Status npm version

简介

fluent-leancloud 用于帮助开发者构建基于 LeanCloud 服务的流畅API。LeanCloud.cn 的云服务提供了足够的后台服务帮助开发者专注于App本身的开发。但是官方客户端SDK的设计上和当下流行的开发方式上有一些不匹配,以至于使用起来有相当的不便。特别是存储部分,由于使用了Data Access Object的模式,使得Data Object维护了大量的内部状态,这个和我们目前项目中所提倡的中的Immutable Data有很大的违和感。 这个项目的目标是希望通过直接调用http restful endpoints实现数据的建模,ORM,用户管理,实时通讯等服务。构建一个简洁,可定制,轻量的开发工具集,特别针对使用React, Flux, Immutable的环境,从而为开发者提供一个官方SDK外的选择。

使用场景

fluent-leancloud 并不假设开发者的使用环境,设计初衷是为了构建一个轻量级的工具集,这里从基本用途到ORM建模的路径简单介绍一下三种使用场景。

1. 作为一个简单的HTTP客户端使用

这是最基本的应用场景,fluent-leancloud 提供了一个基于fetch的HTTP client,这个客户端封装了LeanCloud的鉴权方法,让开发者直接使用http的(get | put | post | delete)方法访问Leancloud服务而不用关心请求头中 X-LC-Id, X-LC-Sign, X-LC-Session 构建方法。

//Node环境下必须提供一个fetch polyfil
import 'whatwg-fetch';
import {LeancloudHttp} from 'fluent-leancloud';

// 创建一个Http client
const http = LeancloudHttp({
  appId: "应用appId",
  appKey: '应用appKey',
  masterKey: '应用masterKey,可选是可选项'
})

// 获取app信息
http.get('/stats/appinfo').then(console.log, console.error);

// 创建一条Todo记录
http.post('/classes/Todo', {content:"演示TODO", completed:false}).then(console.log, console.error);

// 修改Todo记录
http.put('/classes/Todo/57e5c7b78ac247005bc28e82', {completed:true}).then(console.log, console.error);

// 删除Todo记录
http.delete('/classes/Todo/57e5c7b78ac247005bc28e82').then(console.log, console.error);

这里需要注意的是,由于LeancloudHttp是基于fetch api的,所以在没有fetch的环境(例如Node)中使用时,需要require一个fetch的polyfill。github的 whatwg-fetch 是个不错的选择。

2. 作为API封装工具,让开发者使用流畅API来访问Leancloud的服务

使用LeancloudHttp虽然可以很方便地使用 GET | POST | PUT | DELETE 方法调用LeanCloud的RESTFUL API,但是还是需要写很多代码。所以在LeancloudHttp的基础上我们提供了一种基于模版声明的方法帮助开发者定义API。这是fluent-leancloud和官方SDK最大的区别。我个人认为Declarative的编程方式比Imperative编程方法能够更方便简洁地描述API。

Declarative programming is “the act of programming in languages that conform to the mental model of the developer rather than the operational model of the machine”.

以LeanCloud的存储服务为例,所有的数据表都提供了相同的CRUD操作方法,所以我们可以定义一个resource模版来描述这些方法。而不用每次显性地呼叫操作流程来完成操作。事实上, fluent-leancloud所有的api方法都是通过模版的方式定义的,用户的自定义方法也是通reduce已有模版完成。

还是以Http Client中的Todo为例子

//Node环境下必须提供一个fetch polyfil
import 'whatwg-fetch';
import {LeancloudHttp} from 'fluent-leancloud';

// 创建一个Http client, 和之前示例一致,此处省略...
const http = ...

// 创建 API factory
const {factory} = LeancloudApi(http)

// 创建 Todo Api Object.
const Todo = factory({type: 'Todo'})


// --------------------
//    Collection 方法
// --------------------

// 创建一条Todo
Todo.create({content: "演示TODO", completed:false}).then(console.log, console.error);

// 查找Todo
Todo.find({where: {completed: false}, limit:2, order:'createdAt'}).then(console.log, console.error);

// 查找第一条满足条件的记录
Todo.findOne({where: {completed: false}}).then(console.log, console.error);

// 计数
Todo.count().then(console.log, console.error);

// --------------------
//    Instance 方法
// --------------------
// 实例的objectId
const objectId="5811e206a0bb9f0061e22250";

// 获取Todo的数据实例
Todo(objectId).get().then(console.log, console.error);

// 更新Todo的数据实例
Todo(objectId).update({completed: true}).then(console.log, console.error)

// 将Todo的viewCount字段加2
Todo(objectId).increase('viewCount', 2).then(console.log, console.error);

// 删除Todo的数据实例
Todo(objectId).destroy().then(console.log, console.error);

通过LeancloudApi方法,我们将一个http client封装成一个factory方法,这个factory方法被用于创建应用的Api Object。默认情况下,factory方法使用resource模版构建一个Api对象。通过这个Api对象我们可以使用流畅Api的方式操作数据表。这些操作被分成两类。

  1. Collection方法是针对整个数据表的操作,包括(create, find, findOne 和 count)。 调用方法类似于static方法,例如 Todo.create(data)
  2. Instance方法是针对某个数据项的操作,包括(get, update, destroy 和 increase)。调用方法为函数链接,例如 Todo(id).update(data)

我们可以基于内建模版增添新的方法函数,或者彻底使用其它模版构建方法函数。下面就是增添自定义方法的示例,在这个场景中,我们希望通过实例方法为一个Todo添加标签。

//Node环境下必须提供一个fetch polyfil
import 'whatwg-fetch';

// 导入 LeancloudHttp, LeancloudApi
import {LeancloudHttp, LeancloudApi} from 'fluent-leancloud';

// 导入Array操作
import {Array} from 'fluent-leancloud/dist/FieldOps';

// 创建一个Http client, 和之前示例一致,此处省略...
const http = ...

// 创建 API factory
const {factory} = LeancloudApi(http);

// 创建 Todo Api Object
const Todo = factory({type: 'Todo'}, (todo)=>{
  // 在instance上声明定制的addTags方法
  todo.instance({
    addTags:{
      verb: 'put',
      args: ['labels'],
      data: ({id, labels})=>({id, tags: Array.addUnique(labels)})
    }
  })
})

// --------------------
//  instance 方法
// --------------------
// 实例的objectId
const objectId="5811e206a0bb9f0061e22250";

// 为Todo数据项的tags字段添加一个‘work’标签
Todo(objectId).addTags(['work','programming']).then(console.log, console.error);

3. 作为ORM数据建模工具使用,同样使用流畅API降低使用时的学习成本

对于复杂一些的数据结构,fluent-leancloud提供了ORM的工具,方便开发者声明数据间的关系。目前提供了常用的blongsTohasMany的关系声明,由于LeanCloud的储存服务提供Pointer和Relation两种数据类型。按照官方的说明,我们默认使用Pointer来定义one to many的关系,用Relation来定义many to many的关系。

HasMany by pointer 和 BelongsTo 关系

在此先以Post和Comment为例,它们间的关系如官方文档中描述的一致,我们在Comment数据表上添加了post字段,这个字段的类型为Pointer指向一个Post

//Node环境下必须提供一个fetch polyfil
import 'whatwg-fetch';

// 导入 LeancloudHttp, LeancloudApi
import {LeancloudHttp, LeancloudApi} from 'fluent-leancloud';

// 创建一个Http client, 和之前示例一致,此处省略...
const http = ...

// 创建 API factory
const {factory} = LeancloudApi(http)

// 创建 Post Api Object.
const Post = factory({type: 'Post'}, (post)=>{
  // Post hasMany comments by pointer via comments relation
  post.hasMany('comments', {type:'Comment', by:'pointer', foreignKey:'post'})
});

// 创建 Comment Api Object.
const Comment = factory({type: 'Comment'}, (comment)=>{
  // Comment belongs to Post via post relation
  comment.belongsTo('post', {type:'Post'})
});

// 创建一条Post记录
Post.create({title:'Test'}).then(console.log, console.error);

// fake post id
const postId = "5817091567f3560058686e00";
//--------------------------------------------------
// Post(postId).comments 是个 hasMany by pointer 关系
// 这个关系有create, find, count方法
//--------------------------------------------------

// 创建一条Post的Comment记录,Comment的post字段会包含指向这个Post的指针
Post(postId).comments.create({content:"it is good"}).then(console.log, console.error);

// 查找这个Post的所有Comment记录
Post(postId).comments.find({order:"createdAt"}).then(console.log, console.error);

// 这个Post的comments总数
Post(postId).comments.count().then(console.log, console.error);


// fake comment id
const commentId = "581709658ac247004fbf50c5"
//--------------------------------------------------
// Comment(commentId).post 是个 belongsTo 关系
// 这个关系有get, set方法
//--------------------------------------------------

// 设置这条Comment的Post
Comment(commentId).post.set('5817091567f3560058686e00').then(console.log, console.error)

// 获得这条Comment的Post数据
Comment(commentId).post.get().then(console.log, console.error)
  • hasMany by pointer关系定义了3个方法,分别是 find, count, create
  • belongsTo关系定义了2个方法,分别是 get, set 用于设定Pointer
HasMany by relation 关系

除了使用Pointer外,我们还可以使用LeanCloud提供的Relation作为构建hasMany关系的方法。以LeanCloud内建的Role为列,它包含了两个relations,一个是users, 一个是roles。

我们可以这样定义:

...
// 创建 API factory
const {factory} = LeancloudApi(http)

// 定义 Role Api
const Role = factory({type:'_Role'}, role=>{
  role.hasMany('users', {type: '_User', by: 'relations'});
  role.hasMany('roles', {type: '_Role', by: 'relations'});
})

使用示例:

...
async function seedRolesExample(){
  // 创建 admin role
  const {objectId: admin} = await Role.create({name:'admin', ACL:{"*":{read: true}}});
  // 创建 manager role
  const {objectId: manager} = await Role.create({name:'manager', ACL:{"*":{read: true}}});
  // 将 manager role 添加到 admin roles
  await Role(admin).roles.add(manager);

  return {admin, manager}
}


async function userRelationExamples(roleId, userId){
  // 将用户添加到一个Role中
  await Role(roleId).users.add(userId);
  // 将用户从一个Role中移除
  await Role(roleId).users.remove(userId);
  // 查询一个Role下的所有用户
  await Role(roleId).users.find({where:{verified:false}});
  // 查询一个Role下的所有用户数量
  await Role(roleId).users.count();
}

// 执行 seedRolesExample
seedRolesExample().then(console.log, console.error)

这上面的示例中我们定义了一个hasMany by relations的关系,定义方法为 role.hasMany('users', {type: '_User', by: 'relations'});。 目前hasMany by relations关系定义了4个方法,分别是 find, count, add, remove

我们会根据需要在后续的版本中加入hasMany by through的关系,用于描述多对多关系中的另一边,在_Role这个场景中,我们可以在_User端定义user.hasMany('roles', {type:'_Role', by: 'through'})的关系。

需要注意的是我们用下划线来表明LeanCloud内部的数据表,比如_User和_Role。

最佳实践 Best Practice

关于Store (MemoryStore, FileStore, SessionStore)

(补充文档)

Node Express 和 KOA 整合

(补充文档)

React Redux Flux 整合

(补充文档)

React Native 注意事项

(补充文档)

内建模型

User

(补充文档)

Sms

(补充文档)

Push

(补充文档)

About

fluent lean cloud api builder

Resources

Stars

Watchers

Forks

Packages

No packages published