teambition-sdk icon indicating copy to clipboard operation
teambition-sdk copied to clipboard

例子当中的 task$ 同步

Open yozman opened this issue 7 years ago • 21 comments

你好,在 README.md 中的最后例子 基于 task$ 的数据同步是理解了, 可是怎么处罚 task$ 的改变呢? 能说明下么?

yozman avatar Jan 26 '17 09:01 yozman

我们公司正在基于 angular2 技术栈重构整个系统 想参考 teambition-sdk 做全局状态数据流管理

yozman avatar Jan 26 '17 09:01 yozman

sdk 在整个 teambition 的架构中只处理数据相关的 state,而外部的 UI state 还是交给 redux。 task$ 是一个 subject,内部在做完 diff 之后如果有变更直接 next 新的值就好了。 https://github.com/teambition/teambition-sdk/blob/master/src/storage/Model.ts#L179

Brooooooklyn avatar Jan 27 '17 10:01 Brooooooklyn

能抽空科普下 teambition sdk 的架构么? 我们也是 restful + websocket

yozman avatar Jan 28 '17 13:01 yozman

然后我们的系统没有所谓的 ui state 都是直接走 service provider 的数据

get state () {
  return this.service.state
}

这样无需页面组件之间的通信 都是操作数据层,逻辑更清晰

yozman avatar Jan 28 '17 13:01 yozman

@yozman

光用getter是解决不了数据层问题的,比如说,一些异步更新怎么推到你的getter内部?要么就得用类似ng1那种暴力方式。

https://github.com/xufei/blog/issues/42

xufei avatar Feb 01 '17 11:02 xufei

@xufei 感谢分享收获良多~ :+1: 关于异步更新我们现在用的是 ngZonerun 强制更新

import { NgZone } from '@angular/core'
...
constructor (
  public zone: NgZone
) {}

update () {
  // 异步更新
  this.zone.run(() => {
    // 更新数据
    this.service.state = data
  })
}

yozman avatar Feb 02 '17 04:02 yozman

@yozman angular 2 还是建议直接将 Observable 绑到模版上,直接更新 Observable 里面的数据就好了。用 ngZone 强制更新不仅麻烦而且会产生很多奇怪的问题。可以参考:https://www.zhihu.com/question/46662780/answer/102300661

Brooooooklyn avatar Feb 02 '17 05:02 Brooooooklyn

@Brooooooklyn 还没吃透 Observable,我是转换成 toPromise 来使用的 如果用 HostListener + EventEmmiter 的方式的话可以省略 zone 不会产生需要强制刷新的场景,可能唯一的缺点是没有懒执行

话说你们招人么,要求高么? 我近一年做业务一直在思考数据层抽象的问题 想找一个有相同思想战友的地方, 孤军奋战忒难了 T.T

yozman avatar Feb 03 '17 02:02 yozman

@Brooooooklyn 知乎那个解答里第三个坑里写的, 双向变更其实是可以避免使其无法发生的。

我是这么做的 数据模型 service 之间禁止相互依赖注入 service 数据状态的变更在组件里触发

service 方法统一两个前缀 domake

  • 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()
}

yozman avatar Feb 03 '17 03:02 yozman

你这都变成 Promise 了当然不会有问题...

Brooooooklyn avatar Feb 03 '17 03:02 Brooooooklyn

待会给你补一个例子

Brooooooklyn avatar Feb 03 '17 03:02 Brooooooklyn

@Brooooooklyn 有个问题你考虑下:

比如说,业务上,有些东西有上下级关联,比如,某个子任务产生了变更,所以你会把它所属的任务的流也产生一个更新,实际上这个地方不一定合适。

比如说,界面上有一个任务详情,一个任务卡片,这两者对子任务变更的响应是没有必要一样的,详情要响应,但卡片未必要,所以我觉得,是不是sdk内部不聚合流,把这些丢给业务来聚合?比如说聚合出两种task$,一种包含了子任务之类的信息,另外一种没有。后者可以直接是sdk里面export出来的,前者由业务(详情界面)再自己合并子任务的流。

xufei avatar Feb 03 '17 05:02 xufei

@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的权衡的吧。

Saviio avatar Feb 03 '17 09:02 Saviio

@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,所以是可以正确更新。

Brooooooklyn avatar Feb 03 '17 12:02 Brooooooklyn

@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 一次。

Brooooooklyn avatar Feb 03 '17 12:02 Brooooooklyn

@yozman https://zhuanlan.zhihu.com/p/24607229 JD

Brooooooklyn avatar Feb 03 '17 12:02 Brooooooklyn

飞叔是站在业务层角度来看这个场景的,在business这个层面上,他设想的这个场景还是可以成立的,他的假设前提是业务开发人员面向sdk编程。

但如果你跑到sdk层去看这个问题,因为sdk自己定义就是在做combine stream这件事,自然你也就不会觉得这个场景有什么问题,因为最少我们有一个解决方案就是sdk根据场景的需要自己写接口export出来就完了。

Saviio avatar Feb 03 '17 14:02 Saviio

@Saviio 昨天我说的这个问题,确实是 @Brooooooklyn 说的意思…… 现在这样是避免了,因为每个东西的查询约束不再一样了,之前是没有分开的,只要从SDK出去,都是一样的,而实际业务上会有对同一个实体不一样结构的订阅需求

xufei avatar Feb 04 '17 01:02 xufei

@xufei 我能理解你最早描述的场景(但我不太认可它),不过假设先按照你的设想走,rdb现在能提供的解决方案就是当我需要子任务数据时,多export一个接口,build 不一样的query。 但问题是,我每次遇到这样的需求,我都要去多extend一个接口export出来,而且这样的解决能力是rdb提供给sdk的,在business layer编程时这样的能力就又是被屏蔽掉的。

Saviio avatar Feb 04 '17 17:02 Saviio

@Brooooooklyn 那这种互推的情况应该怎么解决?

yozman avatar May 06 '17 10:05 yozman

@xufei 最近技术栈转到 react 了, 遇到如何将异步更新推到 getter 的问题了, 望大大指点一二

yozman avatar Aug 24 '17 10:08 yozman