构建 NodeJS 影院预订微服务并使用 docker 部署(04/4)

·

一、说明

        构建一个微服务的电影网站,需要Docker、NodeJS、MongoDB,这样的案例您见过吗?如果对此有兴趣,您就继续往下看吧。

  我们前几章的快速回顾

  • 第一篇文章介绍了微服务架构模式,并讨论了使用微服务的缺点
  • 第二篇文章我们讨论了使用 HTTP/2 协议微服务安全性
  • 本系列的第三篇文章描述了微服务架构中通信的不同方面,我们解释了 NodeJS 中的设计模式,如依赖注入、控制反转和 SOLID 原则。
  • 我们已经制作了 3 个 API,并将其运行到 Docker 容器
  • 我们已经进行了单元,集成压力测试。

        如果你还没有读过前面的章节,你错过了一些很棒的东西🤘🏽,我会把链接放在下面,所以你可以看看👀。

构建 NodeJS 影院微服务并使用 docker 部署【01/4】

构建 NodeJS 影院微服务并使用 docker 部署它(02/4)

构建 NodeJS 影院预订微服务并使用 docker 部署(03/4)

这是🏛“构建 NodeJS 影院微服务”系列的第四篇文章。本系列文章演示如何使用 ES6、¿ES7 ...8?,连接到 MongoDB 副本集,本文还演示了如何将其部署到 docker 容器中,并模拟此微服务在云环境中的运行方式。

好的,在这一点上,我们将完成下一个图表:

子影院微服务架构

notification.api.js hosted with ❤ by GitHub剩下的是为我们构建的是支付服务和 通知服务,这次我们将非常快速地开发它们,以专注于我们在此架构中没有讨论过的东西,从一开始就存在,API 网关,所以请和我在一起,让我们开始制作一些有趣的东西😎。

我们将在本文中使用的是:

  • NodeJS 版本 7.5.0(本地安装)
  • MongoDB 3.4.1
  • Docker for Mac 1.13.0(已安装,1.13.1 破坏了东西)

跟进文章的先决条件:

  • 已完成上一章中的示例。

如果你还没有,我已经上传了一个 github 存储库,所以你可以在分支步骤 3 上获得最新的存储库链接。

二、支付和通知服务

        由于本文用于构建 API 网关,因此这次我不会花太多时间来描述下一个服务,我将只强调有趣的部分。对于此服务,我们将继续使用相同的项目和应用程序结构,对此会略有变化,因此让我们看看此服务是如何组成的👀。

2.1 支付服务

        要使支付服务正常工作,您可能知道有一堆库供节点进行信用卡收费,此时我将使用一个名为 stripe 的库,但在构建我们的支付服务之前,您应该访问 stripe 网站,并创建一个帐户才能使用此库, 因为我们需要一个条纹代币来进行支付测试。

# Then we need to install stripe in our project
cinema-microservice/payment-service $ npm i -S stripe --silent

        那么我们如何使用条纹,首先让我们在我们的文件中注册我们的条纹依赖项di.js

const { createContainer, asValue } = require('awilix')
const stripe = require('stripe')

