如何构建一个 NodeJS 影院微服务并使用 Docker 部署

前言

如何构建一个 NodeJS 影院微服务并使用 Docker 部署。在这个系列中,将构建一个 NodeJS 微服务,并使用 Docker Swarm 集群进行部署。

以下是将要使用的工具:

  • NodeJS 版本7.2.0
  • MongoDB 3.4.1
  • Docker for Mac 1.12.6

在尝试本指南之前,应该具备:

  • NodeJS 的基本知识
  • Docker 的基本知识(并且已经安装了 Docker)
  • MongoDB 的基本知识(并且数据库服务正在运行)

什么是微服务?

微服务是一个单独的自包含单元,与其他许多单元一起构成一个大型应用程序。通过将应用程序拆分为小单元,每个部分都可以独立部署和扩展,可以由不同的团队和不同的编程语言编写,并且可以单独进行测试。

微服务架构意味着应用程序由许多较小的、独立的应用程序组成,这些应用程序能够在自己的内存空间中运行,并且可以在可能的多个独立计算机上独立扩展。

微服务的好处:

  • 应用程序启动更快,这使得开发人员更具生产力,并加快了部署速度。
  • 每个服务可以独立于其他服务部署 — 更容易频繁部署服务的新版本。
  • 更容易扩展开发,也可能具有性能优势。
  • 消除对技术栈的长期承诺。在开发新服务时,可以选择新的技术栈。
  • 微服务通常更好组织,因为每个微服务有一个非常具体的工作,不涉及其他组件的工作。
  • 解耦的服务也更容易重新组合和重新配置,以服务不同应用程序的目的(例如,同时为 Web 客户端和公共 API 提供服务)。

微服务的缺点:

  • 开发人员必须处理创建分布式系统的额外复杂性。
  • 部署复杂性。在生产环境中,部署和管理许多不同服务类型的系统也会带来操作复杂性。
  • 在构建新的微服务架构时,可能会发现许多交叉关注点,这些交叉关注点在设计时没有预料到。

构建电影目录微服务

假设正在一家电影院的 IT 部门工作,给了我们一个任务,将他们的电影票务和杂货店从单体系统重构为微服务。

因此,在“构建 NodeJS 电影目录微服务”系列中,将仅关注电影目录服务。

在这个架构中,可以看到有 3 种不同的设备使用该微服务,即 POS(销售点)、移动设备/平板电脑和计算机。POS 和移动设备/平板电脑都有自己的应用程序(在 electron 中开发),并直接使用微服务,而计算机则通过 Web 应用程序访问微服务(一些专家也将 Web 应用程序视为微服务)。

构建微服务

现在,来模拟在最喜欢的电影院预订一场电影首映的过程。

首先,想看看电影院目前正在上映哪些电影。以下图表显示了通过 REST 进行的内部通信,通过此 REST 通信,可以使用 API 来获取目前正在上映的电影。

电影服务的 API 将具有以下 RAML 规范:

#%RAML 1.0
title: cinema
version: v1
baseUri: / types:
Movie:
properties:
id: string
title: string
runtime: number
format: string
plot: string
releaseYear: number
releaseMonth: number
releaseDay: number
example:
id: "123"
title: "Assasins Creed"
runtime: 115
format: "IMAX"
plot: "Lorem ipsum dolor sit amet"
releaseYear : 2017
releaseMonth: 1
releaseDay: 6 MoviePremieres:
type: Movie [] resourceTypes:
Collection:
get:
responses:
200:
body:
application/json:
type: <<item>> /movies:
/premieres:
type: { Collection: {item : MoviePremieres } } /{id}:
type: { Collection: {item : Movie } }
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

如果不了解 RAML,可以查看这个很好的教程。

API 项目的结构将如下所示:

  • api/ # 我们的API
  • config/ # 应用程序配置
  • mock/ # 不是必需的,仅用于数据示例
  • repository/ # 抽象出数据库
  • server/ # 服务器设置代码
  • package.json # 依赖项
  • index.js # 应用程序的主入口

首先要看的部分是 repository。这是对数据库进行查询的地方。


