如何構(gòu)建一個(gè)?NodeJS?影院微服務(wù)并使用?Docker?部署
前言
如何構(gòu)建一個(gè) NodeJS 影院微服務(wù)并使用 Docker 部署。在這個(gè)系列中,將構(gòu)建一個(gè) NodeJS 微服務(wù),并使用 Docker Swarm 集群進(jìn)行部署。
以下是將要使用的工具:
- NodeJS 版本7.2.0
- MongoDB 3.4.1
- Docker for Mac 1.12.6
在嘗試本指南之前,應(yīng)該具備:
- NodeJS 的基本知識(shí)
- Docker 的基本知識(shí)(并且已經(jīng)安裝了 Docker)
- MongoDB 的基本知識(shí)(并且數(shù)據(jù)庫服務(wù)正在運(yùn)行)
什么是微服務(wù)?
微服務(wù)是一個(gè)單獨(dú)的自包含單元,與其他許多單元一起構(gòu)成一個(gè)大型應(yīng)用程序。通過將應(yīng)用程序拆分為小單元,每個(gè)部分都可以獨(dú)立部署和擴(kuò)展,可以由不同的團(tuán)隊(duì)和不同的編程語言編寫,并且可以單獨(dú)進(jìn)行測(cè)試。
微服務(wù)架構(gòu)意味著應(yīng)用程序由許多較小的、獨(dú)立的應(yīng)用程序組成,這些應(yīng)用程序能夠在自己的內(nèi)存空間中運(yùn)行,并且可以在可能的多個(gè)獨(dú)立計(jì)算機(jī)上獨(dú)立擴(kuò)展。
微服務(wù)的好處:
- 應(yīng)用程序啟動(dòng)更快,這使得開發(fā)人員更具生產(chǎn)力,并加快了部署速度。
- 每個(gè)服務(wù)可以獨(dú)立于其他服務(wù)部署 — 更容易頻繁部署服務(wù)的新版本。
- 更容易擴(kuò)展開發(fā),也可能具有性能優(yōu)勢(shì)。
- 消除對(duì)技術(shù)棧的長期承諾。在開發(fā)新服務(wù)時(shí),可以選擇新的技術(shù)棧。
- 微服務(wù)通常更好組織,因?yàn)槊總€(gè)微服務(wù)有一個(gè)非常具體的工作,不涉及其他組件的工作。
- 解耦的服務(wù)也更容易重新組合和重新配置,以服務(wù)不同應(yīng)用程序的目的(例如,同時(shí)為 Web 客戶端和公共 API 提供服務(wù))。
微服務(wù)的缺點(diǎn):
- 開發(fā)人員必須處理創(chuàng)建分布式系統(tǒng)的額外復(fù)雜性。
- 部署復(fù)雜性。在生產(chǎn)環(huán)境中,部署和管理許多不同服務(wù)類型的系統(tǒng)也會(huì)帶來操作復(fù)雜性。
- 在構(gòu)建新的微服務(wù)架構(gòu)時(shí),可能會(huì)發(fā)現(xiàn)許多交叉關(guān)注點(diǎn),這些交叉關(guān)注點(diǎn)在設(shè)計(jì)時(shí)沒有預(yù)料到。
構(gòu)建電影目錄微服務(wù)
假設(shè)正在一家電影院的 IT 部門工作,給了我們一個(gè)任務(wù),將他們的電影票務(wù)和雜貨店從單體系統(tǒng)重構(gòu)為微服務(wù)。
因此,在“構(gòu)建 NodeJS 電影目錄微服務(wù)”系列中,將僅關(guān)注電影目錄服務(wù)。
在這個(gè)架構(gòu)中,可以看到有 3 種不同的設(shè)備使用該微服務(wù),即 POS(銷售點(diǎn))、移動(dòng)設(shè)備/平板電腦和計(jì)算機(jī)。POS 和移動(dòng)設(shè)備/平板電腦都有自己的應(yīng)用程序(在 electron 中開發(fā)),并直接使用微服務(wù),而計(jì)算機(jī)則通過 Web 應(yīng)用程序訪問微服務(wù)(一些專家也將 Web 應(yīng)用程序視為微服務(wù))。
構(gòu)建微服務(wù)
現(xiàn)在,來模擬在最喜歡的電影院預(yù)訂一場(chǎng)電影首映的過程。
首先,想看看電影院目前正在上映哪些電影。以下圖表顯示了通過 REST 進(jìn)行的內(nèi)部通信,通過此 REST 通信,可以使用 API 來獲取目前正在上映的電影。
電影服務(wù)的 API 將具有以下 RAML 規(guī)范:
#%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 } }
如果不了解 RAML,可以查看這個(gè)很好的教程。
API 項(xiàng)目的結(jié)構(gòu)將如下所示:
- api/ # 我們的API
- config/ # 應(yīng)用程序配置
- mock/ # 不是必需的,僅用于數(shù)據(jù)示例
- repository/ # 抽象出數(shù)據(jù)庫
- server/ # 服務(wù)器設(shè)置代碼
- package.json # 依賴項(xiàng)
- index.js # 應(yīng)用程序的主入口
首先要看的部分是 repository。這是對(duì)數(shù)據(jù)庫進(jìn)行查詢的地方。
'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})
可能已經(jīng)注意到,向 repository 的 connect ( connection ) 方法提供了一個(gè) connection
對(duì)象。在這里,使用了 JavaScript 的一個(gè)重要特性“閉包”,repository 對(duì)象返回了一個(gè)閉包,其中的每個(gè)函數(shù)都可以訪問 db
對(duì)象和 collection
對(duì)象, db
對(duì)象保存著數(shù)據(jù)庫連接。在這里,抽象了連接的數(shù)據(jù)庫類型,repository 對(duì)象不知道數(shù)據(jù)庫是什么類型的,對(duì)于這種情況來說,是一個(gè) MongoDB 連接。甚至不需要知道是單個(gè)數(shù)據(jù)庫還是復(fù)制集連接。雖然使用了 MongoDB 語法,但可以通過應(yīng)用 SOLID 原則中的依賴反轉(zhuǎn)原則,將存儲(chǔ)庫功能抽象得更深,將 MongoDB 語法轉(zhuǎn)移到另一個(gè)文件中,并只調(diào)用數(shù)據(jù)庫操作的接口(例如,使用 mongoose 模型)。
有一個(gè) repository/repository.spec.js
文件來測(cè)試這個(gè)模塊,稍后在文章中會(huì)談到測(cè)試。
接下來要看的是 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})
在這里,創(chuàng)建了一個(gè)新的 express 應(yīng)用程序,驗(yàn)證是否提供了 repository 和 server port 對(duì)象,然后為 express 應(yīng)用程序應(yīng)用一些中間件,例如用于日志記錄的 morgan
,用于安全性的 helmet
,以及一個(gè)錯(cuò)誤處理函數(shù),最后導(dǎo)出一個(gè) start 函數(shù)來啟動(dòng)服務(wù)器。
Helmet 包含了整整 11 個(gè)軟件包,它們都用于阻止惡意方破壞或使用應(yīng)用程序來傷害其用戶。
好的,現(xiàn)在既然服務(wù)器使用了電影的 API,繼續(xù)查看 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) }) }
在這里,為API創(chuàng)建了路由,并根據(jù)監(jiān)聽的路由調(diào)用了 repo 函數(shù)。repo 在這里使用了接口技術(shù)方法,在這里使用了著名的“為接口編碼而不是為實(shí)現(xiàn)編碼”,因?yàn)?express 路由不知道是否有一個(gè)數(shù)據(jù)庫對(duì)象、數(shù)據(jù)庫查詢邏輯等,它只調(diào)用處理所有數(shù)據(jù)庫問題的 repo 函數(shù)。
所有文件都有與源代碼相鄰的單元測(cè)試,看看 movies.js
的測(cè)試是如何進(jìn)行的。
可以將測(cè)試看作是對(duì)正在構(gòu)建的應(yīng)用程序的安全保障。不僅會(huì)在本地機(jī)器上運(yùn)行,還會(huì)在 CI 服務(wù)上運(yùn)行,以確保失敗的構(gòu)建不會(huì)被推送到生產(chǎn)系統(tǒng)。
為了編寫單元測(cè)試,必須對(duì)所有依賴項(xiàng)進(jìn)行存根,即為模塊提供虛擬依賴項(xiàng)??纯?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) }) })
/* 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/) }) })
可以看到,為 movies API 存根了依賴項(xiàng),并驗(yàn)證了需要提供一個(gè) server port 和一個(gè) repository 對(duì)象。
繼續(xù)看一下如何創(chuàng)建傳遞給 repository 模塊的 db 連接對(duì)象,現(xiàn)在定義說每個(gè)微服務(wù)都必須有自己的數(shù)據(jù)庫,但是對(duì)于示例,將使用一個(gè) MongoDB 復(fù)制集服務(wù)器,但每個(gè)微服務(wù)都有自己的數(shù)據(jù)庫。
從 NodeJS 連接到 MongoDB 數(shù)據(jù)庫
以下是需要從 NodeJS 連接到 MongoDB 數(shù)據(jù)庫的配置。
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})
這里可能有更好的方法,但基本上可以這樣創(chuàng)建與 MongoDB 的復(fù)制集連接。
傳遞了一個(gè) options 對(duì)象,其中包含 Mongo 連接所需的所有參數(shù),并且傳遞了一個(gè)事件 — 中介者對(duì)象,當(dāng)通過認(rèn)證過程時(shí),它將發(fā)出 db 對(duì)象。
注意 在這里,使用了一個(gè)事件發(fā)射器對(duì)象,因?yàn)槭褂?promise 的方法在某種程度上并沒有在通過認(rèn)證后返回 db 對(duì)象,順序變得空閑。所以這可能是一個(gè)很好的挑戰(zhàn),看看發(fā)生了什么,并嘗試使用 promise 的方法。
現(xiàn)在,既然正在傳遞一個(gè) options 對(duì)象來進(jìn)行參數(shù)設(shè)置,讓我們看看這是從哪里來的,因此要查看的下一個(gè)文件是 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 })
這是配置文件,大部分配置代碼都是硬編碼的,但正如看到的,一些屬性使用環(huán)境變量作為選項(xiàng)。環(huán)境變量被視為最佳實(shí)踐,因?yàn)檫@可以隱藏?cái)?shù)據(jù)庫憑據(jù)、服務(wù)器參數(shù)等。
最后,對(duì)于構(gòu)建電影服務(wù) API 的最后一步是使用 index.js 將所有內(nèi)容組合在一起。
'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')
在這里,組合了所有的電影 API 服務(wù),添加了一些錯(cuò)誤處理,然后加載配置、啟動(dòng)存儲(chǔ)庫,并最后啟動(dòng)服務(wù)器。
因此,到目前為止,已經(jīng)完成了與 API 開發(fā)相關(guān)的所有內(nèi)容。
下面是項(xiàng)目中需要用到的初始化以及運(yùn)行命令:
- npm install # 設(shè)置Node依賴項(xiàng)
- npm test # 使用mocha進(jìn)行單元測(cè)試
- npm start # 啟動(dòng)服務(wù)
- npm run node-debug # 以調(diào)試模式運(yùn)行服務(wù)器
- npm run chrome-debug # 使用chrome調(diào)試Node
- npm run lint # 使用standard進(jìn)行代碼lint
最后,第一個(gè)微服務(wù)已經(jīng)在本地運(yùn)行,并通過執(zhí)行 npm start 命令啟動(dòng)。
現(xiàn)在是時(shí)候?qū)⑵浞湃?Docker 容器中。
首先,需要使用“使用 Docker 部署 MongoDB 復(fù)制集”的文章中的 Docker 環(huán)境,如果沒有,則需要進(jìn)行一些額外的修改步驟,以便為微服務(wù)設(shè)置數(shù)據(jù)庫,以下是一些命令,進(jìn)行測(cè)試電影服務(wù)。
首先創(chuàng)建 Dockerfile,將 NodeJS 微服務(wù)制作成 Docker 容器。
# Node v7作為基本映像以支持ES6 FROM node:7.2.0 # 為新容器創(chuàng)建一個(gè)新用戶,并避免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"]
使用 NodeJS 鏡像作為 Docker 鏡像的基礎(chǔ),然后為鏡像創(chuàng)建一個(gè)用戶以避免非 root 用戶,接下來,將 src 復(fù)制到鏡像中,然后安裝依賴項(xiàng),暴露一個(gè)端口號(hào),并最后實(shí)例化電影服務(wù)。
接下來,需要構(gòu)建 Docker 鏡像,使用以下命令:
$ docker build -t movies-service .
首先看一下構(gòu)建命令。
docker build
告訴引擎要?jiǎng)?chuàng)建一個(gè)新的鏡像。-t movies-service
用標(biāo)記movies-service
標(biāo)記此鏡像。從現(xiàn)在開始,可以根據(jù)標(biāo)記引用此鏡像。- .:使用當(dāng)前目錄來查找
Dockerfile
。
經(jīng)過一些控制臺(tái)輸出后,新鏡像中就有了 NodeJS 應(yīng)用程序,所以現(xiàn)在需要做的就是使用以下命令運(yùn)行鏡像:
$ 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
在上面的命令中,傳遞了一個(gè)環(huán)境變量,它是一個(gè)服務(wù)器數(shù)組,需要連接到 MongoDB 復(fù)制集的服務(wù)器,這只是為了說明,有更好的方法可以做到這一點(diǎn),比如讀取一個(gè)環(huán)境變量文件。
現(xiàn)在,容器已經(jīng)運(yùn)行起來了,獲取 docker-machine
IP地址,以獲取微服務(wù)的 IP 地址,現(xiàn)在準(zhǔn)備對(duì)微服務(wù)進(jìn)行一次集成測(cè)試,另一個(gè)測(cè)試選項(xiàng)可以是JMeter,它是一個(gè)很好的工具,可以模擬HTTP請(qǐng)求。
這是集成測(cè)試,將檢查一個(gè) API 調(diào)用。
/* 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) }) })
總結(jié)
創(chuàng)建了用于查詢電影院正在上映的電影的 movies 服務(wù),使用 RAML 規(guī)范設(shè)計(jì)了 API,然后開始構(gòu)建 API,并進(jìn)行了相應(yīng)的單元測(cè)試,最后,組合了所有內(nèi)容,使微服務(wù)完整,并能夠啟動(dòng) movies 服務(wù)服務(wù)器。
然后,將微服務(wù)放入 Docker 容器中,以進(jìn)行一些集成測(cè)試。
微服務(wù)架構(gòu)可以為大型應(yīng)用程序帶來許多好處,但也需要小心管理和設(shè)計(jì),以處理分布式系統(tǒng)的復(fù)雜性和其他挑戰(zhàn)。使用 Docker 容器可以簡化微服務(wù)的部署和管理,使其更加靈活和可擴(kuò)展。
到此這篇關(guān)于如何構(gòu)建一個(gè) NodeJS 影院微服務(wù)并使用 Docker 部署的文章就介紹到這了,更多相關(guān)Docker 部署NodeJS 影院微服務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Node.js Sequelize如何實(shí)現(xiàn)數(shù)據(jù)庫的讀寫分離
Sequelize是一個(gè)易于使用,支持多SQL方言(dialect)的對(duì)象-關(guān)系映射框架(ORM),這個(gè)庫完全采用JavaScript開發(fā)并且能夠用在Node.JS環(huán)境中。它當(dāng)前支持MySQL, MariaDB, SQLite 和 PostgreSQL 數(shù)據(jù)庫。在Node.js中,使用 Sequelize操作數(shù)據(jù)庫時(shí),同樣支持讀寫分離。2016-10-10node實(shí)現(xiàn)將json轉(zhuǎn)為excel
平時(shí)我們寫代碼處理的數(shù)據(jù)格式一般都是json格式的數(shù)據(jù),但有時(shí)候我們也需要將數(shù)據(jù)轉(zhuǎn)為excel格式進(jìn)行保存或分享,所以下面我們就來學(xué)習(xí)一下如何通過node實(shí)現(xiàn)json轉(zhuǎn)excel吧2024-11-11使用?Node-RED對(duì)?MQTT?數(shù)據(jù)流處理
本文將介紹使用 Node-RED 連接到 MQTT 服務(wù)器,并對(duì) MQTT 數(shù)據(jù)進(jìn)行過濾和處理后再將其發(fā)送至 MQTT 服務(wù)器的完整操作流程。讀者可以快速了解如何使用 Node-RED 對(duì) MQTT 數(shù)據(jù)進(jìn)行簡單的流處理2022-05-05為nuxt項(xiàng)目寫一個(gè)面包屑cli工具實(shí)現(xiàn)自動(dòng)生成頁面與面包屑配置
這篇文章主要介紹了為nuxt項(xiàng)目寫一個(gè)面包屑cli工具實(shí)現(xiàn)自動(dòng)生成頁面與面包屑配置,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09