// here we include the stripeSettings
function initDI ({serverSettings, dbSettings, database, models, stripeSettings}, mediator) {
  mediator.once('init', () => {
    mediator.on('db.ready', (db) => {
      const container = createContainer()

      container.register({
        database: asValue(db),
        validate: asValue(models.validate),
        ObjectID: asValue(database.ObjectID),
        serverSettings: asValue(serverSettings),
        // and here we register our stripe module
        stripe: asValue(stripe(stripeSettings.secret))
      })

      mediator.emit('di.ready', container)
    })
// more code ..., check the cinema microservice repository for to see the full code

接下来,我们将看到我们的文件如何:api/payment.js

'use strict'
const status = require('http-status')

module.exports = ({repo}, app) => {
  app.post('/payment/makePurchase', (req, res, next) => {
    const {validate} = req.container.cradle

    validate(req.body.paymentOrder, 'payment')
      .then(payment => {
        return repo.registerPurchase(payment)
      })
      .then(paid => {
        res.status(status.OK).json({paid})
      })
      .catch(next)
  })

  app.get('/payment/getPurchaseById/:id', (req, res, next) => {
    repo.getPurchaseById(req.params.id)
      .then(payment => {
        res.status(status.OK).json({payment})
      })
      .catch(next)
  })
}

最后让我们检查一下我们的文件:repository.js

 
  // this the function that makes the charge, when it's done
  // returns the charge object returned by stripe
  
  const makePurchase = (payment) => {
    return new Promise((resolve, reject) => {
      // here we retrieve or stripe dependecy
      const {stripe} = container.cradle
      
      // we create the charge
      stripe.charges.create({
        amount: Math.ceil(payment.amount * 100),
        currency: payment.currency,
        source: {
          number: payment.number,
          cvc: payment.cvc,
          exp_month: payment.exp_month,
          exp_year: payment.exp_year
        },
        description: payment.description
      }, (err, charge) => {
        if (err && err.type === 'StripeCardError') {
          reject(new Error('An error occuered procesing payment with stripe, err: ' + err))
        } else {
          const paid = Object.assign({}, {user: payment.userName, amount: payment.amount, charge})
          resolve(paid)
        }
      })
    })
  }

  // this the function that our API calls first
  const registerPurchase = (payment) => {
    return new Promise((resolve, reject) => {
    
      // and here we call the function to execute stripe
      makePurchase(payment)
        .then(paid => {
        // if every thing is succesfull, we make the registry at our db, for the record only
          db.collection('payments').insertOne(paid, (err, result) => {
            if (err) {
              reject(new Error('an error occuered registring payment at db, err:' + err))
            }
            resolve(paid)
          })
        })
        .catch(err => reject(err))
    })
  }

  const getPurchaseById = (paymentId) => {
    ... more code, where we only query our database for the payment with the id
  }

// more code... visit the repository to see the complete code

我想在这里强调一些事情,我们在这里使用一些指导方针,就像我们一样优秀的开发人员,在函数和 ,指导方针是:repository.jsregisterPurchase()makePurchase()

做一件事(DOT)
每个函数应该只做一件事,并尽可能做那一件事。

少即是多
“F项应尽可能短:如果它们运行的时间更长,请考虑将子任务和数据分解为单独的函数和对象。

——摘自《Programming Apps with Javascript》一书,

埃里克·艾略特

2.2 通知服务

        好的,现在,在我们的通知服务中,再次有一些非常好的库用于发送电子邮件,短信,彩信等,您可以查看twiliosendgrid,以更深入地了解通知服务,但是这次我将向您展示一个非常简单的服务,使用nodemailer。

# So we need to install nodemailer in our project
notification-service$ npm i -S nodemailer nodemailer-smtp-transport --silent

        现在让我们看看我们的js文件如何,首先是我们的,然后是我们的 api/notification.jsrepository.js

module.exports = ({repo}, app) => {
  // this our endpoint where is going to validate our email, and the create and finally send it
  app.post('/notifiaction/sendEmail', (req, res, next) => {
    const {validate} = req.container.cradle

    validate(req.body.payload, 'notification')
      .then(payload => {
        return repo.sendEmail(payload)
      })
      .then(ok => {
        res.status(status.OK).json({msg: 'ok'})
      })
      .catch(next)
  })
}

notification.api.js hosted with ❤ by GitHub

const sendEmail = (payload) => {
    return new Promise((resolve, reject) => {
      const {smtpSettings, smtpTransport, nodemailer} = container.cradle

      const transporter = nodemailer.createTransport(
        smtpTransport({
          service: smtpSettings.service,
          auth: {
            user: smtpSettings.user,
            pass: smtpSettings.pass
          }
        }))

      const mailOptions = {
        from: '"Do Not Reply, Cinemas Company 👥" <no-replay@cinemas.com>',
        to: `${payload.user.email}`,
        subject: `Tickects for movie ${payload.movie.title}`,
        html: `
            <h1>Tickest for ${payload.movie.title}</h1>
            <p>Cinem: ${payload.cinema.name}</p>
            <p>Room: ${payload.cinema.room}</p>
            <p>Seats: ${payload.cinema.seats}</p>
            <p>description: ${payload.description}</p>
            <p>Total: ${payload.totalAmount}</p>
            <p>Total: ${payload.orderId}</p>
            <h3>Cinemas Microserivce 2017, Enjoy your movie !</h3>
          `
      }

      transporter.sendMail(mailOptions, (err, info) => {
        if (err) {
          reject(new Error('An error occured sending an email, err:' + err))
        }
        transporter.close()
        resolve(info)
      })
    })
  }

        要一如既往地查看完整配置,欢迎您在分支步骤 4 的 github 上查看影院微服务存储库

如果我们设置一切正常,并运行集成测试,我们的通知服务可以发送如下图所示的电子邮件:

三、 结论 支付和通知服务

        如果您认为我一如既往地使用这两项服务进行快速访问,欢迎您向我发送推文或在下面🤓发表评论,以便我们可以更详细地讨论这里发生的事情。

        有一件重要的事情我没有提到,当我们用钱(信用卡、账户)处理时,我们需要确保我们的数据被加密只是为了增加另一层安全性,而在支付服务内部,我们需要解密用户信息,继续进行条纹结账

        最后在存储库中,每个服务上都有我们的 bash 文件,如果我们执行它,我们会将我们的服务放入 docker 容器中。start_service.sh

$ bash < start_service.sh

如果我们在docker机器管理器1中运行它,我们应该有这样的东西:

码头工人容器列表 === 码头工人 PS :D

四、接口网关

迁移到微服务时,应用程序设计和体系结构的最大变化之一是 使用网络在应用程序的功能组件之间进行通信。在整体式应用中,应用程序组件在内存中进行通信。在微服务应用中,这种通信是通过网络进行的,因此网络设计和实现变得至关重要。— @Nginx 互认协议文件

4.1 但首先¿什么是API网关?¿ 我们需要它吗?

API 网关是作为进入系统的单一入口点的服务器。它类似于面向对象设计中的立面模式。— 克里斯·理查森

API 网关封装了内部系统架构,并为每个客户端提供了量身定制的 API。它可能具有其他职责,例如身份验证、监视、负载平衡、缓存、请求整形和管理以及静态响应处理。

下图向我们展示了 API 网关如何适应我们的影院架构:

影院微服务 API 网关示例

API 网关负责请求路由、组合和协议转换。来自客户端的所有请求首先通过 API 网关。然后,它将请求路由到相应的微服务

4.2  我们为什么需要它?(优点和缺点)

        因为使用 API 网关封装了应用程序的内部结构,这减少了客户端和应用程序之间的往返次数,并且还简化了我们的代码。实现 API 网关以两种方式之一处理请求。某些请求只是代理/路由到相应的服务。它通过扇出到多个服务来处理其他请求。

        但是 API 网关也有一些缺点。它是另一个“必须开发、部署和管理的高可用性组件”。还存在 API 网关成为开发瓶颈的风险,因此作为优秀的开发人员,我们必须更新 API 网关,更新 API 网关的过程必须尽可能轻量级。

跨领域关注点的实现方式必须使微服务无需处理有关其特定范围之外的问题的详细信息。例如,身份验证可以作为任何 API 网关或代理的一部分实现。— @authO

        API 网关处理的常见问题列表:

  • 认证
  • 运输安全
  • 负载平衡
  • 请求调度(包括容错和服务发现)
  • 依赖关系解析
  • 传输转换

五、构建微服务

        好的,现在我们已经湿透了,我们已经阅读📖了 API 网关的功能,让我们开始构建我们的影院微服务 API 网关 👩🏻 🔬👨🏾 🔬

        在知道我们一直在使用 http/2 协议构建微服务之前,为了启动和运行该协议,我们需要满足一些规则,我们需要创建一些 SSL 证书,为了简单起见,我们创建了自签名证书但这将成为在我们的 API 网关中代理我们的路由的问题。http-proxy nodejs 模块有一个建议,告诉我们以下内容:

您可以在选项中设置安全 SSL 证书到目标连接(避免自签名证书)的验证。secure: true

        我们不能将代理为自签名证书,我们需要将微服务 HTTP/2 协议回滚到前一个协议,但并非一切都是坏消息,因为 API 网关有很多问题,我们现在将使用 HTTP/2 协议,仅在 API 网关中,这就是我们如何激活网关传输安全问题

        那么让我们开始吧,让我们开始动手🖐🏽一些<编码/>👩🏻‍💻👨🏻‍💻,但首先我们需要进行一些审查并查看我们的微服务定义,这在我们的raml中文件然后我们需要检查我们的 api 文件并确认我们的 api 路由定义良好。这很重要,因为这些路由是我们要代理的路由。

        因此,让我们从raml files

booking.raml hosted with ❤ by GitHub

#%RAML 1.0
title: Booking Service
version: v1
baseUri: /booking

/:
  type:   { POST: {item : Booking, item2 : User, item3: Ticket} }

  /verify/{orderId}:
    type:  { GET: {item : Ticket} }

view rawcatalog.raml hosted with ❤ by GitHub

#%RAML 1.0
title: Cinema Catalog Service
version: v1
baseUri: /cinemas

/:
  type:  { GET: {item : Cinemas } }

  /{cinema_id}:
    type:  { GET: {item : Movies } }

  /{city_id}/{movie_id}:
      type:  { GET: {item : Schedules } }

movies.raml hosted with ❤ by GitHub

#%RAML 1.0
title: Movies Service
version: v1
baseUri: /movies

/:
  /premieres:
    type:  { GET: {item : MoviePremieres } }

  /{id}:
    type:  { GET: {item : Movie } }

notification.raml hosted with ❤ by GitHub

#%RAML 1.0
title: Notification Service
version: v1
baseUri: /notification

/sendEmail:
  type:   { POST: {item : Payload} }

/sendSMS:
  type:  { POST: {item : Payload} }

payment.raml hosted with ❤ by GitHub

#%RAML 1.0
title: Payment Service
version: v1
baseUri: /payment

/makePurchase:
  type:   { POST: {item : PaymentOrder} }

/getPruchaseById/{orderId}:
  type:  { GET: {item : Payment} }

        因此,现在我们已经很好地定义了端点,是时候重构我们的微服务了,仅使用 http 协议,并在代码中检查我们的 api 端点

现在看🤴🏽👸🏻,最有趣的部分还没有到来 😁

        由于我们一直在对我们所有的微服务进行码头化,因此知道哪些容器正在运行, 并且如果我们发出以下命令:docker-machine manger1

$ docker inspect <containerName | containerId > 

        这将返回我们有关该容器的作用的信息,前提是我们在容器创建时刻指定该信息,如果您一直在关注本系列,您已经注意到每个服务都有一个 所以现在我们需要停止并删除我们的服务,为什么因为我们要在标签标志中添加一个新标志,如下所示:start-service.shdocker run command.


$ docker run 
 --name {service} 
 -l=apiRoute='{route}' 
 -p {host-port}:{container-port} 
 --env-file env 
 -d {service} 

        让我们看看为什么我们需要这个标志,为此,我的朋友们让我们深入了解 api-gateway 源代码,所以我们要看到👀的第一个文件是api-gateway/config.js

const fs = require('fs')

const serverSettings = {
  port: process.env.PORT || 8080,
  ssl: require('./ssl')
}

const machine = process.env.DOCKER_HOST
const tls = process.env.DOCKER_TLS_VERIFY
const certDir = process.env.DOCKER_CERT_PATH

if (!machine) {
  throw new Error('You must set the DOCKER_HOST environment variable')
}
if (tls === 1) {
  throw new Error('When using DOCKER_TLS_VERIFY=1 you must specify the property DOCKER_CERT_PATH for certificates')
}
if (!certDir) {
  throw new Error('You must set the DOCKER_CERT_PATH environment variable')
}

const dockerSettings = {
  protocol: 'https',
  host: machine.substr(machine.indexOf(':', 0) + 3, machine.indexOf(':', 6) - 6),
  port: parseInt(machine.substr(-4), 10),
  checkServerIdentity: false,
  ca: fs.readFileSync(certDir + '/ca.pem'),
  cert: fs.readFileSync(certDir + '/cert.pem'),
  key: fs.readFileSync(certDir + '/key.pem'),
  version: 'v1.25'
}

module.exports = Object.assign({}, { serverSettings, dockerSettings })

        在这里,我们设置 docker-settings 变量,因为我们将与我们的通信,但为了能够与该机器通信,我们需要将我们的环境正确设置为 manger1 docker 机器,我们可以在我们的终端中执行此操作执行以下命令:manager1 docker-machine

$ eval `docker-machine env manager1 

        一旦设置了我们的环境,我们将直接从nodejs连接到我们的docker-machie,为什么?因为我们正在获取正在运行的容器的信息,并且能够正确地将请求从我们的 API 网关代理到微服务。

        那么我们如何从nodejs连接到我们的docker机器,首先我们需要安装一个调用到我们项目的nodejs模块,如下所示:dockerode

$ npm i -S dockerode --silent

        在我们继续查看 API 网关的代码之前,首先让我们弄清楚什么是代理。

在计算机网络中,代理服务器是充当客户端请求的中介的服务器,这些请求从其他服务器寻求资源。— 维基百科

        好的,现在让我们看看什么是 ES6 代理。

代理是 ES6 中一个有趣而强大的功能,它充当 API 使用者和对象之间的中介。简而言之,每当访问基础对象的属性时,都可以使用 来确定所需的行为。对象可用于为您的 配置陷阱,这些陷阱定义和限制访问底层对象的方式 — 来自《实用 ES6》一书,作者:尼古拉斯·贝瓦夸ProxytargethandlerProxy

因此,现在我们知道代理在计算机网络中和作为 ES6 对象的含义,让我们看看docker.js

在我们的文件中,发生了很多魔术🔮✨,所以让我们看看发生了什么。docker.js

'use strict'
const Docker = require('dockerode')

const discoverRoutes = (container) => {
  return new Promise((resolve, reject) => {
    // here we retrieve our dockerSettings
    const dockerSettings = container.resolve('dockerSettings')
  
    // we instatiate our docker object, that will communicate with our docker-machine
    const docker = new Docker(dockerSettings)
  
    // function to avoid registering our database route and api route 
    const avoidContainers = (name) => {
      if (/mongo/.test(name) || /api/.test(name)) {
        return false
      }
      return true
    }
    
    // here we register our routes in our ES6 proxy object
    const addRoute = (routes, details) => {
      routes[details.Id] = {
        id: details.Id,
        name: details.Names[0].split('').splice(1).join(''),
        route: details.Labels.apiRoute,
        target: getUpstreamUrl(details)
      }
    }
    
    // we generate the container url to be proxy
    const getUpstreamUrl = (containerDetails) => {
      const {PublicPort} = containerDetails.Ports[0]
      return `http://${dockerSettings.host}:${PublicPort}`
    }
    
    // here we list the our running containers
    docker.listContainers((err, containers) => {
      if (err) {
        reject(new Error('an error occured listing containers, err: ' + err))
      }

      const routes = new Proxy({}, {
        get (target, key) {
          console.log(`Get properties from -> "${key}" container`)
          return Reflect.get(target, key)
        },
        set (target, key, value) {
          console.log('Setting properties', key, value)
          return Reflect.set(target, key, value)
        }
      })

      containers.forEach((containerInfo) => {
        if (avoidContainers(containerInfo.Names[0])) {
          addRoute(routes, containerInfo)
        }
      })
     
      // and finally we resolve our routes
      resolve(routes)
    })
  })
}

module.exports = Object.assign({}, {discoverRoutes})

        因此,首先,我们实例化我们的对象以便能够与我们的 docker 机器通信,然后我们创建我们的来存储我们的对象发现的所有路由并列出它,然后我们遍历发现的容器并使用容器详细信息注册我们的 对象,这就是为什么我们需要在启动 docker 容器时添加标签标志,因为,它可以为我们提供更多信息,而这种操作系统是向容器添加信息的一种方式,因此对我们来说,它可以帮助我们了解容器的用途。dockerproxy routes objectdockerroute

所以最后我们解析或路由对象在 .你可能会问我为什么使用 ES6 代理对象,这是因为我认为这可能是使用 ES6 代理对象的🤓😄一个很好的例子(因为我还没有看到很多 ES6 代理的例子),我们可以使用任何类型的对象来存储我们的路由,但代理对象可以帮助我们做更多的事情, 我们可以在 JavaScript 对象中看到中间件等代理,但这超出了本文的范围。server.js

所以现在让我们看看我们的文件,看看我们如何实现我们的路由:server.js

'use strict'
const express = require('express')
const proxy = require('http-proxy-middleware')
const spdy = require('spdy')
const morgan = require('morgan')
const helmet = require('helmet')
const cors = require('cors')
const status = require('http-status')

