-
Notifications
You must be signed in to change notification settings - Fork 123
Description
I created a pull #338 that fix a problem in connect()
method of adapter mongoose
Today, I've created a new project with moleculer-db-adapter-mongoose and I still got error message when connect to mongodb
problem
The code below can be used to reproduce the problem
const { ServiceBroker } = require('moleculer');
const { Schema } = require('mongoose')
const DbService = require('moleculer-db')
const MongooseDbAdapter = require('moleculer-db-adapter-mongoose')
const broker = new ServiceBroker({
logger: {
type: 'Console',
options: {
formatter: 'short',
}
}
})
broker.createService({
name: 'foo',
schema: new Schema({}),
modelName: 'foo',
mixins: [DbService],
adapter: new MongooseDbAdapter("mongodb://127.0.0.1/test"),
})
broker.start().then(() => {
console.log('Broker started')
return broker.stop()
}).catch(() => {
console.log('Broker failed to start')
console.error(err)
})
Here is screenshot
You can see that the foo
service was not make connection to db server but success at retry
For more detail, here what happen
- First because I used
schema
, so the line below resolve with theconn
frommongoose.createConnection
moleculer-db/packages/moleculer-db-adapter-mongoose/src/index.js
Lines 80 to 85 in 072f1b6
} else if (this.schema) { conn = new Promise(resolve =>{ const c = mongoose.createConnection(this.uri, this.opts); this.model = c.model(this.modelName, this.schema); resolve(c); }); - Next, after connect successfully, the promise should be resolved with
conn.then()
, but instead checkconn
is connected or not, the code usemongoose.connection
that always point tomongoose.connections[0]
(default mongoose connection, but with any call ofmongoose.createConnection
will result to append newconnection
tomongoose.connections
). - The
connect()
was thrown an error and catched bymoleculer-db
that asked adapter to reconnect.
moleculer-db/packages/moleculer-db/src/index.js
Lines 1107 to 1110 in 072f1b6
setTimeout(() => { this.logger.warn("Reconnecting..."); connecting(); }, 1000); - A stroke of luck has happened here
At line 83, when adapter gotschema
field, adapter create amodel
this.model = c.model(this.modelName, this.schema);
When adapter doconnect()
again, becausemodel
field is set, so it jump to below code that usemongoose.connection
to connect
moleculer-db/packages/moleculer-db-adapter-mongoose/src/index.js
Lines 70 to 80 in 072f1b6
if (this.model) { /* istanbul ignore next */ if (mongoose.connection.readyState == 1) { this.db = mongoose.connection; return Promise.resolve(); } else if (mongoose.connection.readyState == 2) { conn = mongoose.connection.asPromise(); } else { conn = mongoose.connect(this.uri, this.opts); } } else if (this.schema) {
So at this time, connection was successfully
But for every services use schema
will share same mongoose connection
updated code
const { ServiceBroker } = require('moleculer');
const { Schema } = require('mongoose')
const DbService = require('moleculer-db')
const MongooseDbAdapter = require('moleculer-db-adapter-mongoose')
const broker = new ServiceBroker({
logger: {
type: 'Console',
options: {
formatter: 'short',
}
}
})
const foo = broker.createService({
name: 'foo',
schema: new Schema({}),
modelName: 'foo',
mixins: [DbService],
adapter: new MongooseDbAdapter("mongodb://127.0.0.1/test"),
})
const bar = broker.createService({
name: 'bar',
schema: new Schema({}),
modelName: 'bar',
mixins: [DbService],
adapter: new MongooseDbAdapter("mongodb://127.0.0.1/test"),
})
broker.start().then(() => {
console.log('Broker started')
console.log(`Connection of foo and bar is ${foo.adapter.conn === bar.adapter.conn ? 'same' : 'different'}`)
return broker.stop()
}).catch(() => {
console.log('Broker failed to start')
console.error(err)
})
screenshot
fix
-
I copied use this.model.db as NativeConnection instance #338
connect
anddisconnect
methods, create a patch file using bypatch-package
module that fix this problem
patches.zip
Unzip this file to root path then runnpx patch-package
-
or copy below lines to overwrite connect, disconnect method in
moleculer-db-adapter-mongoose/src/index.js
moleculer-db/packages/moleculer-db-adapter-mongoose/src/index.js
Lines 59 to 151 in 81f4f3f
/** * Connect to database * * @returns {Promise} * * @memberof MongooseDbAdapter */ connect() { return new Promise((resolve) => { if (this.model) { // model field exists in service schema, should check if model has been connected if (this.model.db) { // if this.model.db is existed, adapter had connected before, just return this.model.db // Model.prototype.db // Connection the model uses. // https://mongoosejs.com/docs/api/model.html#model_Model-db resolve(this.model.db); return; } /* istanbul ignore next */ if ( mongoose.connection.readyState === mongoose.connection.states.connected || mongoose.connection.readyState === mongoose.connection.states.connecting ) { // if mongoose is connecting, will handle below resolve(mongoose.connection); } else { // everything else cases mean we not yet do connect before, make it const conn = mongoose.createConnection(this.uri, this.opts); resolve(conn); } } else if (this.schema) { const conn = mongoose.createConnection(this.uri, this.opts); this.model = conn.model(this.modelName, this.schema); resolve(conn); } }).then(conn => { this.conn = conn; if (this.conn.db != null) { return this.conn.db; } else if (this.conn.readyState === mongoose.connection.states.connecting) { return new Promise((resolve, reject) => { this.conn.once("error", reject); this.conn.once("connected", () => { this.conn.removeListener("error", reject); resolve(this.conn.db); }); }); } throw new MoleculerError("MongoDB connection failed to get DB object"); }).then(db => { this.db = db; this.service.logger.info("MongoDB adapter has connected successfully."); // do not use this.db.on() because of next line // DeprecationWarning: Listening to events on the Db class has been deprecated and will be removed in the next major version. /* istanbul ignore next */ this.conn.on("disconnected", () => this.service.logger.warn("Mongoose adapter has disconnected.")); this.conn.on("error", err => this.service.logger.error("MongoDB error.", err)); this.conn.on("reconnect", () => this.service.logger.info("Mongoose adapter has reconnected.")); }); } /** * Disconnect from database * * @returns {Promise} * * @memberof MongooseDbAdapter */ disconnect() { return new Promise(resolve => { if (this.conn == null) { // model was created and mongoose maybe connected before call .connect() mongoose.connection.close(resolve); } else if (this.conn.readyState === mongoose.connection.states.connecting) { // disconnect() was called before connect() success // try to disconnect at nextTick setTimeout(() => resolve(this.disconnect()), 0); } else if (this.conn.close) { this.conn.close(resolve); } else if (this.db != null && this.db.close) { this.db.close(resolve); } else { // for unknown cases mongoose.connection.close(resolve); } }); }
need support
@icebob if you think this fix is correct, can you (or someone else) can help me to write tests? I not good to write test and dont have enough time at this moment