Skip to content

Refactoring the 'dirty' event, dirtyPayload(), and XXXPayloadDirty() methods logic & spec #158

@huan

Description

@huan
There are only two hard things in Computer Science: cache invalidation and naming things.

-- Phil Karlton

https://martinfowler.com/bliki/TwoHardThings.html

Working PRs

The New Design

In this new design, we will use:

  1. 'dirty' event (added via add dirty rpc function definition for sync data grpc#79)
  2. dirtyPayload() method (added via add dirtyPayload() and hide others #114)
  3. XXXPayloadDirty() method (removed via add dirtyPayload() and hide others #114)

With the logic:

  1. 'dirty' event will be emitted by the deepest puppet by calling the dirtyPayload() method:

    PuppetServiceClient(PuppetServiceServer(PuppetAbstract))).dirtyPayload() -> PuppetAbstract.emit('dirty', payload)

  2. Every Puppet listen to 'dirty' event with its onDirty() method, and invalid(remove) the cache of the payload with the specific id and type (register onDirty() callback to the dirty event will be done automatically by the PuppetAbstract)
  3. XXXPayloadDirty() should call dirtyPayload() first, then wait the 'dirty' event. After receiving the 'dirty' event, it can return because the payload should be dirtied by the listener (which should be registered by the Puppet itself)
    1. a timer should be added in case of the 'dirty' event was lost, and throw an Error. (5 seconds in the current code)

New Puppet API: onDirty()

  1. onDirty() will be addListener to the dirty event by the PuppetAbstract
  2. child puppets need to override it to clean their own payload cache/store (and do not forget to call super.onDirty()!)

Dirty a Payload Locally (in Puppet Provider)

How to dirty a payload (for puppet provider, locally):

  1. puppet.XXXPayloadDirty(id)
    1. future = await new Promise(resolve => puppet.once('dirty', resolve))
      1. check the dirty id/type match before resolve
      2. add a timer to reject the Promise if timeout
  2. puppet.dirtyPayload('XXX', id)
    1. setImmediate(() => puppet.emit('dirty', { type: 'XXX', id })) <- add this task (emit) to the end of event loop queue
  3. puppet.onDirty()
    2. clean the cache
  4. puppet.XXXPayloadDirty(id) <- future resolved
    1. await new Promise(setImmediate) <- wait current event loop task queue to be all executed

call 1, emit 2, listen 3, resolve 4

Dirty a Payload Remotely (with Puppet Service)

  1. 🖥️ Puppet Service
    1. puppetService.XXXPayloadDirty(id)
      1. future = await new Promise(resolve => puppet.once('dirty', resolve))
        1. check the dirty id/type match before resolve
        2. add a timer to reject the Promise if timeout
    2. puppetService.dirtyPayload('XXX', id)
    3. gRPC Service DirtyPayload
  2. ☁️ Puppet Server
    1. puppetServer.dirtyPayload('XXX', id)
    2. puppetServer.emit('dirty', { type: 'XXX', id })
    3. puppetServer.onDirty() listener: remove payload cache
    4. gRPC Service Event (Stream)
  3. 🖥️ Puppet Service
    1. puppetClient.emit('dirty', { type: 'XXX', id })
    2. puppetClient.onDirty() listener: remove payload cache
    3. puppetClient.XXXPayloadDirty(id) future resolved

Steps Mapping

Locally Remote Client Remote Server
1 1. i 2.i
2 1. ii 2.ii
3 3. ii 2.iii
4 3. iii 2.iv

Related issues

Breaking Change

This change will not affect the end-users.

However, the puppet provider developer needs to follow this new logic when they are using the new version of Puppet Abstract Class.

The Puppet Service will be affected too, so we recommend paying attention to the Wechaty versions to be matching on both the server and the client.

CC @wechaty/puppet @wechaty/grpc

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions