携程旅游电子商务网站策划书,wordpress固定地址404,百度推广长春分公司,龙元建设网站一、说明 构建一个微服务的电影网站#xff0c;需要Docker、NodeJS、MongoDB#xff0c;这样的案例您见过吗#xff1f;如果对此有兴趣#xff0c;您就继续往下看吧。 你好社区#xff0c;这是#x1f3f0;“构建 NodeJS 影院微服务”系列的第三篇文章。本系列文章演示了… 一、说明 构建一个微服务的电影网站需要Docker、NodeJS、MongoDB这样的案例您见过吗如果对此有兴趣您就继续往下看吧。 你好社区这是“构建 NodeJS 影院微服务”系列的第三篇文章。本系列文章演示了如何使用 ES6、¿ES7 ...8连接到 MongoDB 副本集本文还演示了如何将其部署到 docker 容器中并模拟此微服务在云环境中的运行方式。 二、我们前几章的快速回顾 我们谈论什么是微服务我们看到了微服务的优点和缺点。我们定义了影院微服务架构。我们设计和开发了电影服务和电影目录服务。我们为每个服务制作了一个API并对我们的API进行了单元测试。我们编写我们的 API 以使其成为服务并将其运行到 Docker 容器中。我们对在 Docker 上运行的服务进行了集成测试。我们谈论微服务安全性我们实现HTTP / 2协议。我们对电影目录服务进行了压力测试。 如果你还没有读过前面的章节你错过了一些有趣的东西我会把链接放在下面所以你可以看看。 在前面的章节中我们已经实现了下图中的高级子体系结构我们将在本章中开始开发较差的子体系结构。 此时最终用户已经可以看到电影院有哪些电影首映可以选择电影院并请求预订因此在本文中我们将继续构建电影院架构我们将看到预订服务内部发生了什么所以跟进让我们学习一些有趣的事情。 我们将在本文中使用的是 NodeJS 版本 7.5.0MongoDB 3.4.1Docker for Mac 1.13 跟进文章的先决条件 已完成上一章中的示例。 如果你还没有我已经上传了一个 github 存储库所以你可以在分支步骤 2 上获得最新的存储库链接。 三、NodeJS 中的依赖注入 到目前为止我们已经为我们的微服务构建了 2 个 API但在这些微服务中我们还没有做太多的配置和这么多的开发因为它的性质和简单性但这一刻已经到来在我们的预订微服务中我们将看到与其他服务更多的交互为此我们将需要更多的依赖项来完成分配给此微服务的任务 但是为了不开始制作一些意大利面条代码作为优秀的开发人员我们将跟进一些开发设计模式为此我们将看到什么是“依赖注入”。 为了实现优秀的设计模式我们必须很好地理解并应用S.O.L.I.D.原则我用javascript写了一篇关于这个的文章所以你可以看一看看看这个原则是什么我们如何从中受益。 在我们开始讨论依赖注入之前如果您不熟悉它可以在继续之前观看以下视频。 依赖关系注入是一种软件设计模式其中一个或多个依赖关系或服务被注入或通过引用传递到依赖对象中。 为什么理解什么是依赖注入很重要这很重要因为它为我们提供了开发模式中的 3 个要点如下所示 解耦依赖注入使我们的模块耦合更少并且随着它的实现我们获得了主要的可维护性。单元测试通过依赖注入我们可以为每个模块进行更好的单元测试我们的代码也会减少错误。更快的开发通过依赖注入在定义接口后可以轻松工作没有任何合并冲突。 因此到目前为止在我们的微服务中我们已经在index.js // more codemediator.on(db.ready, (db) {let rep// here we are making DI to the repository// we are injecting the database object and the ObjectID objectrepository.connect({db, ObjectID: config.ObjectID}).then(repo {console.log(Connected. Starting Server)rep repo// here we are also making DI to the server// we are injecting serverSettings and the repo objectreturn server.start({port: config.serverSettings.port,ssl: config.serverSettings.ssl,repo})}).then(app {console.log(Server started succesfully, running on port: ${config.serverSettings.port}.)app.on(close, () {rep.disconnect()})})
})// more code 我们在文件中所做的是手动DI因为我们不需要做更多的事情但是知道在预订服务中我们需要制定更好的DI方法让我们看看为什么我们需要它所以在我们开始构建API之前让我们弄清楚预订服务需要做什么。index.js 预订服务需要一个预订对象和一个用户对象在执行预订操作后我们需要首先验证这些对象。一旦验证我们就能够继续开始购买门票的过程。预订服务需要用户信用卡信息才能通过支付服务购买门票。成功收费后我们需要通过通知服务发送通知。此外我们需要为用户生成票证将票证和采购订单ID代码发送回给用户。 因此这里的开发任务已经增加了一点因此代码也会增加这就是为什么我们需要为DI创建单一事实来源的原因因为我们将要做更多的功能。 四、构建微服务 好的首先让我们看看我们的RAML文件将如何用于预订服务。 #%RAML 1.0
title: Booking Service
version: v1
baseUri: /types:Booking:properties:city: stringcinema: stringmovie: stringschedule: datetimecinemaRoom: stringseats: arraytotalAmount: numberUser:properties:name: stringlastname: stringemail: stringcreditcard: objectphoneNumber?: stringmembership?: numberTicket:properties:cinema: stringschedule: stringmovie: stringseat: stringcinemaRoom: stringorderId: stringresourceTypes:GET:get:responses:200:body:application/json:type: itemPOST:post:body:application/json:type: itemtype: item2responses:201:body:application/json:type: item3/booking:type: { POST: {item : Booking, item2 : User, item3: Ticket} }description: The booking service need a Booking object that contains allthe needed information to make a purchase of cinema tickets.Needs a user information to make the booking succesfully.And returns a ticket object./verify/{orderId}:type: { GET: {item : Ticket} }description: This route is for verify orders, and would return all the detailsof a specific purchased by orderid. 我们定义了 3 个模型对象即预订、用户和票证因此由于这是我们在本系列中看到的第一个 POST 请求因此有一个我们尚未使用的 NodeJS 最佳实践即数据验证。 我从文章“构建漂亮的节点API”中读到了一个很好的引用其中说了以下内容 因此我们将从这里开始构建我们的预订服务。与上一章一样我们仍将使用相同的项目结构但这次我们将进行更多的修改。所以让我们停止谈论理论让饥饿游戏开始再次抱歉所以让乐趣开始让我们做一些“编码. 首先我们需要在名为/srcmodels booking-service/src $ mkdir models
# Now lets move to the folder and create some files
booking-service/src/models $ touch user.js booking.js ticket.js
# Now is moment to install a new npm package for data validation
npm i -S joi --silent 好的现在我们已经准备好了是时候开始编码我们的模式验证对象了MongoDB 也内置了一个验证对象但这里我们需要验证的是对象是完整的这就是我选择 joi 的原因joi 也允许我们同时验证数据所以让我们从 开始最后从booking.model.jsticket.model.jsuser.model.js const bookingSchema (joi) ({bookingSchema: joi.object().keys({city: joi.string(),schedule: joi.date().min(now),movie: joi.string(),cinemaRoom: joi.number(),seats: joi.array().items(joi.string()).single(),totalAmount: joi.number()})
})module.exports bookingSchema const ticketSchema (joi) ({ticketSchema: joi.object().keys({cinema: joi.string(),schedule: joi.date().min(now),movie: joi.string(),seat: joi.array().items(joi.string()).single(),cinemaRoom: joi.number(),orderId: joi.number()})
})module.exports ticketSchema const userSchema (joi) ({userSchema: joi.object().keys({name: joi.string().regex(/^[a-bA-B]/).required(),lastName: joi.string().regex(/^[a-bA-B]/).required(),email: joi.string().email().required(),phoneNumber: joi.string().regex(/^(\0?1\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/),creditCard: joi.string().creditCard().required(),membership: joi.number().creditCard()})
})module.exports userSchema 如果你不知道你可以在这里查看他们的github文档链接到文档。joi 现在让我们对模型进行编码以公开如下所示的验证函数index.js const joi require(joi)
const user require(./user.model)(joi)
const booking require(./booking.model)(joi)
const ticket require(./ticket.model)(joi)const schemas Object.create({user, booking, ticket})const schemaValidator (object, type) {return new Promise((resolve, reject) {if (!object) {reject(new Error(object to validate not provided))}if (!type) {reject(new Error(schema type to validate not provided))}const {error, value} joi.validate(object, schemas[type])if (error) {reject(new Error(invalid ${type} data, err: ${error}))}resolve(value)})
}module.exports Object.create({validate: schemaValidator}) 因此我们所做的我们应用了单一责任从每个模型都有自己的验证的坚实原则我们还应用了开-关原则其中模式验证器函数能够验证我们声明的任意数量的模型所以让我们看看这个模型的测试文件如何。 /* eslint-env mocha */
const test require(assert)
const {validate} require(./)console.log(Object.getPrototypeOf(validate))describe(Schemas Validation, () {it(can validate a booking object, (done) {const now new Date()now.setDate(now.getDate() 1)const testBooking {city: Morelia,cinema: Plaza Morelia,movie: Assasins Creed,schedule: now,cinemaRoom: 7,seats: [45],totalAmount: 71}validate(testBooking, booking).then(value {console.log(validated)console.log(value)done()}).catch(err {console.log(err)done()})})it(can validate a user object, (done) {const testUser {name: Cristian,lastName: Ramirez,email: cristianonupp.com,creditCard: 1111222233334444,membership: 7777888899990000}validate(testUser, user).then(value {console.log(validated)console.log(value)done()}).catch(err {console.log(err)done()})})it(can validate a ticket object, (done) {const testTicket {cinema: Plaza Morelia,schedule: new Date(),movie: Assasins Creed,seats: [35],cinemaRoom: 1,orderId: 34jh1231ll}validate(testTicket, ticket).then(value {console.log(validated)console.log(value)done()}).catch(err {console.log(err)done()})})
}) 下一个要审查的文件将是 在这一点上我们开始陷入很多麻烦为什么因为在这里我们将与两个外部服务进行交互支付服务和通知服务这种交互可以引导我们重新思考微服务的架构 有一个叫做事件驱动的数据管理和 CQRS但这些主题将被保存到本系列的后续章节中并且不会使本章变得冗长和复杂因此在此期间让我们简化与本章服务的交互。api/booking.js use strict
const status require(http-status)module.exports ({repo}, app) {app.post(/booking, (req, res, next) {// we grab the dependencies need it for this routeconst validate req.container.resolve(validate)const paymentService req.container.resolve(paymentService)const notificationService req.container.resolve(notificationService)Promise.all([validate(req.body.user, user),validate(req.body.booking, booking)]).then(([user, booking]) {const payment {userName: user.name user.lastName,currency: mxn,number: user.creditCard.number,cvc: user.creditCard.cvc,exp_month: user.creditCard.exp_month,exp_year: user.creditCard.exp_year,amount: booking.amount,description: Tickect(s) for movie ${booking.movie},with seat(s) ${booking.seats.toString()}at time ${booking.schedule}}return Promise.all([// we call the payment servicepaymentService(payment),Promise.resolve(user),Promise.resolve(booking)])}).then(([paid, user, booking]) {return Promise.all([repo.makeBooking(user, booking),repo.generateTicket(paid, booking)])}).then(([booking, ticket]) {// we call the notification servicenotificationService({booking, ticket})res.status(status.OK).json(ticket)}).catch(next)})app.get(/booking/verify/:orderId, (req, res, next) {repo.getOrderById(req.params.orderId).then(order {res.status(status.OK).json(order)}).catch(next)})
} 正如你在这里看到的我们正在使用 expressjs 中间件我们正在利用从单一事实来源注册依赖项的容器。 但是DI的容器在哪里 好吧我们对项目结构进行了一些更改主要是在文件夹中现在如下所示config .
|-- config
| |-- db
| | |-- index.js
| | |-- mongo.js
| | -- mongo.spec.js
| |-- di
| | |-- di.js
| | -- index.js
| |-- ssl
| | |-- certificates
| | -- index.js
| |-- config.js
| |-- index.spec.js
| -- index.js 在文件中我们主要包括所有配置以及 DI 服务config/index.js const {dbSettings, serverSettings} require(./config)
const database require(./db)
const {initDI} require(./di)
const models require(../models)
const services require(../services)
const init initDI.bind(null, {serverSettings, dbSettings, database, models, services})
module.exports Object.assign({}, {init}) 在上面的代码中我们看到了一些罕见的东西让我再次为您缩放它 initDI.bind(null, {serverSettings, dbSettings, database, models, services}) 我们在这里做什么我说我们正在配置 DI但在这里我们正在制作一种叫做控制反转的东西是的是的我知道这是很多技术术语可能听起来很臃肿但它很容易理解一旦你得到它如果你还没有听说过 IoC我建议你观看以下视频 所以我们的 DI 函数不需要知道我们的依赖项来自哪里它只需要注册我们的依赖项即可在我们的应用程序中使用所以我们的文件如下所示di.js const { createContainer, asValue, asFunction, asClass } require(awilix)function initDI ({serverSettings, dbSettings, database, models, services}, mediator) {mediator.once(init, () {mediator.on(db.ready, (db) {const container createContainer()// loading dependecies in a single source of truthcontainer.register({database: asValue(db).singleton(),validate: asValue(models.validate),booking: asValue(models.booking),user: asValue(models.booking),ticket: asValue(models.booking),ObjectID: asClass(database.ObjectID),serverSettings: asValue(serverSettings),paymentService: asValue(services.paymentService),notificationService: asValue(services.notificationService)})// we emit the container to be able to use it in the APImediator.emit(di.ready, container)})mediator.on(db.error, (err) {mediator.emit(di.error, err)})database.connect(dbSettings, mediator)mediator.emit(boot.ready)})
}module.exports.initDI initDI 如您所见我们正在使用一个名为依赖注入的 npm 包awilix 在 nodejs 中实现了依赖注入的机制我目前正在评估这个库但我在这里使用它来说明示例所以要安装它我们需要执行下一个命令awilix npm i -S awilix --silent 要进一步了解 awilix 的工作原理您可以查看作者在以下链接中撰写的依赖注入系列文章DI 系列和 awilix 文档。 现在或主文件将如下所示index.js use strict
const {EventEmitter} require(events)
const server require(./server/server)
const repository require(./repository/repository)
const di require(./config)
const mediator new EventEmitter()console.log(--- Booking Service ---)
console.log(Connecting to movies repository...)process.on(uncaughtException, (err) {console.error(Unhandled Exception, err)
})process.on(uncaughtRejection, (err, promise) {console.error(Unhandled Rejection, err)
})mediator.on(di.ready, (container) {repository.connect(container).then(repo {container.registerFunction({repo})return server.start(container)}).then(app {app.on(close, () {container.resolve(repo).disconnect()})})
})di.init(mediator)mediator.emit(init) 正如你现在看到的我们只使用一个单一的事实来源它有我们需要的所有依赖项可以通过容器请求它那么我们如何将其设置为expressjs中间件就像之前注释的那样它只是几行代码 const express require(express)
const morgan require(morgan)
const helmet require(helmet)
const bodyparser require(body-parser)
const cors require(cors)
const spdy require(spdy)
const _api require(../api/booking)const start (container) {return new Promise((resolve, reject) {// here we grab our dependencies needed for the serverconst {repo, port, ssl} container.resolve(serverSettings)if (!repo) {reject(new Error(The server must be started with a connected repository))}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) {if (err) {reject(new Error(Something went wrong!, err: err))res.status(500).send(Something went wrong!)}next()})// here is where we register the container as middlewareapp.use((req, res, next) {req.container container.createScope()next()})// here we inject the repo to the API, since the repo is need it for all of our functions// and we are using inversion of control to make it availableconst api _api.bind(null, {repo: container.resolve(repo)})api(app)if (process.env.NODE test) {const server app.listen(port, () resolve(server))} else {const server spdy.createServer(ssl, app).listen(port, () resolve(server))}})
}所以基本上我们将容器对象附加到 expressjs req 对象这就是我们如何通过所有 expressjs 路由使用它。如果你想更深入地了解中间件如何与 expressjs 一起工作你可以访问此链接并查看 expressjs 文档。 嗯有句话说越好越好最后我们要审查文件repository.js use strict
const repository (container) {// we get the db object via the containerconst {db} container.resolve(database)const makeBooking (user, booking) {return new Promise((resolve, reject) {// payload to be insterted to the booking collection const payload {city: booking.city,cinema: booking.cinema,book: {userType: (user.membership) ? loyal : normal,movie: {title: booking.movie.title,format: booking.movie.format,schedule: booking.schedule}}}db.collection(booking).insertOne(payload, (err, booked) {if (err) {reject(new Error(An error occuered registring a user booking, err: err))}resolve(booked)})})}const generateTicket (paid, booking) {return new Promise((resolve, reject) {// payload of ticketconst payload Object.assign({}, {booking, orderId: paid._id})db.collection(tickets).insertOne(payload, (err, ticket) {if (err) {reject(new Error(an error occured registring a ticket, err: err))}resolve(ticket)})})}const getOrderById (orderId) {return new Promise((resolve, reject) {const ObjectID container.resolve(ObjectID)const query {_id: new ObjectID(orderId)}const response (err, order) {if (err) {reject(new Error(An error occuered retrieving a order, err: err))}resolve(order)}db.collection(booking).findOne(query, {}, response)})}const disconnect () {db.close()}return Object.create({makeBooking,getOrderById,generateTicket,disconnect})
}const connect (container) {return new Promise((resolve, reject) {if (!container.resolve(database)) {reject(new Error(connection db not supplied!))}resolve(repository(container))})
}module.exports Object.assign({}, {connect}) 好的所以在我们的相关性上没有太多的相关性可能是我们第一次在系列中使用该方法但是我想在这个文件中指出一件事特别是在方法上如果您看到有效负载对象这是集合数据模型模式但为什么 为什么我们会使用这种方法如果我们使用它我们不是会重复很多信息吗repository.jsinsertOne()makeBooking() 嗯是的我们将重复信息这不是最佳做法但这是有原因的直到下次我才会告诉你为什么是因为该系列有一些非常有趣的东西...... 如果你想要一个提示我会留下这个给你好奇 ----------------------------------------| || v| Jane ------(went to)----------| | || | (loyal vistor) || v vJoe --(normal visitor)-- Movie Name --(displayed)-- Plaza Morelia| || (format) | (city)v v4DX Morelia 如果您能发现即将发生的事情欢迎您在评论部分发表评论。 好吧让我们继续我们已经评论说我们正在与两个外部服务进行交互为简单起见让我们看看我们需要从这些外部服务中获得什么 # for the payment service we will need to implement something like the following
module.exports (paymentOrder) {return new Promise((resolve, reject) {supertest(url to the payment service).get(/makePurchase).send({paymentOrder}).end((err, res) {if (err) {reject(new Error(An error occured with the payment service, err: err))}resolve(res.body.payment)})})
}
# since we havent made the payment service yet, lets make something simple to fulfill the article example, like the following
module.exports (paymentOrder) {return new Promise((resolve, reject) {resolve({orderId: Math.floor((Math.random() * 1000) 1)})})
}
# for the notification service, at the moment we dont need any information from this service we will not implement it, this service will have the task for sending an email, sms or another notification, but we will make this service in the next chapter. 好吧我们已经完成了这个微服务的构建所以现在是时候使用以下命令在存储库中执行文件了 $ bash start_service 让我们的微服务准备就绪并完全正常运行到 docker 容器中并开始进行集成测试。 五、是时候回顾一下了 我们做了什么...如果您遵循了我之前的章节我们有一个如下系统架构 影院系统架构 如果你注意到我们的系统开始成形但有些东西让我们感觉不对那就是在工作线程 1 和工作线程 2 中我们没有任何微服务运行那是因为我们没有在其中创建任何服务但我们很快就会这样做。docker-machines 现在在影院微服务架构中我们几乎完成了下图 我们只是构建预订服务然后简单实现支付服务和通知服务。 因此我们在本章中学习了依赖注入我们看到了一点 SOLID 原则和控制反转使用 NodeJS我们还在微服务中发出了第一个 POST 请求我们还学习了如何使用 joi 库验证对象和数据。 我们已经在 NodeJS 中看到了很多开发但我们可以做和学习的东西还有很多这只是一个先睹为快的高峰。我希望这已经展示了一些有趣和有用的东西你可以在你的工作流程中用于Docker和NodeJS。 六、即将推出 在接下来的剧集中我们将创建并完成支付服务和通知服务的实现但这不是有趣的部分有趣的是我们将创建我们的 API 网关因为我们的影院微服务开始增长并且微服务有必要相互通信。但是要拥有一个非常强大的微服务系统还有很多事情要做在后面的章节中我们将看到如何使十二因素应用程序适应微服务。 # 在 Github 上完成代码 您可以在以下链接中查看文章的完整代码。克里斯蒂安·拉米雷斯 ·