Я пытаюсь создать очень маленький проект, чтобы попрактиковаться в продолжении. В основном у меня есть пользователи, продукты, категории и покупки.
- Пользователи могут купить не более 3 товаров
- Пользователь не может покупать один и тот же товар более одного раза.
- Каждый продукт относится к категории
И я хочу поддерживать с помощью кода три совокупных счетчика:
- затраченные деньги и PurchaseCount (для пользователя)
- productCount (для категории)
Мне известны такие варианты, как простые запросы, триггеры, представления и т. д. для агрегатных операций. Я просто хочу попрактиковаться в продолжении.
Итак, я сделал это, и, насколько я понимаю, это работает. Мой вопрос касается методов: createProduct и registerProductPurchase. Склонны ли они к гонкам? Я так не думаю, потому что я использую номер версии и транзакции. Внутри createProduct я проверяю номер версии категории, а внутри registerProductPurchase — номер версии пользователя. Оба они загружаются в начале транзакции.
createProduct сопоставляется с этим sql:
Executing (9136e456-63f0-4753-98eb-a28099b0d881): START TRANSACTION;
Executing (9136e456-63f0-4753-98eb-a28099b0d881): SELECT "id", "name", "productCount", "version" FROM "categories" AS "ProductCategory" WHERE "ProductCategory"."id" = 2;
Executing (9136e456-63f0-4753-98eb-a28099b0d881): INSERT INTO "products" ("id","name","price","categoryId") VALUES (DEFAULT,$1,$2,$3) RETURNING "id","name","price","categoryId";
Executing (default): UPDATE "categories" SET "productCount"=$1,"version"=$2 WHERE "id" = $3 AND "version" = $4
Executing (9136e456-63f0-4753-98eb-a28099b0d881): COMMIT;
и registerProductPurchase сопоставляется с этим:
Executing (54fa334a-fe94-4686-88ee-6c6ed6ceddbd): START TRANSACTION;
Executing (54fa334a-fe94-4686-88ee-6c6ed6ceddbd): SELECT "id", "name", "price", "categoryId" FROM "products" AS "Product" WHERE "Product"."id" = 2;
Executing (54fa334a-fe94-4686-88ee-6c6ed6ceddbd): SELECT "id", "email", "spentMoney", "purchaseCount", "version" FROM "users" AS "User" WHERE "User"."id" = 2;
Executing (54fa334a-fe94-4686-88ee-6c6ed6ceddbd): SELECT "purchaseDate", "userId", "productId" FROM "purchases" AS "Purchase" WHERE "Purchase"."productId" = 2 AND "Purchase"."userId" IN (2);
Executing (54fa334a-fe94-4686-88ee-6c6ed6ceddbd): INSERT INTO "purchases" ("userId","productId") VALUES (2,2) RETURNING "purchaseDate","userId","productId";
Executing (54fa334a-fe94-4686-88ee-6c6ed6ceddbd): UPDATE "users" SET "spentMoney"=$1,"purchaseCount"=$2,"version"=$3 WHERE "id" = $4 AND "version" = $5
Executing (54fa334a-fe94-4686-88ee-6c6ed6ceddbd): COMMIT;
что мне кажется хорошо.
Это код (соответствующая часть. Я также использую экспресс) Если в моем коде существуют условия гонки, как я могу их протестировать?
var sequelize = require("sequelize")
const db = new sequelize.Sequelize('postgres://root:root@localhost:5432/catalog')
var Category = db.define('ProductCategory', {
id: {
type: sequelize.DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: sequelize.DataTypes.STRING,
allowNull: false
},
productCount: {
type: sequelize.DataTypes.INTEGER,
allowNull: true,
defaultValue: 0
}
}, {
version: true,
timestamps: false,
tableName: 'categories',
modelName: 'ProductCategory'
});
var Product = db.define('Product', {
id: {
type: sequelize.DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: sequelize.DataTypes.STRING,
allowNull: false
},
price: {
type: sequelize.DataTypes.DOUBLE,
allowNull: false
}
}, {
timestamps: false,
tableName: 'products',
modelName: 'Product'
});
var User = db.define('User', {
id: {
type: sequelize.DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
email: {
type: sequelize.DataTypes.STRING,
allowNull: false,
unique: true
},
spentMoney: {
type: sequelize.DataTypes.DOUBLE,
allowNull: false,
defaultValue: 0
},
purchaseCount: {
type: sequelize.DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
}
}, {
version: true,
timestamps: false,
tableName: 'users',
modelName: 'User'
});
var Purchase = db.define('Purchase', {
purchaseDate: {
type: sequelize.DataTypes.DATEONLY,
allowNull: true
}
}, {
version: false,
timestamps: false,
tableName: 'purchases',
modelName: 'Purchase'
});
Category.hasMany(Product, { foreignKey: "categoryId" })
Product.belongsTo(Category, { foreignKey: "categoryId" })
Product.belongsToMany(User, { through: Purchase, foreignKey: "userId" })
User.belongsToMany(Product, { through: Purchase, foreignKey: "productId" })
Category.prototype.incrementProductCount = async function(options) {
this.productCount += 1
return await this.save(options)
}
User.prototype.buy = async function(product, options) {
if (this.purchaseCount === 3)
throw new Error("User can't buy more than 3 products")
try {
await this.addProduct(product, options)
} catch (err) {
if (err instanceof UniqueConstraintError) {
throw new Error("User already bought product " + p.id)
}
throw err
}
this.spentMoney += product.price
this.purchaseCount += 1
await this.save(options)
}
async function createProduct(categoryId, productInfo) {
let t
try {
t = await db.transaction()
let category = await Category.findByPk(categoryId, { transaction: t })
let product = await Product
.build({...productInfo, categoryId: categoryId })
.save({ transaction: t })
await category.incrementProductCount({ transaction: t })
await t.commit()
return product
} catch (error) {
if (t)
await t.rollback()
throw err
}
}
async function registerProductPurchase(userId, productId) {
let t
try {
t = await db.transaction()
let product = await Product.findByPk(productId, { transaction: t })
let user = await User.findByPk(userId, { transaction: t })
await user.buy(product, { transaction: t })
await t.commit()
} catch (error) {
if (t)
await t.rollback()
throw error
}
}
incrementProductCount
. - person Anatoly   schedule 19.01.2021incrementProductCount
:function()
иthis.save()
- person Anatoly   schedule 19.01.2021