# NotifierMgr

Notifier Manager is a program designed for managin all notifiers. However, it now grows up as a notification pipeline manager.

## Design

A notification flows along the stream: middleware -> ... -> middleware -> ... -> middleware.

Middleware are used to produce, process and consume events. Do whatever you want to do with middleware! For example: produce ticker event, produce a daily report when receive a daily ticker event, filter event, split one event into several small event. A Middleware is a struct contains two channels, one `<-chan event.Event` for output and one `chan<- event.Event` for input. A tipical implementation of middleware will open up one or more goroutines to receive event from input channel and handle this event and finally pass it into output channel.

### Driver

But what is driver? Aha, driver is just constructor of middleware. If you need to send to different slack, you could construct two different middleware with same constructor which is abstracted as a driver.

Driver is also the only thing which can read config from config file. Actually config field will be passed to driver (as `interface{}`).

In general every middleware is constructed by a driver. So every middleware configured in config file should have a field `driver`.

There are some typical drivers:

1. `combine` driver. Config of it is a list of middleware names. Every event into it will be passed to these middlewares and every output from these middlewares will be passed outside.

2. `pipeline` driver. Config of it is a string which contains middleware names splited by |. Event comes here will be passed through these middlewares one by one.

3. `slack` driver. Config of it contains `channel`, `username` and `token` to send message to slack.

...

### Main

Entry is a name of middleware.

Manager will try to receive event from every entry and consume them (to avoid memory leak).

### Config

Let's talk about design of config file. It a yaml (toml is too flat for this config structure). Here is the sample config:

```yaml
middleware:
  - name: 'exp_check'
    driver: 'schedule'
    config:
      interval: 5
  - name: 'dailyReport'
    driver: 'schedule'
    config:
      interval: 86400

  - name: 'log'
    driver: 'log'
  - name: 'notify'
    driver: 'combine'
    config:
      - 'log'
      - 'slack'
  - name: 'slack'
    driver: 'slack'
    config:
      __ref: 'slack'
      title: 'schrodinger'

  - name: 'foreach'
    driver: 'foreach'
  - name: 'serialize'
    driver: 'serialize'
  - name: 'pass'
    driver: 'pass'

  - name: 'get_undestroyed_abnormal_unalerted_experiments'
    driver: 'get_undestroyed_abnormal_unalerted_experiments'
    config:
      __ref: 'storage'
  - name: 'update_notify_time'
    driver: 'update_notify_time'
    config:
      __ref: 'storage'
  - name: 'generate_exp_report'
    driver: 'generate_exp_report'
    config:
      __ref: 'baseConfig'
  - name: 'notify_pipeline'
    driver: 'pipeline'
    config: 'update_notify_time|generate_exp_report'
  - name: 'match_too_early_exp'
    driver: 'match_too_early_exp'
    config:
      time: 72
  - name: 'destroy_exp'
    driver: 'destroy_exp'
    config:
      __ref:
        - 'baseConfig'
  - name: 'generate_destroy_alert'
    driver: 'generate_destroy_alert'
    config:
      __ref:
        - 'baseConfig'
  - name: 'destroy_pipeline'
    driver: 'pipeline'
    config: 'match_too_early_exp|destroy_exp|generate_destroy_alert'

  - name: 'handle_experiments'
    driver: 'combine'
    config:
      - 'notify_pipeline'
      - 'destroy_pipeline'
  
  - name: 'exp_check_pipeline'
    driver: 'pipeline'
    config: 'exp_check|get_undestroyed_abnormal_unalerted_experiments|foreach|handle_experiments|notify'
  
  - name: 'generate_daily_report'
    driver: 'daily_report'
    config:
      __ref: 
        - 'storage'
        - 'baseConfig'
  
  - name: 'daily_report_pipeline'
    driver: 'pipeline'
    config: 'dailyReport|generate_daily_report|notify'

config:
  storage:
    driver: 'mysql'
    datasource: 'root:@(172.16.4.4:32582)/schrodinger'
    'max-idle-conns': 256
  baseConfig:
    baseDomain: 'schrodinger.pingcap.net'
  slack:
    channel: "#schrodinger_alert"
    username: "schrodinger_bot"
    token: "xoxb-338269940566-EqbYEgr8kg007mVEqWdY4gwT"

main:
  - 'exp_check_pipeline'
  - 'daily_report_pipeline'
```

When start up the manager, manager will check config file and construct every middleware by passing config field into their driver. How manager can find their drivers? By finding in a predefined map from driver name to driver in `interface.go`.

#### Trick

##### __ref

Some configs are needed in multiple place such as database config and host config. A convenient trick is provided for such case. `__ref` key in config is special. When parsing a config, the value of `__ref` will be used as the key to find common config in `config` part. For example:

```yaml
middleware:
 - name: 'generateDailyReport'
    driver: 'dailyReport'
    config:
      __ref: 
        - 'storage'
        - 'baseConfig'

config:
  storage:
    driver: 'mysql'
    datasource: 'root:@(172.16.4.4:32582)/schrodinger'
    'max-idle-conns': 256
  baseConfig:
    baseDomain: 'schrodinger.pingcap.net'
```

This config will be turned into:

```yaml
middleware:
 - name: 'generateDailyReport'
    driver: 'dailyReport'
    config:
      driver: 'mysql'
      datasource: 'root:@(172.16.4.4:32582)/schrodinger'
      'max-idle-conns': 256
      baseDomain: 'schrodinger.pingcap.net'
      __ref: 
        - 'storage'
        - 'baseConfig'
```


##### default driver (TODO)

Some driver doesn't read config and only appear once. A better way is not to repeat name once again, but do it automatically.

```yaml
middleware:
  - name: 'pass'
```

Under this feature it will be turned into 

```yaml
middleware:
  - name: 'pass'
    driver: 'pass'
```