'use strict'
// factory function, that holds an open connection to the db,
// and exposes some functions for accessing the data.
const repository = (db) => { // since this is the movies-service, we already know
// that we are going to query the `movies` collection
// in all of our functions.
const collection = db.collection('movies') const getMoviePremiers = () => {
return new Promise((resolve, reject) => {
const movies = []
const currentDay = new Date()
const query = {
releaseYear: {
$gt: currentDay.getFullYear() - 1,
$lte: currentDay.getFullYear()
},
releaseMonth: {
$gte: currentDay.getMonth() + 1,
$lte: currentDay.getMonth() + 2
},
releaseDay: {
$lte: currentDay.getDate()
}
}
const cursor = collection.find(query)
const addMovie = (movie) => {
movies.push(movie)
}
const sendMovies = (err) => {
if (err) {
reject(new Error('An error occured fetching all movies, err:' + err))
}
resolve(movies)
}
cursor.forEach(addMovie, sendMovies)
})
} const getMovieById = (id) => {
return new Promise((resolve, reject) => {
const projection = { _id: 0, id: 1, title: 1, format: 1 }
const sendMovie = (err, movie) => {
if (err) {
reject(new Error(`An error occured fetching a movie with id: ${id}, err: ${err}`))
}
resolve(movie)
}
// fetch a movie by id -- mongodb syntax
collection.findOne({id: id}, projection, sendMovie)
})
} // this will close the database connection
const disconnect = () => {
db.close()
} return Object.create({
getAllMovies,
getMoviePremiers,
getMovieById,
disconnect
})
} const connect = (connection) => {
return new Promise((resolve, reject) => {
if (!connection) {
reject(new Error('connection db not supplied!'))
}
resolve(repository(connection))
})
}
// this only exports a connected repo
module.exports = Object.assign({}, {connect})
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

可能已经注意到,向 repository 的 connect ( connection ) 方法提供了一个 connection 对象。在这里,使用了 JavaScript 的一个重要特性“闭包”,repository 对象返回了一个闭包,其中的每个函数都可以访问 db 对象和 collection 对象,db 对象保存着数据库连接。在这里,抽象了连接的数据库类型,repository 对象不知道数据库是什么类型的,对于这种情况来说,是一个 MongoDB 连接。甚至不需要知道是单个数据库还是复制集连接。虽然使用了 MongoDB 语法,但可以通过应用 SOLID 原则中的依赖反转原则,将存储库功能抽象得更深,将 MongoDB 语法转移到另一个文件中,并只调用数据库操作的接口(例如,使用 mongoose 模型)。

有一个 repository/repository.spec.js 文件来测试这个模块,稍后在文章中会谈到测试。

接下来要看的是 server.js 文件。

'use strict'
const express = require('express')
const morgan = require('morgan')
const helmet = require('helmet')
const movieAPI = require('../api/movies') const start = (options) => {
return new Promise((resolve, reject) => {
// we need to verify if we have a repository added and a server port
if (!options.repo) {
reject(new Error('The server must be started with a connected repository'))
}
if (!options.port) {
reject(new Error('The server must be started with an available port'))
}
// let's init a express app, and add some middlewares
const app = express()
app.use(morgan('dev'))
app.use(helmet())
app.use((err, req, res, next) => {
reject(new Error('Something went wrong!, err:' + err))
res.status(500).send('Something went wrong!')
}) // we add our API's to the express app
movieAPI(app, options) // finally we start the server, and return the newly created server
const server = app.listen(options.port, () => resolve(server))
})
} module.exports = Object.assign({}, {start})
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

在这里,创建了一个新的 express 应用程序,验证是否提供了 repository 和 server port 对象,然后为 express 应用程序应用一些中间件,例如用于日志记录的 morgan,用于安全性的 helmet,以及一个错误处理函数,最后导出一个 start 函数来启动服务器。

Helmet 包含了整整 11 个软件包,它们都用于阻止恶意方破坏或使用应用程序来伤害其用户。

好的,现在既然服务器使用了电影的 API,继续查看 movies.js 文件。

