kaelzhang / ctrip-apollo

The most delightful and handy Node.js client for ctrip apollo configuration service.
Other
61 stars 5 forks source link
apollo apollo-client config-service ctrip-apollo

Build Status Coverage

ctrip-apollo

The most delightful and handy Node.js client for Ctrip's apollo configuration service, which

ctrip-apollo directly uses async/await and requires node >= 7.10.1

Related NPM Package: apollo-declare

For some scenarios, we want to:

Then you can try apollo-declare

Install

$ npm i ctrip-apollo

Usage

const apollo = require('ctrip-apollo')

const app = apollo({
  host: 'http://localhost:8070',
  appId: '100004458'
})

// Get the default namespace
const namespace = app.namespace()
// Namespace is an EventEmitter of nodejs
.on('change', ({
  key,
  oldValue,
  newValue
}) => {
  // Updates new values to `process.env`
  process.env[key] = newValue
})

// - Fetch configurations for the first time
// - start update notification polling (by default)
namespace.ready()
.then(() => {
  console.log(namespace.get('portal.elastic.cluster.name'))
  // 'hermes-es-jp'

  // THen, go and change/publish configurations in apollo admin
  console.log(namespace.get('portal.elastic.cluster.name'))
  // 'hermes-es-us'
  // <----
  // ctrip-apollo handles update notifications in the background

  console.log(process.env['portal.elastic.cluster.name'])
  // 'hermes-es-us'
})

Initialize with cluster and namespace

const ns = apollo({
  host,
  appId,

  // Save local cache to the current directory
  cachePath: __dirname
})
.cluster('my-cluster')
.namespace('my-namespace')

const start = async () => {
  // We can also use async/await
  await ns.ready()

  console.log(ns.config())
  // {
  //   'portal.elastic.document.type': 'biz',
  //   'portal.elastic.cluster.name': 'hermes-es-fws'
  // }
}

start()

Disable update notifications(HTTP long polling)

and enable fetching on every 3 minutes

const app = apollo({
  host,
  appId,
  enableUpdateNotification: false,
  enableFetch: true,
  fetchInterval: 3 * 60 * 1000
})

client.ready()
.then(() => {
  console.log(client.get('portal.elastic.cluster.name'))
  // might be:
  // 'hermes-es-us'
})

Chainable

const ns = await

apollo({host, appId})               // <-- app

  .cluster('ap-northeast-1')        // <-- cluster
  .enableUpdateNotification(true)   // <-- cluster

    .namespace('account-service')   // <-- namespace
    .enableFetch(false)             // <-- namespace
    .ready()                        // <-- Promise {namespace}

console.log(ns.get('account.graphql.cluster.name'))
// might be:
// graph-us-3

Configurations in JSON format

By default, ctrip-apollo and apollo admin service store configurations in java.util.Properties format.

If we have two namespaces.

The one is account-service which stores configurations in JSON,

{
  "redis.sentinel.port": 6379
}

while the other one asset-service in properties

redis.sentinel.port=6379
const cluster = apollo({host, appId}).cluster('ap-northeast-1')

// in JSON format
cluster.namespace('account-service', 'JSON') // <=====
.ready()
// .ready() resolves the namespace instance
.then(account => {
  console.log(account.get('redis.sentinel.port')) // 6379
})

cluster.namespace('asset-service')
.ready()
.then(asset => {
  console.log(asset.get('redis.sentinel.port'))   // '6379'
})

APIs

  app (client)                           config service
    |                                        | | |
    |-- cluster  <----- long polling --------| | |
    |   |                                      | |
    |   |-- namespace  <------- fetch ---------| |
    |                                            |
    |-- cluster  <-------- long polling ---------|

app ApolloApplication

An application can have one or more clusters

apollo(options): ApolloApplication

Essential options:

Optional options:

Returns ApolloApplication

options.enableUpdateNotification

If options.enableUpdateNotification is set to true(the default value), all clusters will start to receive update notification automatically.

Make sure the timeout of your gateway is configured more than 60 seconds, via

options.enableFetch

If options.enableFetch is set to true (default value is false), all namespaces will fetch new configurations on every time period of options.fetchInterval