const start = (container) => {
  return new Promise((resolve, reject) => {
    const {port, ssl} = container.resolve('serverSettings')
    const routes = container.resolve('routes')

    if (!routes) {
      reject(new Error('The server must be started with routes discovered'))
    }
    if (!port) {
      reject(new Error('The server must be started with an available port'))
    }

    const app = express()
    app.use(morgan('dev'))
    app.use(bodyparser.json())
    app.use(cors())
    app.use(helmet())
    app.use((err, req, res, next) => {
      reject(new Error('Bad Gateway!, err:' + err))
      res.status(status.BAD_GATEWAY).send('url not found!')
      next()
    })

    for (let id of Reflect.ownKeys(routes)) {
      const {route, target} = routes[id]
      app.use(route, proxy({
        target,
        changeOrigin: true,
        logLevel: 'debug'
      }))
    }

    if (process.env.NODE === 'test') {
      const server = app.listen(port, () => resolve(server))
    } else {
      const server = spdy.createServer(ssl, app)
        .listen(port, () => resolve(server))
    }
  })
}

module.exports = Object.assign({}, {start})

        在这里,我们要做的是创建一个然后我们循环我们的路由,并将其注册到应用程序中作为中间件,在该中间件中,我们正在应用另一个称为将请求代理到正确的微服务的中间件,最后我们启动我们的服务器。express apphttp-proxy-middleware

        所以现在我们已经准备好在本地运行我们的 api-gateway 并运行超级集成测试,我们将调用 raml 文件中声明的所有端点,并通过 api-gateway 调用它。

        但首先让我们重新创建我们的容器,为此,让我们创建一个名为的自动化脚本,该脚本为我们完成所有工作,如下所示:start_all_microservices.sh

