Build a Backend For Frontend(BFF)ap in Predix
著者:ルテアン、フロントエンドエンジニア、GEデジタルグループ
今は前後の端分離が盛んで、後端の領域では、多くのマイクロサービスが複雑な業務システムを構成しています.一つの前端ページは複数の異なるサーバに要求を送信し、データを取得し、ページのレンダリングを完了する必要があります.さらに、クロスドメイン問題を導入し、帯域幅を取りすぎて、これらの問題に直面する時、Backend For Frontend(BFF)は良い解決策です.本論文では、Predixプラットフォームで使用されるBFFを構築するために、皆様をご案内します.
まず、あなたがexpress serverのプロジェクトを初期化し、Predixに展開したと仮定します.(どうやってexpress serverを初期化しますか?参照してください.http://expressjs.com/en/starter/hello-world.html)
注意事項:Predix上で私たちが傍受すべきポート番号は環境変数中の
まず、あなたがPredix Timeseriesの例
よりシームレスにローカルとリモートの環境で同じコードを実行し、コード量を低減するために、ローカルでもオンラインと同じ環境変数を読み込むことができるようにしたいので、これらの環境変数をシミュレートしたい.
しかし、実際にオンライン環境変数には、いろいろな情報が含まれていますので、私たちが必要とする部分を明確にしなければなりません.前に決めた機能を思い出します.predix timeseriesデータを取り戻す必要があります.ですから、必要なのは「predx」だけです.
転送要求の中間件を書きます.
今は前後の端分離が盛んで、後端の領域では、多くのマイクロサービスが複雑な業務システムを構成しています.一つの前端ページは複数の異なるサーバに要求を送信し、データを取得し、ページのレンダリングを完了する必要があります.さらに、クロスドメイン問題を導入し、帯域幅を取りすぎて、これらの問題に直面する時、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)
})
}
}