About ServiceGeneratorFunction

The ServiceGeneratorFunction is where we put the codes for Async Data Logic. With Generator, we can:

  • yield req(actionType) to wait for certain Types of Action
  • Write async code in a more concentrated form

The ServiceGeneratorFunction should be defined as:

const session = { data: 'data' } // store data (ignore for now)

const serviceGeneratorFunction = function * ({ store, req, res }) {
  while (true) { // keeps the Service running
    const action = yield req('service:sample:one') // wait for a single Type of Action
    // some Logic
    session.data = 'updated'
    yield res({ type: 'reducer:sample:update', payload: session }) // Update Redux State (ignore for now)

    const action = yield req([ // wait for a multiple Types of Actions
      'service:sample:two', 
      'service:sample:three'
    ])
    // some Logic
    session.data = 'updated, again'
    yield res({ type: 'reducer:sample:update', payload: session }) // Update Redux State (ignore for now)
  }
}

Using ServiceGeneratorFunction, instead of ReducerFunction, allows to accept different Actions at different running state.

Like a mini State Machine, GeneratorFunction makes the async logic easier to read and write, than using Promise or callback.

A Service should contain only the logic focused on the accompanied Session Data, like implementing a standard Class. Instead, push the wrapping code up into EntryFunction.

About SessionObject

Control Flow

A suggested Control Flow is the most simple one:

const session = { isActive: false }

const serviceGeneratorFunction = function * ({ store, req, res }) {
  yield req('service:sample:init') // block until init

  while (true) {
    yield res({ type: 'reducer:sample:update', payload: session }) // Update
    const action = yield req('service:sample:start') // block until start
    session.isActive = true

    while (session.isActive) {
      yield res({ type: 'reducer:sample:update', payload: session }) // Update
      const action = yield req([
        'service:sample:create', 
        'service:sample:update', 
        'service:sample:delete', 
        'service:sample:stop' // accept Action until stop
      ])
      // map or switch to main Logic
      if (action.type === 'service:sample:stop') session.isActive = false
    }
  }
}

I've yet to encounter a Service Logic complex enough to use a fourth layer of while(true). And if that happens, may be try to split it to smaller Services, then use Entry to coordinate the Actions.

Action Block

If an Action's Type is requested by a Service, then this Action will be consumed by the Service.

This means the Action is Blocked, unlike EntryFunction, which allows you to decide.

So do not request the same actionType in multiple Service, only the first Service will get that Action. And do not use this actionType in Reducers, either.

In fact, use a specific prefix on Service-Targeted Actions, like the sample for ActionObject

Prevent Loop in Service

Do not yield req() and yield res() the same Type of Action within the Service. This creates loop, and re-entry of ServiceGenerators. It's generally hard to Observe, Understand, and Debug. So there will be warnings for Service re-entry. Check GeneratorError for details on re-entry.

Just get all the Data ready in one go. Here's one way to do so:

const session = {}

const operationMap = {
  'service:sample:start': (session, store, payload) => {},
  'service:sample:create': (session, store, payload) => {
    // some code
    operationMap['service:sample:update'](session, store, payload) // apply another operation
  },
  'service:sample:update': (session, store, payload) => {},
  'service:sample:delete': (session, store, payload) => {},
  'service:sample:stop': (session, store, payload) => {}
}

const service = function * ({ store, req, res }) {
  while (true) {
      yield res({ type: 'reducer:sample:update', payload: session }) // Update
      const action = yield req([
        'service:sample:start', 
        'service:sample:create', 
        'service:sample:update', 
        'service:sample:delete', 
        'service:sample:stop' // accept Action until stop
      ])
      // map or switch to main Logic
      operationMap[action.type](session, store, action.payload)
  }
}

results matching ""

    No results matching ""