teambition-sdk
teambition-sdk copied to clipboard
例子当中的 task$ 同步
你好,在 README.md 中的最后例子
基于 task$
的数据同步是理解了,
可是怎么处罚 task$
的改变呢?
能说明下么?
我们公司正在基于 angular2 技术栈重构整个系统 想参考 teambition-sdk 做全局状态数据流管理
sdk 在整个 teambition 的架构中只处理数据相关的 state,而外部的 UI state 还是交给 redux。 task$ 是一个 subject,内部在做完 diff 之后如果有变更直接 next 新的值就好了。 https://github.com/teambition/teambition-sdk/blob/master/src/storage/Model.ts#L179
能抽空科普下 teambition sdk 的架构么? 我们也是 restful + websocket
然后我们的系统没有所谓的 ui state 都是直接走 service provider 的数据
get state () {
return this.service.state
}
这样无需页面组件之间的通信 都是操作数据层,逻辑更清晰
@yozman
光用getter是解决不了数据层问题的,比如说,一些异步更新怎么推到你的getter内部?要么就得用类似ng1那种暴力方式。
https://github.com/xufei/blog/issues/42
@xufei
感谢分享收获良多~ :+1:
关于异步更新我们现在用的是 ngZone
的 run
强制更新
import { NgZone } from '@angular/core'
...
constructor (
public zone: NgZone
) {}
update () {
// 异步更新
this.zone.run(() => {
// 更新数据
this.service.state = data
})
}
@yozman angular 2 还是建议直接将 Observable 绑到模版上,直接更新 Observable 里面的数据就好了。用 ngZone
强制更新不仅麻烦而且会产生很多奇怪的问题。可以参考:https://www.zhihu.com/question/46662780/answer/102300661
@Brooooooklyn 还没吃透 Observable,我是转换成 toPromise 来使用的 如果用 HostListener + EventEmmiter 的方式的话可以省略 zone 不会产生需要强制刷新的场景,可能唯一的缺点是没有懒执行
话说你们招人么,要求高么? 我近一年做业务一直在思考数据层抽象的问题 想找一个有相同思想战友的地方, 孤军奋战忒难了 T.T
@Brooooooklyn 知乎那个解答里第三个坑里写的, 双向变更其实是可以避免使其无法发生的。
我是这么做的 数据模型 service 之间禁止相互依赖注入 service 数据状态的变更在组件里触发
service 方法统一两个前缀 do
和 make
-
do
用来请求数据比如doSyncUser
,doUpdateOrder
-
make
用来转换 api 返回的数据格式比如makeUser
,makeOrder
组件里的方法用 getter
来绑定 service
的数据状态
用 set
前缀出发 service 数据更新比如
// 组件
get username () {
return this.user.username
}
setUser () {
this.userService
.doSyncUser()
.then(() => {
alert('执行成功')
})
}
// service
doSyncUser () {
return this.api
.get('/user')
.then(res => this.makeUser(res))
.then(user => this.user = user)
}
// api service
get (url, data?) {
url = this.url + url
data && (url = url + '?' + json2url(data))
return this.http.get(url)
.map(res => res.json())
.toPromise()
}
你这都变成 Promise 了当然不会有问题...
待会给你补一个例子
@Brooooooklyn 有个问题你考虑下:
比如说,业务上,有些东西有上下级关联,比如,某个子任务产生了变更,所以你会把它所属的任务的流也产生一个更新,实际上这个地方不一定合适。
比如说,界面上有一个任务详情,一个任务卡片,这两者对子任务变更的响应是没有必要一样的,详情要响应,但卡片未必要,所以我觉得,是不是sdk内部不聚合流,把这些丢给业务来聚合?比如说聚合出两种task$,一种包含了子任务之类的信息,另外一种没有。后者可以直接是sdk里面export出来的,前者由业务(详情界面)再自己合并子任务的流。
@xufei 飞叔你这个是不是想复杂了,我个人倾向就是把sdk想象成屏蔽client 和server直接通讯的一层proxy,那你这个场景类比到传统开发里,就是assume有一个接口A,返回数据字段如下
{
user: 'foo',
followers: [
'bar',
'baz'
],
rate: 90,
lastUpdated: 1486114179228
}
如果这个接口在前端有两处地方会用到,第一个地方会用到followers
,但第二个地方不会,然而在这样的需求下,你不可能会要求后端把这样一个业务接口写两遍,那么对前端来说也只是选择消费
或者不消费
数据而已。
我猜测你会有这样的假想在于因为我们是直接对流
做订阅,那么子任务的更新会导致多个订阅者都收到更新,即使某一个视图内部并没有消费这次更新的那个字段?如果是这样的话,那最后导致的问题应该还是冗余的render
,我个人倾向还是从视图层更新的角度来做这部分需求吧。当然让开发者自己组合sdk暴露的流这个空间当然是要有(同时也是天然存在的),但是这个一般都是用来响应比较高阶/复杂的需求吧,主要是在你的场景里,假设SDK已然export了task$
,我个人也是认为开发者会偷懒直接用,而不会选择去自己聚合的。(更何况,自己聚合的过程就是把sdk里同样的代码再写一遍.....
主要,在我看来SDK的价值是在于帮助业务开发人员直接把复杂度收敛,隔离数据层面上出现bug的发生点,产出的数据可以大范围覆盖使用场景。能达到这几点,其实已经挺功德无量了,性能倒反而在第二梯队,而且如果是因为抽象带来的性能损失,我觉得还是可以更case by case的权衡的吧。
@xufei @Saviio 其实飞叔说的这个担忧在我们用了 ReactiveDB 之后并不存在。 他说的这个例子:
// 任务板中的任务:
TaskInTaskBoard: {
_id: string
content: string
subtaskCount: number
isDone: false
}
// 对应的 ReactiveDB Query:
Database.get({
fields: ['_id', 'content', 'subtaskCount', 'isDone']
})
.change()
.subscribe()
// 任务详情的任务:
TaskInTaskDetail: {
_id: string
content: string
isDone: true
subtasks: SubtaskSchema[]
}
// 对应的 ReactiveDB Query:
Database.get({
fields: ['_id', 'content', 'isDone', {
subtask: ['_id', 'isDone', 'dueDate']
}]
})
.change()
.subscribe()
这种用法里面更新 subtask
是不会影响到任务板里面的 Observable
。
原来的设计里面会都影响到,是因为以前的 sdk 里面没有做细粒度的 query
,且没有做 diff。
现在把 diff -> notify 的工作交给了 Lovefield,所以是可以正确更新。
@yozman 如果你的代码是这样写的:
// 组件
<div> {{ user$.name | async }} </div>
user$ = this.service.user$
.merge(this.userSocket$)
.scan((acc, val) => Object.assign(acc, val))
// service
user$ = this.api
.get('/user')
.map(res => this.makeUser(res))
.do(() => {
// do some side effect
// 这里可能导致其它的 this.otherDate$ 推送新的数据
})
otherData$ = this.api
.get('/other')
.map(res => this.parseOther(res))
.do(() => {
// do some side effect
// 这里可能导致其它的 this.user$ 推送新的数据
})
// api service
get (url, data?) {
url = this.url + url
data && (url = url + '?' + json2url(data))
return this.http.get(url)
.map(res => res.json())
}
代码里都是 Observable 然后就可能会重复相互导致推送,例子里这种 A => B => A 的情况一般不会写出来。真正的级联更新的情况可以参见飞叔文章中的 A=>B=>C=>D => A 这种情况,并且非常难调试和追踪。
你把值变成 Promise 绑上去不会出现这种问题,因为 Promise 只会 Resolve 一次。
@yozman https://zhuanlan.zhihu.com/p/24607229 JD
飞叔是站在业务层角度来看这个场景的,在business这个层面上,他设想的这个场景还是可以成立的,他的假设前提是业务开发人员面向sdk编程。
但如果你跑到sdk层去看这个问题,因为sdk自己定义就是在做combine stream这件事,自然你也就不会觉得这个场景有什么问题,因为最少我们有一个解决方案就是sdk根据场景的需要自己写接口export出来就完了。
@Saviio 昨天我说的这个问题,确实是 @Brooooooklyn 说的意思…… 现在这样是避免了,因为每个东西的查询约束不再一样了,之前是没有分开的,只要从SDK出去,都是一样的,而实际业务上会有对同一个实体不一样结构的订阅需求
@xufei 我能理解你最早描述的场景(但我不太认可它),不过假设先按照你的设想走,rdb现在能提供的解决方案就是当我需要子任务数据
时,多export一个接口,build 不一样的query。
但问题是,我每次遇到这样的需求,我都要去多extend一个接口export出来,而且这样的解决能力是rdb提供给sdk的,在business layer编程时这样的能力就又是被屏蔽掉的。
@Brooooooklyn 那这种互推的情况应该怎么解决?
@xufei 最近技术栈转到 react 了, 遇到如何将异步更新推到 getter 的问题了, 望大大指点一二