options.skipInitFetchIfCacheFound

If neither reading local cache nor fetching from remote comes successful, await namespace.ready() will fail.

app.cluster(clusterName?): ApolloCluster

Creates a new cluster under the current application.

Returns ApolloCluster

app.namespace(namespaceName?, type?): ApolloNamespace

Create a new namespace under the default cluster of the current application

Returns ApolloNamespace.

cluster ApolloCluster

A cluster can have one or more namespaces. And all namespaces of the same cluster use a single polling manager to receive update notifications.

cluster.namespace(namespaceName?): ApolloNamespace

Creates a new namespace of the current cluster.

Returns ApolloNamespace

cluster.enableUpdateNotification(enable): this

Enable or disable the updateNotification. Returns this

// Get the default cluster, which is equivalent to
// app.cluster('default')
const cluster = app.cluster()

// Disable update notification
cluster.enableUpdateNotification(false)

namespace ApolloNamespace

A namespace is what a configuration key belongs to.

await namespace.ready(): this

Fetch the configuration from config service for the first time, or fallback to reading the local cache file, and if options.skipInitFetchIfCacheFound is set to false (the default value).

If it fails to fetch configurations and read local cache, this method will reject.

MAKE SURE we await namespace.ready() before any namespace.config() or namespace.get() methods are invoked.

namespace.config(): Object

Returns Object the shallow copy of all configurations for the current namespace / application.

Notice that, all configuration getters of a namespace are synchronous methods

console.log('application config', namespace.config())

namespace.get(key): string

Returns the config value of the corresponding key key

console.log('config for host', namespace.get('host'))

namespace.has(key): boolean

new in 4.4.0

Check if a key is in the config.

namespace.enableFetch(enable): this

Enable or disable fetching in every options.fetchInterval

Getters

Getter: namespace.namespace

Returns string the namespace name

Getter: namespace.cluster

Returns string name of the cluster which the current namespace belongs to

Getter: cluster.cluster

Returns string the cluster name

Namespace Events

Event: 'change'

Emits if the any configuration changes.

By default, ctrip-apollo uses HTTP long polling to listen the changes of the configurations.

namespace.on('change', e => {
  console.log('key', e.key)
  console.log('oldValue', e.oldValue)
  console.log('newValue', e.newValue)
})

Event: 'add'

Emits if a new configuration key has been added

Event: 'delete'

Emits if a configuration key has been deleted

If options.fetchInterval is set to 0 and options.updateNotification is set to false, then the event will never emit.

Event: 'updated'

Added in 4.2.0, event data added in 4.5.0

Emits after all changes, adding and deletion of the current updation have been processed.

Event: 'fetch-error'

Emits if it fails to fetch configurations

Event: 'save-error'

Emits if it fails to save configurations to local cache file

PollingRetryPolicy

PollingRetryPolicy is to tell the system what to do next if an error occured when receiving update notifications.

type PollingRetryPolicy = Function(retries): PollingRetryDirective
interface PollingRetryDirective {
  // `abandon: true` ignores all properties below
  // and stops update notification polling which is a dangerous directive.
  abandon?: boolean
  // After `delay` milliseconds,
  // it will try to poll the update notification again
  delay?: number
  // Tells the system to reset the `retries` counter.
  // And the `retries` counter always reset to zero if it receives a sucessful notification
  reset?: boolean
}

// If there is no properties set, it will retry immediately.

The default pollingRetryPolicy is equivalent to:

// It schedule the first retry in 10 seconds, and adds extra 10s delay everytime.
// It will reset the `retries` counter after the 6th retry.
const {DEFAULT_POLLING_RETRY_POLICY} = require('ctrip-apollo')

And we can define our own policy

const pollingRetryPolicy = retries => ({
  // Stop polling after 11(the first and the latter 10 retries) errors
  abandon: retries >= 10,
  // Longer and longer delays
  delay: retries * 10 * 1000
  // And no reset
})

const app = apollo({
  ...,
  pollingRetryPolicy
})

apollo.AVAILABLE_OPTIONS Array

List of available option keys.

Error Codes

See error.js for details

License

MIT