Most project using popular ORMs like Sequelize, Mongoose, or Typeorm, typically has a models folder. How would go structure it by components?
Good one.
Each component is 100% a stand-alone application with its own models. Shared logic can be saved reused via npm package as illustrated in 1.3.. The major principle should be keeping your DAL as a data-mapper that maps domain entities into DB and NOT owning the canonical application entities. In other words, you have the domain models, simple JS objects that are agnostic to any DB concerns, which are the heart of the system - the DAL is responsible to serialize this model into DB. If you use ORM than that DAL will use its own internal models for the mapping. This is being referred to as the 'data mapper' pattern. What developers usually use is the 'active record' pattern (ORM model is the main entity), there are many blog posts on why data-mapper should be used instead of active record
Code example using Sequelize:
Defining a domain model/entity which is absolutely DB agnostic
const JSONValidator = require("jsonschema").Validator;
const CancelReasonEnum = require('./cancel-reason-enum');
const orderSchema = require('./order-schema.json');
class Order {
constructor() {
}
validate() {
const v = new JSONValidator();
const {errors} = v.validate(this, orderSchema);
return errors;
}
}
module.exports = Order;
The domain service which uses the DAL for any persistence operations:
addRawOrder(newOrder) {
logger.info(`Order service is about to add a raw order ${newOrder}`, this.context);
if (!newOrder.validate().errors > 0) throw new errorHelpers.commonErrors.InvalidInputError(`Invalid Order`);
return new Repository().addOrder(newOrder);
}
And finally, the repository which is an encapsulated object, separated from the domain, that internally is using Sequelize for persistence:
const Order = dbAccess.sequelizeDBReference.define("order", {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
date: { type: Sequelize.DATE, defaultValue: Sequelize.NOW },
order_price: { type: Sequelize.TINYINT },
});
class OrderRepository {
async addOrder(newOrder) {
await Order.create(newOrder, {
include: [PaymentTable],
});
}
Does this make sense? Do you think we should add a new best practice - 'Prefer a data mapper over active record'?
@chanlito The bottom line here is that each component has its own DB folder with all the DB concerns to keep the components autonomous and share nothing. Any further thoughts or can I close this issue?
@i0natan I feel that this way leads to repetitive code, right?
Does this best practice address this concern? @chanlito
Yeah but I don't think private npm is a solution for everyone, you can close this if you want.
You don't have two use a private npm, just put the reusable stuff in a shared/libraries folder outside the comoonents
@i0natan I am beginner developer, so please forgive if this is too trivial. Could you please share how to do this is in Mongoose, similar to the example using sequelize?
@lightbringer2994 Welcome and nothing is trivial here. I don't have mongoose examples ready, also on a travel so won't have the time to create some but maybe the following instructions might help:
Wrap it as a class with public methods - all the operations that you created in step 1', should be exposed via class methods, those methods will receive entities/models which are not related to mongoose - you'll have to map them to the mongoose models, this is why this layer is called the data mapper e.g.
class myDAL{
addNewUser(user){//your mongoose code here, accepting a user object and converting/mapping it to the user model
}
}
Create your domain/business-logic/core layer - create all the entities (i.e. models) again, this time without any reference to some library like mongoose rather plain JS objects. Write your custom code and features, whenever you need some db services, approach the DAL from object 2'
The idea here to isolate your app core (domain) from step 3', from the persistence layer that saves it into db at step 2
@i0natan Is there a good open source project I can look at which implements this?
@lightbringer2994 I'm not familiar with one, I suggest that you read about the data mapper pattern (vs active record) and try to implement yourself. Feel free to ask here and share snippets
/remind me in a week
@i0natan set a reminder for Oct 14th 2018
Closing as it seems to address most concerns, sorry I couldn't help further with the Mongoose thing
:wave: @i0natan,
Most helpful comment
Good one.
Each component is 100% a stand-alone application with its own models. Shared logic can be saved reused via npm package as illustrated in 1.3.. The major principle should be keeping your DAL as a data-mapper that maps domain entities into DB and NOT owning the canonical application entities. In other words, you have the domain models, simple JS objects that are agnostic to any DB concerns, which are the heart of the system - the DAL is responsible to serialize this model into DB. If you use ORM than that DAL will use its own internal models for the mapping. This is being referred to as the 'data mapper' pattern. What developers usually use is the 'active record' pattern (ORM model is the main entity), there are many blog posts on why data-mapper should be used instead of active record
Code example using Sequelize:
Defining a domain model/entity which is absolutely DB agnostic
The domain service which uses the DAL for any persistence operations:
And finally, the repository which is an encapsulated object, separated from the domain, that internally is using Sequelize for persistence:
Does this make sense? Do you think we should add a new best practice - 'Prefer a data mapper over active record'?