#!/usr/bin/env bash

eval `docker-machine env manager1`

array=('./movies-service'
  './cinema-catalog-service'
  './booking-service'
  './payment-service'
  './notification-service'
)

# we go to the root of the project
cd ..

for ((i = 0; i < ${#array[@]}; ++i)); do
  # we go to each folder
  cd ${array[$i]}
  sh ./start-service.sh
  # and we go back to the root again :D
  cd ..
done

        但是我们需要使用新的标签标志修改每个微服务,如下所示:start-service.sh

docker run --name booking-service -l=apiRoute='/booking' -p 3002:3000 --env-file env -d booking-service
docker run --name catalog-service -l=apiRoute='/cinemas' -p 3001:3000 --env-file env -d catalog-service
// and so on
# and once we have it, let's run the command
$ bash < start_all_microservice.sh

我们需要有这样的东西

        带有新标签标志的新容器和新创建的容器,以便能够在我们的nodejs api-gateway中发现它,所以现在让我们使用以下命令运行我们的api-gateway

$ npm start

        这将在我们的“没有类似的地方”中启动一个服务器:127.0.0.1:8080,(我们心爱的本地主机💕我们应该看到这样的东西:

API 网关控制台输出

        接下来我们需要打开另一个终端并将其放置在我们的影院微服务项目中,并运行以下命令来执行我们的超大型集成测试 👩🏻 🔬👨🏾 🔬

$ npm run int-test

        我们将有一些如下所示的输出:

        在上控制台中,我们可以看到代理如何调度并重定向到相应的url:port,在下控制台中,我们可以看到所有测试是如何正确通过的。

        我们需要对我们的 api-gateway 做一件事,那就是 dockerize 我们的 api-gateway,但让我告诉你,我无法 dockerize 它,我还在弄清楚如何从容器到 docker-machine(host)说话,如果你能弄清楚,欢迎你在下面发表评论, 与我们分享您发现并应用于Dockerize我们的API网关的内容,因此,与此同时,我仍在研究如何完成此任务,因此这就是为什么我们在“没有像127.0.0.1这样的地方”地址而不是Docker机器IP地址中进行集成测试的原因,所以请继续关注,我会告诉您我所发现的。

六、更新  API 网关docker化解决方案

        再次您好,让我告诉您我发现了什么,以及如何解决此问题,因此让我们开始对 API 网关进行 docker化。

        因此,按照我们的模式,让我们看一下我们的内部 api-gateway 文件夹,这个文件应该包含这样的内容:start-service.sh

#!/usr/bin/env bash
eval `docker-machine env manager1`
docker rm -f api-gateway-service
docker rmi api-gateway-service
docker image prune
docker volume prune
docker build -t api-gateway-service .
docker run --name api-gateway-service -v /Users/Cramirez/.docker/machine/machines/manager1:/certs --net='host' --env-file env -d api-gateway-service

在这里,我们添加了一些标志,所以让我们看看它们。

  • 卷标志 在这里,我们将 docker 机器证书文件夹绑定到我们的容器,以便它能够读取这些证书-v
  • net 标志在这里,我们告诉容器连接主机网络适配器,这会将 docker 机器 IP 绑定到我们的 API 网关容器,我们唯一需要关心的是可用的端口,以便我们可以运行我们的 API 网关,就我而言,我正在端口 8080 上执行 API 网关--net

所以现在我们可以再次调用我们的超级骗子测试,但知道我们的 docker 机器 ip + api 网关端口,例如https://192.168.99.100:8080

七、是时候回顾一下了

        我们做了什么...? 我们总结了下图:

        我们在本文中只构建了 3 个服务:支付服务、通知服务和超级骗子 API 网关 😎 .

        我们已经集成了微服务的所有功能,所以现在我们可以发出 GET 和 POST 请求,在这些请求中,我们的一些微服务具有数据验证、数据库交互、第三方服务交互等......总而言之,我们已经做了很多工作,但让我告诉你亲爱的读者,这不是该系列的结束,也许这可能会开始像权力的游戏一样,没有结局(冬天来了❄️,对我来说很多电视节目),但与我们的电影微服务系统有很大关系, 所以请继续关注一些有趣的东西。

        我们通过 API 网关🤔实现了什么?

        我们提供微服务安全性,仅在我们的API网关服务器中处理,这称为传输安全性 ✔️

        我们做了一个半动态的容器服务发现,为什么是半?因为我们使用添加到容器中的 label 标志对路由进行硬编码,并且我们一个接一个地创建容器,但这有助于我们将请求重定向到正确的目标,这称为请求调度✔️依赖关系解析 ✔️

        我们已经在 NodeJS 中看到了很多开发,但我们可以做和学习的东西还有很多,这只是更高级编程的先睹为快。我希望这已经展示了一些有趣和有用的东西,你可以在你的工作流程中用于Docker和NodeJS

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/91879.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Dataset类实践

Dataset类实践 蚂蚁蜜蜂分类数据集和下载链接https://download.pytorch.org/tutorial/hymenoptera_data.zip Dataset&#xff1a;提供一种方式去获取数据及其lable Q&#xff1a;如何获取每个数据及其lable 重写构造方法和获取标签方法 Q&#xff1a;告诉我们总共有多少数据 …

最新域名和子域名信息收集技术

域名信息收集 1&#xff0e;WHOIS查询 WHOIS是一个标准的互联网协议&#xff0c;可用于收集网络注册信息、注册域名﹑IP地址等信息。简单来说&#xff0c;WHOIS就是一个用于查询域名是否已被注册及注册域名详细信息的数据库&#xff08;如域名所有人、域名注册商&#xff09;…

OLED透明屏水波纹效果:打造独特的显示体验

OLED透明屏水波纹效果是一种独特的显示技术&#xff0c;通过模拟水波纹的视觉效果&#xff0c;为用户带来更加生动逼真的观感。 根据市场调研报告显示&#xff0c;OLED透明屏水波纹效果已经在广告、游戏和商业领域得到广泛应用&#xff0c;为品牌提供了新的展示方式&#xff0…

语言基础篇1——Python概述,Python是什么?Python能干什么?

概述 简介 Python&#xff0c;计算机高级语言&#xff0c;读作/ˈpaɪθən/&#xff08;英音&#xff09;、/ˈpaɪθɑːn/&#xff08;美音&#xff09;&#xff0c;意为蟒蛇&#xff0c;Python的logo为两条缠绕的蟒蛇 特点 Python以开发效率高而运行效率低著称 应用领域…

2023年国赛 高教社杯数学建模思路 - 案例:随机森林

文章目录 1 什么是随机森林&#xff1f;2 随机深林构造流程3 随机森林的优缺点3.1 优点3.2 缺点 4 随机深林算法实现 建模资料 ## 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是随机森林&#xff…

Win解答 | 解决键盘中 字母+空格 导致的输入法弹窗导致的一系列问题

近三个月来&#xff0c;一直都有一个键盘组合键的问题影响我的电脑使用&#xff0c;不管是打字还是打游戏&#xff0c;都会出现按键盘的 字母空格 弹出一个特殊符号的候选框&#xff0c;如下图所示 图片中为 S空格 所出现的弹窗 一个看似方便&#xff0c;实则难受的功能 其实打…

JVM7:垃圾回收是什么?从运行时数据区看垃圾回收到底回收哪块区域?垃圾回收如何去回收?垃圾回收策略,引用计数算法及循环引用问题,可达性分析算法

垃圾回收是什么&#xff1f;从运行时数据区看垃圾回收到底回收哪块区域&#xff1f; 垃圾回收如何去回收&#xff1f; 垃圾回收策略 引用计数算法及循环引用问题 可达性分析算法 垃圾回收是什么&#xff1f;从运行时数据区看垃圾回收到底回收哪块区域&#xff1f;垃圾回收如何去…

详细手机代理IP配置

嗨&#xff0c;亲爱的朋友们&#xff01;作为一家代理产品供应商&#xff0c;我知道有很多小伙伴在使用手机进行网络爬虫和数据采集时&#xff0c;常常会遇到一些IP限制的问题。别担心&#xff01;今天我要给大家分享一下手机IP代理的设置方法&#xff0c;让你们轻松应对这些限…

基于Android水果蔬菜果蔬到家商城系统 微信小程序uniAPP的开发与实现

果蔬到家是商家针对用户必不可少的一个部分。在商铺发展的整个过程中&#xff0c;果蔬到家担负着最重要的角色。为满足如今日益复杂的管理需求&#xff0c;各类果蔬到家程序也在不断改进。本课题所设计的springboot基于HBuilder X的果蔬到家APP&#xff0c;使用SpringBoot框架&…

Nginx 高级配置

目录 1 网页的状态页 2 Nginx 第三方模块 2.1 ehco 模块 3 变量 3.1 内置 3.2 定义变量 4 Nginx压缩功能 5 https 功能 6 自定义图标 1 网页的状态页 基于nginx 模块 ngx_http_stub_status_module 实现&#xff0c;在编译安装nginx的时候需要添加编译参数 --with-http…

php开发websocket笔记(1)

1.运行server1.php文件 Windows命令行运行 php server1.php<?phperror_reporting(E_ALL); set_time_limit(0); //ob_implicit_flush(); $address 0.0.0.0;//可以监听网络上的请求 $address 127.0.0.1;//只能监听本机的请求$port 10005; //创建端口 $socket1 socket_cr…

win开机自启jar包

下载winsw工具 只需下载图中红框的工具 https://github.com/winsw/winsw/releases 文件配置 将下载的文件与jar文件放置在一起&#xff0c;两个文件名修改为服务名 编辑xml文件 注意不要出现中文&#xff0c; 标签内的jar文件地址要改为自己目录 <service><!-- I…

【JVM】运行时数据区域

文章目录 说明程序计数器虚拟机栈本地方法栈Java堆方法区运行时常量池直接内存 说明 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途&#xff0c;以及创建和销毁的时间&#xff0c;有的区域随着虚拟机进程的启动而一直…

【图论】缩点的综合应用(一)

一.缩点的概念 缩点&#xff0c;也称为点缩法&#xff08;Vertex Contraction&#xff09;&#xff0c;是图论中的一种操作&#xff0c;通常用于缩小图的规模&#xff0c;同时保持了图的某些性质。这个操作的目标是将图中的一些节点合并为一个超级节点&#xff0c;同时调整相关…

【Springboot】| 从深入自动配置原理到实现 自定义Springboot starter

目录 一. &#x1f981; 前言二. &#x1f981; Spring-boot starter 原理实现分析2.1 自动配置原理 三. &#x1f981; 操作实践3.1 项目场景3.2 搭建项目3.3 添加相关依赖3.4 删除一些不需要的东西3.5 发邮件工具类逻辑编写3.6 创建相关配置类3.7 创建 Spring.factories 文件…

【javaweb】学习日记Day7 - Mysql 数据库 DQL 多表设计

之前学习过的SQL语句笔记总结戳这里→【数据库原理与应用 - 第六章】T-SQL 在SQL Server的使用_Roye_ack的博客-CSDN博客 目录 一、DQL 数据查询 1、基本查询 2、条件查询 3、分组查询 &#xff08;1&#xff09;聚合函数 ① count函数 ② max min avg sum函数 &…

【Tkinter系列02/5】界面初步和布局

本文是系列文章第二部分。前文见&#xff1a;【Tkinter系列01/5】界面初步和布局_无水先生的博客-CSDN博客 说明 一般来说&#xff0c;界面开发中&#xff0c;如果不是大型的软件&#xff0c;就不必用QT之类的实现&#xff0c;用Tkinter已经足够&#xff0c;然而即便是Tkinter规…

基于大数据+django+mysql的银行信用卡用户的数仓系统

系统阐述的是银行信用卡用户的数仓系统的设计与实现&#xff0c;对于Python、B/S结构、MySql进行了较为深入的学习与应用。主要针对系统的设计&#xff0c;描述&#xff0c;实现和分析与测试方面来表明开发的过程。开发中使用了 django框架和MySql数据库技术搭建系统的整体架构…

gitcode中删除已有的项目

镜像地址&#xff1a; https://www.jianshu.com/p/504c1418adb7?v1693021320653 扩展阅读 如何在GitLab中删除一个项目 https://www.codenong.com/cs106866762/ 简介&#xff1a; 如何在GitLab中删除一个项目 最近GIT上建了太多项目。想清一下&#xff0c;就在网上查了查…

opencv-答题卡识别判卷

#导入工具包 import numpy as np import argparse import imutils import cv2# 设置参数 ap argparse.ArgumentParser() ap.add_argument("-i", "--image", requiredTrue,help"path to the input image") args vars(ap.parse_args())# 正确答案…