Build a Backend For Frontend(BFF)ap in Predix


著者:ルテアン、フロントエンドエンジニア、GEデジタルグループ
今は前後の端分離が盛んで、後端の領域では、多くのマイクロサービスが複雑な業務システムを構成しています.一つの前端ページは複数の異なるサーバに要求を送信し、データを取得し、ページのレンダリングを完了する必要があります.さらに、クロスドメイン問題を導入し、帯域幅を取りすぎて、これらの問題に直面する時、Backend For Frontend(BFF)は良い解決策です.本論文では、Predixプラットフォームで使用されるBFFを構築するために、皆様をご案内します.BFFは、/predix-tsの例のデータを返すように要求するコア機能を実現するであろう.
まず、あなたがexpress serverのプロジェクトを初期化し、Predixに展開したと仮定します.(どうやってexpress serverを初期化しますか?参照してください.http://expressjs.com/en/starter/hello-world.html)
注意事項:Predix上で私たちが傍受すべきポート番号は環境変数中のpredix timeseriesです.
まず、あなたがPredix Timeseriesの例PORTを持っていると仮定して、Predix上で実行し、cf bind-serviceを通じてmy-tsを私たちのnode ap上にバインドします.
cf bind-service [app-name] my-ts
my-tsを通して、appの環境変数を読むことができます.
System-Provided:
{  
  "VCAP_SERVICES":{  
    "predix-timeseries":[  
      {  
        "credentials":{  
          "ingest":{  
            "uri":"wss://gateway-predix-data-services.run.aws-jp01-pr.ice.predix.io/v1/stream/messages",
            "zone-http-header-name":"Predix-Zone-Id",
            "zone-http-header-value":"<your-service-zone-id>",
            "zone-token-scopes":[  
              "timeseries.zones.<your-service-zone-id>.user",
              "timeseries.zones.<your-service-zone-id>.ingest"
            ]
          },
          "query":{  
            "uri":"https://time-series-store-predix.run.aws-jp01-pr.ice.predix.io/v1/datapoints",
            "zone-http-header-name":"Predix-Zone-Id",
            "zone-http-header-value":"<your-service-zone-id>",
            "zone-token-scopes":[  
              "timeseries.zones.<your-service-zone-id>.user",
              "timeseries.zones.<your-service-zone-id>.query"
            ]
          }
        },
        "label":"predix-timeseries",
        "name":"timeseries-ds",
        "plan":"Free",
        "provider":null,
        "syslog_drain_url":null,
        "tags":[  
          "timeseries",
          "time-series",
          "time series"
        ],
        "volume_mounts":[  

        ]
      }
    ]
  }
}

{  
  "VCAP_APPLICATION":{  
    "application_id":"<application_id>",
    "application_name":"<app-name>",
    "application_uris":[  
      "<app-name>.run.aws-jp01-pr.ice.predix.io"
    ],
    "application_version":"<version-code>",
    "limits":{  
      "disk":1024,
      "fds":16384,
      "mem":128
    },
    "name":"<app-name>",
    "space_id":"<space_id>",
    "space_name":"<space_name>",
    "uris":[  
      "<app-name>.run.aws-jp01-pr.ice.predix.io"
    ],
    "users":null,
    "version":"<version-code>"
  }
}

No user-defined env variables have been set

No running env variables have been set

No staging env variables have been set
注意したいのは、私たちのnode appにおいてprocess.envにアクセスしてもcf env [app-name]にリストされたcf envおよびVCAP_SERVICES変数にアクセスできます.
よりシームレスにローカルとリモートの環境で同じコードを実行し、コード量を低減するために、ローカルでもオンラインと同じ環境変数を読み込むことができるようにしたいので、これらの環境変数をシミュレートしたい.
しかし、実際にオンライン環境変数には、いろいろな情報が含まれていますので、私たちが必要とする部分を明確にしなければなりません.前に決めた機能を思い出します.predix timeseriesデータを取り戻す必要があります.ですから、必要なのは「predx」だけです.
{  
  "VCAP_SERVICES":{  
    "predix-timeseries":[
      ...
      {  
        "credentials":{
          ...
          "query":{  
            "uri":"https://time-series-store-predix.run.aws-jp01-pr.ice.predix.io/v1/datapoints",
            "zone-http-header-value": "<your-zone-id>"
          }
        }
      }
    ]
  }
}
したがって、私たちはappが起動する前に環境変数にVCAP_APPLICATIONを追加すればいいです.
if (process.env.NODE_ENV === 'development') {
  process.env.VCAP_SERVICES = JSON.stringify({
    "predix-timeseries": [
      {  
        "credentials": {
          "query": {  
            "uri":"https://time-series-store-predix.run.aws-jp01-pr.ice.predix.io/v1/datapoints",
            "zone-http-header-value": "<your-zone-id>"
          }
        }
      }
    ]
  })
}
Predix環境と同じ環境変数ができたら、次のステップを開始します.
転送要求の中間件を書きます.
// proxy to predix ts service
app.use('/predix-ts', proxyMiddleware)
私たちはすべてのPredix ServicesがUAAと結合されていることを知っています.これから使えるかもしれない他のPredix ServicesもUAA授権を必要としているものが多いので、ライセンスを取得する中間部品を増やす必要があります.
// proxy to predix ts service
app.use('/predix-ts', authMiddleware , proxyMiddleware)
私たちはVCAP_SERVICESというパッケージを使ってUAAのライセンスやaccess tokenなどの情報を取得します.npmでインストールしてから、私たちはpredix-uaa-clientを書き始めました.まず、authMiddlewareの方法を書き、Predix UAAからtokenを取得して保存し、再起動時にtokenが期限切れでない場合はそのままtokenを使用し、期限が切れたら新しいtokenを要求する.
const uaaClient = require('predix-uaa-client')

let existedToken = null

function getToken () {
  if (existedToken && existedToken.expire_time > new Date() + 10) {
    return Promise.resolve(existedToken)
  } else {
    return uaaClient.getToken(`${yourUaaURL}/oauth/token`, yourClientId, yourClientSecret)
    .then(token => {
      existedToken = Object.assign({}, token)
      return existedToken
    })
    .catch((err) => {
      console.error('Error getting token', err)
    })
  }
}
書込みgetTokenは、get Token方法を呼び出してtokenを取得し、要求対象のauthMiddlewareフィールドを、Predix Serviceの認証のために設定する.
app.use('/predix-ts', (req, res, next) => {
  getToken()
  .then(token => {
    req.headers['Authorization'] = token.access_token
    next()
  })
} , proxyMiddleware)
次はAuthorizationを書き始めます.ここではhttp-proxy-middlewareを使って転送を要請します.
const url = require('url')
const proxy = require('http-proxy-middleware')

const VCAP_SERVICES = JSON.parse(process.env.VCAP_SERVICES)

const predixTs = VCAP_SERVICES['predix-timeseries'][0]
const urlObj = url.parse(predixTs.credentials.query.uri) //    node     url       api   endpoint

app.use('/predix-ts', (req, res, next) => {
  getToken()
  .then(token => {
    req.headers['Authorization'] = token.access_token
    next()
  })
}, proxy({
  target: urlObj.protocol + '//' + urlObj.host,
  changeOrigin: true,
  pathRewrite: path => path.replace('/predix-ts', '/'),
  onProxyReq: (proxyReq, req, res) => {
  //           zone id
    proxyReq.setHeader('Predix-Zone-Id', predixTs.credentials.query['zone-http-header-value'])
    proxyReq.setHeader('Content-Type', 'application/json')
  },
  onProxyRes: (proxyRes, req, res) => {
    delete proxyRes.headers['access-control-allow-origin']
  }
}))
これで私達のミドルウェアの機能はすでに完成しました.proxyMiddlewareを通じてcurl https://[app-name].run.aws-jp01-pr.ice.predix.io/predix-ts/v1/aggregationsの中のデータを得ることができます.my-tsのデータにアクセスすることもできます.
<html>
  <head>
    <title>bff node</title>
  </head>
  <body>
    Hello World BFF
    <script> Promise.all([ fetch('/predix-ts/v1/aggregations'), fetch('/predix-ts/v1/datapoints', { method: 'POST', body: JSON.stringify({ start: '1h-ago', tags: [ { name: '<tag-name>', order: 'asc' } ] }) }) ]) </script>
  </body>
</html>
完全コードは以下の通りです.
const url = require('url')
const express = require('express')
const bodyParser = require('body-parser')
const proxy = require('http-proxy-middleware')
const uaaClient = require('predix-uaa-client')

if (process.env.NODE_ENV === 'development') {
  process.env.VCAP_SERVICES = JSON.stringify({
    "predix-timeseries": [
      {  
        "credentials": {
          "query": {  
            "uri": "https://time-series-store-predix.run.aws-jp01-pr.ice.predix.io/v1/datapoints",
            "zone-http-header-value": "<predix-zone-id>"
          }
        }
      }
    ]
  })
}

let existedToken = null

const app = express()

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))