'use strict'
const status = require('http-status') module.exports = (app, options) => {
const {repo} = options // here we get all the movies
app.get('/movies', (req, res, next) => {
repo.getAllMovies().then(movies => {
res.status(status.OK).json(movies)
}).catch(next)
}) // here we retrieve only the premieres
app.get('/movies/premieres', (req, res, next) => {
repo.getMoviePremiers().then(movies => {
res.status(status.OK).json(movies)
}).catch(next)
}) // here we get a movie by id
app.get('/movies/:id', (req, res, next) => {
repo.getMovieById(req.params.id).then(movie => {
res.status(status.OK).json(movie)
}).catch(next)
})
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

在这里,为API创建了路由,并根据监听的路由调用了 repo 函数。repo 在这里使用了接口技术方法,在这里使用了著名的“为接口编码而不是为实现编码”,因为 express 路由不知道是否有一个数据库对象、数据库查询逻辑等,它只调用处理所有数据库问题的 repo 函数。

所有文件都有与源代码相邻的单元测试,看看 movies.js 的测试是如何进行的。

可以将测试看作是对正在构建的应用程序的安全保障。不仅会在本地机器上运行,还会在 CI 服务上运行,以确保失败的构建不会被推送到生产系统。

为了编写单元测试,必须对所有依赖项进行存根,即为模块提供虚拟依赖项。看看 spec 文件。

/* eslint-env mocha */
const request = require('supertest')
const server = require('../server/server') describe('Movies API', () => {
let app = null
let testMovies = [{
'id': '3',
'title': 'xXx: Reactivado',
'format': 'IMAX',
'releaseYear': 2017,
'releaseMonth': 1,
'releaseDay': 20
}, {
'id': '4',
'title': 'Resident Evil: Capitulo Final',
'format': 'IMAX',
'releaseYear': 2017,
'releaseMonth': 1,
'releaseDay': 27
}, {
'id': '1',
'title': 'Assasins Creed',
'format': 'IMAX',
'releaseYear': 2017,
'releaseMonth': 1,
'releaseDay': 6
}] let testRepo = {
getAllMovies () {
return Promise.resolve(testMovies)
},
getMoviePremiers () {
return Promise.resolve(testMovies.filter(movie => movie.releaseYear === 2017))
},
getMovieById (id) {
return Promise.resolve(testMovies.find(movie => movie.id === id))
}
} beforeEach(() => {
return server.start({
port: 3000,
repo: testRepo
}).then(serv => {
app = serv
})
}) afterEach(() => {
app.close()
app = null
}) it('can return all movies', (done) => {
request(app)
.get('/movies')
.expect((res) => {
res.body.should.containEql({
'id': '1',
'title': 'Assasins Creed',
'format': 'IMAX',
'releaseYear': 2017,
'releaseMonth': 1,
'releaseDay': 6
})
})
.expect(200, done)
}) it('can get movie premiers', (done) => {
request(app)
.get('/movies/premiers')
.expect((res) => {
res.body.should.containEql({
'id': '1',
'title': 'Assasins Creed',
'format': 'IMAX',
'releaseYear': 2017,
'releaseMonth': 1,
'releaseDay': 6
})
})
.expect(200, done)
}) it('returns 200 for an known movie', (done) => {
request(app)
.get('/movies/1')
.expect((res) => {
res.body.should.containEql({
'id': '1',
'title': 'Assasins Creed',
'format': 'IMAX',
'releaseYear': 2017,
'releaseMonth': 1,
'releaseDay': 6
})
})
.expect(200, done)
})
})
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
/* eslint-env mocha */
const server = require('./server') describe('Server', () => {
it('should require a port to start', () => {
return server.start({
repo: {}
}).should.be.rejectedWith(/port/)
}) it('should require a repository to start', () => {
return server.start({
port: {}
}).should.be.rejectedWith(/repository/)
})
})
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

可以看到,为 movies API 存根了依赖项,并验证了需要提供一个 server port 和一个 repository 对象。

继续看一下如何创建传递给 repository 模块的 db 连接对象,现在定义说每个微服务都必须有自己的数据库,但是对于示例,将使用一个 MongoDB 复制集服务器,但每个微服务都有自己的数据库。

从 NodeJS 连接到 MongoDB 数据库

以下是需要从 NodeJS 连接到 MongoDB 数据库的配置。

const MongoClient = require('mongodb')

// here we create the url connection string that the driver needs
const getMongoURL = (options) => {
const url = options.servers
.reduce((prev, cur) => prev + `${cur.ip}:${cur.port},`, 'mongodb://') return `${url.substr(0, url.length - 1)}/${options.db}`
} // mongoDB function to connect, open and authenticate
const connect = (options, mediator) => {
mediator.once('boot.ready', () => {
MongoClient.connect( getMongoURL(options), {
db: options.dbParameters(),
server: options.serverParameters(),
replset: options.replsetParameters(options.repl)
}, (err, db) => {
if (err) {
mediator.emit('db.error', err)
} db.admin().authenticate(options.user, options.pass, (err, result) => {
if (err) {
mediator.emit('db.error', err)
}
mediator.emit('db.ready', db)
})
})
})
} module.exports = Object.assign({}, {connect})
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

这里可能有更好的方法,但基本上可以这样创建与 MongoDB 的复制集连接。

传递了一个 options 对象,其中包含 Mongo 连接所需的所有参数,并且传递了一个事件 — 中介者对象,当通过认证过程时,它将发出 db 对象。

注意 在这里,使用了一个事件发射器对象,因为使用 promise 的方法在某种程度上并没有在通过认证后返回 db 对象,顺序变得空闲。所以这可能是一个很好的挑战,看看发生了什么,并尝试使用 promise 的方法。

现在,既然正在传递一个 options 对象来进行参数设置,让我们看看这是从哪里来的,因此要查看的下一个文件是 config.js。

// simple configuration file

// database parameters
const dbSettings = {
db: process.env.DB || 'movies',
user: process.env.DB_USER || 'cristian',
pass: process.env.DB_PASS || 'cristianPassword2017',
repl: process.env.DB_REPLS || 'rs1',
servers: (process.env.DB_SERVERS) ? process.env.DB_SERVERS.split(' ') : [
'192.168.99.100:27017',
'192.168.99.101:27017',
'192.168.99.102:27017'
],
dbParameters: () => ({
w: 'majority',
wtimeout: 10000,
j: true,
readPreference: 'ReadPreference.SECONDARY_PREFERRED',
native_parser: false
}),
serverParameters: () => ({
autoReconnect: true,
poolSize: 10,
socketoptions: {
keepAlive: 300,
connectTimeoutMS: 30000,
socketTimeoutMS: 30000
}
}),
replsetParameters: (replset = 'rs1') => ({
replicaSet: replset,
ha: true,
haInterval: 10000,
poolSize: 10,
socketoptions: {
keepAlive: 300,
connectTimeoutMS: 30000,
socketTimeoutMS: 30000
}
})
} // server parameters
const serverSettings = {
port: process.env.PORT || 3000
} module.exports = Object.assign({}, { dbSettings, serverSettings })
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

这是配置文件,大部分配置代码都是硬编码的,但正如看到的,一些属性使用环境变量作为选项。环境变量被视为最佳实践,因为这可以隐藏数据库凭据、服务器参数等。

最后,对于构建电影服务 API 的最后一步是使用 index.js 将所有内容组合在一起。

'use strict'
// we load all the depencies we need
const {EventEmitter} = require('events')
const server = require('./server/server')
const repository = require('./repository/repository')
const config = require('./config/')
const mediator = new EventEmitter() // verbose logging when we are starting the server
console.log('--- Movies Service ---')
console.log('Connecting to movies repository...') // log unhandled execpetions
process.on('uncaughtException', (err) => {
console.error('Unhandled Exception', err)
})
process.on('uncaughtRejection', (err, promise) => {
console.error('Unhandled Rejection', err)
}) // event listener when the repository has been connected
mediator.on('db.ready', (db) => {
let rep
repository.connect(db)
.then(repo => {
console.log('Repository Connected. Starting Server')
rep = repo
return server.start({
port: config.serverSettings.port,
repo
})
})
.then(app => {
console.log(`Server started succesfully, running on port: ${config.serverSettings.port}.`)
app.on('close', () => {
rep.disconnect()
})
})
})
mediator.on('db.error', (err) => {
console.error(err)
}) // we load the connection to the repository
config.db.connect(config.dbSettings, mediator)
// init the repository connection, and the event listener will handle the rest
mediator.emit('boot.ready')
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

在这里,组合了所有的电影 API 服务,添加了一些错误处理,然后加载配置、启动存储库,并最后启动服务器。

因此,到目前为止,已经完成了与 API 开发相关的所有内容。

下面是项目中需要用到的初始化以及运行命令:

  • npm install # 设置Node依赖项
  • npm test # 使用mocha进行单元测试
  • npm start # 启动服务
  • npm run node-debug # 以调试模式运行服务器
  • npm run chrome-debug # 使用chrome调试Node
  • npm run lint # 使用standard进行代码lint

最后,第一个微服务已经在本地运行,并通过执行 npm start 命令启动。

现在是时候将其放入 Docker 容器中。

首先,需要使用“使用 Docker 部署 MongoDB 复制集”的文章中的 Docker 环境,如果没有,则需要进行一些额外的修改步骤,以便为微服务设置数据库,以下是一些命令,进行测试电影服务。

首先创建 Dockerfile,将 NodeJS 微服务制作成 Docker 容器。

# Node v7作为基本映像以支持ES6
FROM node:7.2.0
# 为新容器创建一个新用户,并避免root用户
RUN useradd --user-group --create-home --shell /bin/false nupp && \
apt-get clean
ENV HOME=/home/nupp
COPY package.json npm-shrinkwrap.json $HOME/app/
COPY src/ $HOME/app/src
RUN chown -R nupp:nupp $HOME/* /usr/local/
WORKDIR $HOME/app
RUN npm cache clean && \
npm install --silent --progress=false --production
RUN chown -R nupp:nupp $HOME/*
USER nupp
EXPOSE 3000
CMD ["npm", "start"]
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

使用 NodeJS 镜像作为 Docker 镜像的基础,然后为镜像创建一个用户以避免非 root 用户,接下来,将 src 复制到镜像中,然后安装依赖项,暴露一个端口号,并最后实例化电影服务。

接下来,需要构建 Docker 镜像,使用以下命令:

$ docker build -t movies-service .
 
  • 1

首先看一下构建命令。

  • docker build 告诉引擎要创建一个新的镜像。
  • -t movies-service 用标记 movies-service 标记此镜像。从现在开始,可以根据标记引用此镜像。
  • .:使用当前目录来查找 Dockerfile

经过一些控制台输出后,新镜像中就有了 NodeJS 应用程序,所以现在需要做的就是使用以下命令运行镜像:

$ docker run --name movie-service -p 3000:3000 -e DB_SERVERS="192.168.99.100:27017 192.168.99.101:27017 192.168.99.100:27017" -d movies-service
 
  • 1

在上面的命令中,传递了一个环境变量,它是一个服务器数组,需要连接到 MongoDB 复制集的服务器,这只是为了说明,有更好的方法可以做到这一点,比如读取一个环境变量文件。

现在,容器已经运行起来了,获取 docker-machine IP地址,以获取微服务的 IP 地址,现在准备对微服务进行一次集成测试,另一个测试选项可以是JMeter,它是一个很好的工具,可以模拟HTTP请求。

这是集成测试,将检查一个 API 调用。

/* eslint-env mocha */
const supertest = require('supertest') describe('movies-service', () => { const api = supertest('http://192.168.99.100:3000') it('returns a 200 for a collection of movies', (done) => { api.get('/movies/premiers')
.expect(200, done)
})
})
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

总结

创建了用于查询电影院正在上映的电影的 movies 服务,使用 RAML 规范设计了 API,然后开始构建 API,并进行了相应的单元测试,最后,组合了所有内容,使微服务完整,并能够启动 movies 服务服务器。

然后,将微服务放入 Docker 容器中,以进行一些集成测试。

微服务架构可以为大型应用程序带来许多好处,但也需要小心管理和设计,以处理分布式系统的复杂性和其他挑战。使用 Docker 容器可以简化微服务的部署和管理,使其更加灵活和可扩展。

如何构建一个 NodeJS 影院微服务并使用 Docker 部署的更多相关文章

  1. Taurus.MVC 微服务框架 入门开发教程:项目部署:6、微服务应用程序Docker部署实现多开。

    系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 开源地址:https://github.com/cyq1162/Taurus.MVC 本系列第一篇:Tauru ...

  2. Taurus.MVC 微服务框架 入门开发教程:项目部署:1、微服务应用程序常规部署实现多开,节点扩容。

    系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 本系列第一篇:Taurus.MVC V3.0.3 微服务开源框架发布:让.NET 架构在大并发的演进过程更简单 ...

  3. 使用MicroService4Net 快速创建一个简单的微服务

    “微服务架构(Microservice Architecture)”一词在过去几年里广泛的传播,它用于描述一种设计应用程序的特别方式,作为一套独立可部署的服务.目前,这种架构方式还没有准确的定义,但是 ...

  4. gRPC初探——概念介绍以及如何构建一个简单的gRPC服务

    目录 引言 1. gRPC简介 2. 使用Protocol Buffers进行服务定义 2.1 定义消息 2.2 定义服务接口 3.构建简单的gRPC服务 3.1 编写proto文件,定义消息和接口 ...

  5. [转] Akka实战:构建REST风格的微服务

    [From] http://www.yangbajing.me/2015/11/27/akka%E5%AE%9E%E6%88%98%EF%BC%9A%E6%9E%84%E5%BB%BArest%E9% ...

  6. 在 ASP.NET Core Web API中使用 Polly 构建弹性容错的微服务

    在 ASP.NET Core Web API中使用 Polly 构建弹性容错的微服务 https://procodeguide.com/programming/polly-in-aspnet-core ...

  7. Soa: 一个轻量级的微服务库

    Soa 项目地址:Github:MatoApps/Soa 介绍 一个轻量级的微服务库,基于.Net 6 + Abp框架 可快速地将现有项目改造成为面向服务体系结构,实现模块间松耦合. 感谢 Rabbi ...

  8. 构建一个在线ASCII视频流服务

    构建一个在线ASCII视频流服务 2018-03-26  正常的文章 1685 什么是ASCII视频流服务? 其实这个名字是咱胡乱起的,具体叫啥我也不清楚,但效果如下: 大家可以在自己的命令行里试下, ...

  9. Nodejs课堂笔记-第三课 构建一个nodejs的Docker镜像

    本文由Vikings(http://www.cnblogs.com/vikings-blog/) 原创,转载请标明.谢谢! 因为一直做Linux有关的开发工作,所以不习惯在Windows平台编译和测试 ...

  10. 微服务从设计到部署(二)使用 API 网关

    链接:https://github.com/oopsguy/microservices-from-design-to-deployment-chinese 译者:Oopsguy 本书的七个章节是关于设 ...

随机推荐

  1. 【matplotlib 实战】--折线图

    折线图是一种用于可视化数据变化趋势的图表,它可以用于表示任何数值随着时间或类别的变化. 折线图由折线段和折线交点组成,折线段表示数值随时间或类别的变化趋势,折线交点表示数据的转折点. 折线图的方向表示 ...

  2. 【matplotlib 实战】--柱状图

    柱状图,是一种使用矩形条,对不同类别进行数值比较的统计图表.在柱状图上,分类变量的每个实体都被表示为一个矩形(通俗讲即为"柱子"),而数值则决定了柱子的高度. 1. 主要元素 柱状 ...

  3. FreeRTOS 操作系统

    FreeRTOS操作系统 01 FreeRTOS 的定义和概述 定义:FreeRTOS(Free-Real-Time Operating System)是一个开源的实时操作系统内核,专门为嵌入式系统设 ...

  4. Go接口 - 构建可扩展Go应用

    本文深入探讨了Go语言中接口的概念和实际应用场景.从基础知识如接口的定义和实现,到更复杂的实战应用如解耦与抽象.多态.错误处理.插件架构以及资源管理,文章通过丰富的代码示例和详细的解释,展示了Go接口 ...

  5. CF B. Gardener and the Array

    B. Gardener and the Array 思路:只要找到一个c他的每一位均在除了它的集合中出现过即可 这题T了2发,用来multiset,注意multiset大的时间复杂度是O(K + lo ...

  6. Asp.Net Core webapi+net6 使用资源筛选器(过滤器) 做缓存

    写一个特性类,用来做标记 [AttributeUsage(AttributeTargets.Method)] //只对方法有效 public class ResourceFilterAttribute ...

  7. Python 用户输入和字符串格式化指南

    Python 允许用户输入数据.这意味着我们可以向用户询问输入.在 Python 3.6 中,使用 input() 方法来获取用户输入.在 Python 2.7 中,使用 raw_input() 方法 ...

  8. 背包DP全类型

    AcWing 2. 01背包问题 #include <iostream> #include <algorithm> using namespace std; const int ...

  9. Codeforces Round #706 (Div. 2) A-D题解

    写在前边 链接:Codeforces Round #706 (Div. 2) \(A,B,C,D\),这场有点简单,不过由于A写炸了后边题连看都没看就溜了,就从上大分变成了掉大分 A. Split i ...

  10. C/C++ 开发SCM服务管理组件

    SCM(Service Control Manager)服务管理器是 Windows 操作系统中的一个关键组件,负责管理系统服务的启动.停止和配置.服务是一种在后台运行的应用程序,可以在系统启动时自动 ...