const VCAP_SERVICES = JSON.parse(process.env.VCAP_SERVICES)
const predixTs = VCAP_SERVICES['predix-timeseries'][0]
const urlObj = url.parse(predixTs.credentials.query.uri)

app.use('/predix-ts', (req, res, next) => {
  getToken()
  .then(token => {
    req.headers['Authorization'] = token.access_token
    next()
  })
}, proxy({
  target: urlObj.protocol + '//' + urlObj.host,
  changeOrigin: true,
  pathRewrite: path => path.replace('/predix-ts', '/'),
  onProxyReq: (proxyReq, req, res) => {
    proxyReq.setHeader('Predix-Zone-Id', predixTs.credentials.query['zone-http-header-value'])
    proxyReq.setHeader('Content-Type', 'application/json')
  },
  onProxyRes: (proxyRes, req, res) => {
    delete proxyRes.headers['access-control-allow-origin']
  }
}))

const port = process.env.PORT || 8765

module.exports = app.listen(port, err => {
  if (err) {
    console.log(err)
    return
  }
  console.log('Listening at http://localhost:' + port + '
'
) }) const uaaURL = '<uaa url>' const clientId = '<clientId>' const clientSecret = '<clientSecret>' function getToken () { if (existedToken && existedToken.expire_time > new Date() + 10) { return Promise.resolve(existedToken) } else { return uaaClient.getToken(`${uaaURL}/oauth/token`, clientId, clientSecret) .then(token => { existedToken = Object.assign({}, token) return existedToken }) .catch((err) => { console.error('Error getting token', err) }) } }