egg sequelize 實(shí)踐

背景

和同事一起有一個(gè)公司內(nèi)部平臺(tái)的項(xiàng)目愁憔,平臺(tái)需要對(duì)于用戶上傳的圖片,視頻等資源進(jìn)行管理和存儲(chǔ)。

在項(xiàng)目一期而涉,由于申請(qǐng)DB資源的流程比較復(fù)雜,所以我們僅僅將用戶上傳的內(nèi)容記錄存儲(chǔ)在了localStorage中联予,當(dāng)然這是很不安全的啼县,非常容易丟失材原,所以在二期的時(shí)候,我們開(kāi)始了接入DB的工作季眷。

技術(shù)選型

下面的整篇文章都會(huì)和這部分描述的技術(shù)棧相關(guān)余蟹,當(dāng)然,sequelize在實(shí)際業(yè)務(wù)場(chǎng)景的使用相關(guān)內(nèi)容子刮,其實(shí)本身和技術(shù)棧的關(guān)系并不是非常大威酒,如果你在sequelize的使用過(guò)程中遇到了問(wèn)題,這里或許會(huì)有解答~

mysql

采用mysql作為數(shù)據(jù)存儲(chǔ)引擎话告,其實(shí)本身也是無(wú)奈之舉兼搏,因?yàn)镈BA告訴我們目前MongoDB的資源不足,并且對(duì)于這種結(jié)構(gòu)化數(shù)據(jù)的存儲(chǔ)沙郭,mysql對(duì)于未來(lái)將平臺(tái)擴(kuò)展到整個(gè)公司使用佛呻,甚至對(duì)外開(kāi)放,則是必須的病线。

egg

作為一款比較成熟的node.js開(kāi)發(fā)框架吓著,公司toC端很多業(yè)務(wù)架構(gòu)都使用了基于egg的ReactSSR,egg對(duì)于sequelize的支持還是比較好的送挑,提供了專門(mén)的插件來(lái)輔助使用sequelize進(jìn)行DB接入绑莺。

sequelize

這才是本文的核心,sequelize目前可以說(shuō)是目前最為成熟的node.js ORM框架了惕耕。CRUD操作不可能完全使用SQL語(yǔ)句進(jìn)行纺裁,這樣很容易出現(xiàn)各種SQL漏洞,一個(gè)成熟的ORM框架可以幫我們避免掉這些風(fēng)險(xiǎn)司澎,并且將CRUD操作封裝成對(duì)象函數(shù)方法之后欺缘,操作起來(lái)也更加方便,但是這樣會(huì)提升一定的開(kāi)發(fā)學(xué)習(xí)成本挤安。

開(kāi)始

整體的方案都出來(lái)了谚殊,剩下的就是爬坑。由于以前還是做過(guò)一些和數(shù)據(jù)庫(kù)有關(guān)的工作蛤铜,SQL語(yǔ)句和部分ORM的實(shí)現(xiàn)還有過(guò)一點(diǎn)接觸嫩絮,但是。围肥。剿干。我依然在坑里栽了很久,長(zhǎng)成了參天大樹(shù)穆刻,說(shuō)起參怨愤,我就想到西游記里面的人參果。蛹批。撰洗。文體兩開(kāi)花篮愉。

數(shù)據(jù)模型設(shè)計(jì)

接入DB,首先需要考慮就是如何設(shè)計(jì)數(shù)據(jù)模型差导。當(dāng)然這對(duì)于前端來(lái)說(shuō)试躏,還是有一些難度的。于是請(qǐng)教了最近在合作的后端大哥设褐。

關(guān)聯(lián)

一般來(lái)說(shuō)颠蕴,一個(gè)系統(tǒng)都需要有特定的用戶進(jìn)行登錄。又需要對(duì)于用戶進(jìn)行分組助析,一個(gè)用戶可以加入多個(gè)組犀被,一個(gè)組可以有多個(gè)用戶。n:m的關(guān)聯(lián)關(guān)系需要在設(shè)計(jì)數(shù)據(jù)模型的時(shí)候就體現(xiàn)出來(lái)外冀。

生產(chǎn)環(huán)境中寡键,n:m的關(guān)聯(lián)是需要中間表來(lái)輔助的,來(lái)存儲(chǔ)例如用戶和用戶組之間的映射關(guān)系雪隧。

外鍵還是邏輯

關(guān)聯(lián)可以通過(guò)設(shè)置foreignKey來(lái)進(jìn)行關(guān)聯(lián)西轩,但是被后端大哥批斗了,為了保證數(shù)據(jù)庫(kù)的性能脑沿,一般很少采用外鍵關(guān)聯(lián)兩個(gè)數(shù)據(jù)模型藕畔,而是采用邏輯關(guān)聯(lián),通過(guò)開(kāi)發(fā)者人工保證寫(xiě)入和刪除的順序庄拇。

索引

索引是必不可少的注服,我們?cè)谔峤籇BA工單的時(shí)候,必須要建立索引措近,尤其是有些數(shù)據(jù)表會(huì)存儲(chǔ)非常多的記錄祠汇,這時(shí)對(duì)于主鍵建立索引,可以大幅提高查找的效率熄诡。

根據(jù)上面的三個(gè)重點(diǎn),完成了我的數(shù)據(jù)表設(shè)計(jì)诗力。這里可以給出一個(gè)簡(jiǎn)單的栗子數(shù)據(jù)庫(kù)模型凰浮。后面的實(shí)現(xiàn)也是根據(jù)這個(gè)栗子進(jìn)行的。

image

<figcaption></figcaption>

CREATE TABLE `group` (
  `id` INTEGER PRIMARY KEY AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL,
  `db_create_time` DATETIME,
  `db_update_time` DATETIME
);

CREATE TABLE `user` (
  `id` INTEGER PRIMARY KEY AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL,
  `db_create_time` DATETIME,
  `db_update_time` DATETIME
);

CREATE TABLE `group_users` (
  `user` INTEGER NOT NULL,
  `group` INTEGER NOT NULL,
  CONSTRAINT `pk_group_users` PRIMARY KEY (`user`, `group`)
);

CREATE INDEX `idx_group_users` ON `group_users` (`group`);
復(fù)制代碼

Egg-Sequelize實(shí)踐

egg框架本身提供了很多即插即用的plugin苇本,官方最基本的插件集中就有egg-sequelize插件袜茧。插件配置起來(lái)非常簡(jiǎn)單。

下面可能不會(huì)說(shuō)的非常詳細(xì)瓣窄,我主要講一講自己在進(jìn)行開(kāi)發(fā)時(shí)候遇到的各種坑笛厦。

項(xiàng)目結(jié)構(gòu)

|-- app                                     // node服務(wù)端相關(guān)代碼
    |-- controller
        |-- api                             // node端接口controller
            |-- group.js                    // 組相關(guān)controller
            |-- user.js                     // 用戶相關(guān)controller
    |-- extend
        |-- helper.js                       // helper擴(kuò)展
    |-- middleware
    |-- model                               // sequelize數(shù)據(jù)模型
        |-- user.js
        |-- group.js
        |-- group_user.js
    |-- service                             // 可復(fù)用的數(shù)據(jù)處理及查詢方法
    |-- utils                               // service中拿不到helper,部分utils放在這里
    |-- router.js                           // 路由
|-- build                                   // 構(gòu)建代碼
|-- client                                  // 客戶端相關(guān)代碼
|-- config                                  // 配置文件
復(fù)制代碼

啟動(dòng)插件

// config.local.js

module.exports = {
  sequelize: {
    // 數(shù)據(jù)庫(kù)類型
    dialect: 'mysql',
    // 數(shù)據(jù)庫(kù)名
    database: 'swiss',
    // 數(shù)據(jù)庫(kù)IP和端口
    host: '127.0.0.1',
    port: '3306',
    // 數(shù)據(jù)庫(kù)連接的用戶和密碼
    username: 'root',
    password: '123',
    // 是否自動(dòng)進(jìn)行下劃線轉(zhuǎn)換(這里是因?yàn)镈B默認(rèn)的命名規(guī)則是下劃線方式俺夕,而我們使用的大多數(shù)是駝峰方式)
    underscored: true,
    // 時(shí)區(qū)裳凸,sequelize有很多自動(dòng)時(shí)間的方法贱鄙,都是和時(shí)區(qū)相關(guān)的,記得設(shè)置成東8區(qū)(+08:00)
    timezone: '+08:00',
  },
}
復(fù)制代碼

各種配置項(xiàng)一目了然姨谷,記得要設(shè)置好timezone逗宁,否則你所有默認(rèn)為當(dāng)前時(shí)間的值都會(huì)出錯(cuò)。underscored表示自動(dòng)將駝峰表示法轉(zhuǎn)換為mysql的下劃線表示法(當(dāng)然后面會(huì)說(shuō)到梦湘,他的轉(zhuǎn)換機(jī)制有些時(shí)候讓我感覺(jué)費(fèi)解瞎颗,希望了解的大佬們可以幫我解釋一下~)。

直接將啟動(dòng)項(xiàng)配置寫(xiě)死在配置文件里面不是不可以捌议,但是如果需要和同事一起合作開(kāi)發(fā)的話哼拔,這樣寫(xiě)死可能不夠靈活“曷可以將某些配置項(xiàng)提取出來(lái)倦逐,通過(guò)命令行傳入?yún)?shù),來(lái)進(jìn)行開(kāi)發(fā)環(huán)境的動(dòng)態(tài)配置弄捕。

~ npm run dev -- --u=root --p=123

// config.local.js

let DB_USER = 'root';
let DB_PASSWORD = '123';

const ARGV_2 = JSON.parse(process.argv[2] || {});

DB_USER = (ARGV_2 && ARGV_2.u) || 'root';
DB_PASSWORD = (ARGV_2 && ARGV_2.p) || '123';

module.exports = {
    sequelize: {
        // ....
        username: `${DB_USER}`,
        password: `${DB_PASSWORD}`,
        // ....
    }
};
復(fù)制代碼

model

egg-sequelize會(huì)自動(dòng)將sequelize實(shí)例掛載到app.model上面僻孝,然后靜態(tài)方法和屬性則會(huì)直接被綁定到app上,通過(guò)app.Sequelize進(jìn)行獲取守谓。

model層作為MVC的最底層穿铆,需要注意到數(shù)據(jù)模型的puremodel文件也應(yīng)該是純凈的斋荞,這個(gè)文件里面應(yīng)該是和數(shù)據(jù)庫(kù)中的表一一對(duì)應(yīng)荞雏,一個(gè)model文件對(duì)應(yīng)一個(gè)DB中的表,這個(gè)文件中不應(yīng)該包含任何和邏輯相關(guān)的代碼平酿,應(yīng)該完全是數(shù)據(jù)模型的定義凤优。

// app/model/user.js

module.exports = app => {
    // egg-sequelize插件會(huì)將Sequelize類綁定到app上線,從里面可以取到各種靜態(tài)類型
    const { TEXT, INTEGER, NOW } = app.Sequelize;

    const User = app.model.define(
        'user',
        {
            name: TEXT,
            createAt: {
                type: DATE,
                // 可以重寫(xiě)某個(gè)字段的字段名
                field: 'db_create_time',
                allowNull: false,
                defaultValue: NOW,
            },
            updateAt: {
                type: DATE,
                field: 'db_update_time',
                allowNull: false,
                defaultValue: NOW,
            },
        },
        {
            timestamps: false,
            freezeTableName: true,
            tableName: 'users',
            underscored: true,
        }
    );

    // 定義關(guān)聯(lián)關(guān)系
    User.associate = () => {
        // 定義多對(duì)多關(guān)聯(lián)
        User.belongsToMany(app.model.Groups, {
            // 中間表的model
            through: app.model.groupUser,
            // 進(jìn)行關(guān)聯(lián)查詢時(shí)蜈彼,關(guān)聯(lián)表查出來(lái)的數(shù)據(jù)模型的alias
            as: 'project',
            // 是否采用外鍵進(jìn)行物理關(guān)聯(lián)
            constraints: false,
        });
        // 這里如果一個(gè)模型和多個(gè)模型都有關(guān)聯(lián)關(guān)系的話筑辨,關(guān)聯(lián)關(guān)系需要統(tǒng)一定義在這里
    };

    return User;
};

復(fù)制代碼

上面的代碼有非常多需要注意的地方,我們通過(guò)這個(gè)文件定義了一個(gè)數(shù)據(jù)模型幸逆,這個(gè)模型可以映射到數(shù)據(jù)庫(kù)中的某一個(gè)表棍辕,這里就是映射到了users表,用來(lái)存儲(chǔ)用戶信息还绘。

  • 在默認(rèn)情況下楚昭,id字段會(huì)被設(shè)置為主鍵,并且是AUTO_INCREMENT的拍顷,不需要我們自己聲明抚太;
  • timestamps字段可以表示是否采用默認(rèn)的createAtupdateAt字段,我們通過(guò)field字段重寫(xiě)了這兩個(gè)字段的字段名;
  • associate字段可以用來(lái)設(shè)置數(shù)據(jù)模型的關(guān)聯(lián)關(guān)系尿贫,如果一個(gè)數(shù)據(jù)模型關(guān)聯(lián)了多個(gè)數(shù)據(jù)模型电媳,那么這個(gè)方法里面也可以定義多個(gè)關(guān)系;
  • belongsToMany表示n:m的關(guān)系映射帅霜,這個(gè)在官方文檔中描述的非常清楚了匆背;
  • as可以為這個(gè)映射設(shè)置別名,這樣在進(jìn)行查詢的時(shí)候身冀,得到的結(jié)果就是以別名來(lái)標(biāo)識(shí)的钝尸;
  • constraints:這個(gè)屬性非常重要,可以用來(lái)表示這個(gè)關(guān)聯(lián)關(guān)系是否采用外鍵關(guān)聯(lián)搂根。在大多數(shù)情況下我們是不需要通過(guò)外鍵來(lái)進(jìn)行數(shù)據(jù)表的物理關(guān)聯(lián)的珍促,直接通過(guò)邏輯進(jìn)行關(guān)聯(lián)即可;
  • through:這個(gè)屬性表示關(guān)聯(lián)表的數(shù)據(jù)模型剩愧,也就是保存關(guān)聯(lián)關(guān)系的數(shù)據(jù)庫(kù)表的模型猪叙。

上面的這些屬性,在開(kāi)發(fā)過(guò)程中多多少少都消耗了我一些時(shí)間-1s仁卷,模型的設(shè)置和數(shù)據(jù)庫(kù)表之間的關(guān)系非常緊密穴翩,一定要保證你的數(shù)據(jù)模型和數(shù)據(jù)表之間沒(méi)有歧義。

同樣地锦积,我們可以定義到關(guān)聯(lián)表和中間表的模型:

// app/model/group.js

module.exports = app => {
    const { TEXT, INTEGER, NOW } = app.Sequelize;

    const Group = app.model.define(
        'group',
        {
            name: TEXT,
            createAt: {
                type: DATE,
                field: 'db_create_time',
                allowNull: false,
                defaultValue: NOW,
            },
            updateAt: {
                type: DATE,
                field: 'db_update_time',
                allowNull: false,
                defaultValue: NOW,
            },
        },
        {
            timestamps: false,
            freezeTableName: true,
            tableName: 'groups',
            underscored: true,
        }
    );

    // 定義關(guān)聯(lián)關(guān)系
    Group.associate = () => {
        Group.belongsToMany(app.model.User, {
            through: app.model.groupUser,
            as: 'partner',
            constraints: false,
        });
    };

    return Group;
};

// app/model/group_user.js
// 中間表不需要定義關(guān)聯(lián)關(guān)系

module.exports = app => {
    const { INTEGER } = app.Sequelize;

    const GroupUser = app.model.define(
        'group_user',
        {
            user_id: INTEGER,
            group_id: INTEGER,
        },
        {
            timestamps: false,
            freezeTableName: true,
            tableName: 'group_user',
            underscored: true,
        }
    );

    return GroupUser;
};
復(fù)制代碼

controller

在egg中芒帕,controller模塊的作用類似于MVC模式中的控制器,進(jìn)行從modelview的轉(zhuǎn)換,而在提供接口的時(shí)候,controller負(fù)責(zé)的是提供從modelapi的轉(zhuǎn)換豌研,經(jīng)過(guò)model從數(shù)據(jù)庫(kù)中查詢出來(lái)的結(jié)果,將在controller里面進(jìn)行包裝带膀,然后返回給接口的調(diào)用者。

在進(jìn)行數(shù)據(jù)訪問(wèn)的時(shí)候橙垢,很多的接口請(qǐng)求都可以拆分為幾個(gè)類似的CRUD操作垛叨,比如:

  • 我想查一個(gè)用戶的注冊(cè)時(shí)間;
  • 我想查一個(gè)用戶的用戶名柜某; 這樣類似的操作都可以通過(guò)一樣的數(shù)據(jù)庫(kù)操作拿到嗽元,然后再進(jìn)行單獨(dú)處理,這些可復(fù)用的邏輯莺琳,根據(jù)egg的建議,都可以寫(xiě)到service里面载慈。而controller只負(fù)責(zé)請(qǐng)求的響應(yīng)處理惭等。

當(dāng)一個(gè)接口請(qǐng)求跨過(guò)了middleware的處理,經(jīng)過(guò)了router的分發(fā)之后:

// app/router.js

module.exports = app => {
    app.get('/api/user/get', app.controller.api.user.get);

    app.post('/api/group/set', app.controller.api.group.set);
}
復(fù)制代碼

會(huì)被轉(zhuǎn)發(fā)到對(duì)應(yīng)的controller進(jìn)行處理办铡。

// app/controller/user.js

module.exports = class UserController extends Controller {
    async get = () => {
        const { uuid } = this.ctx.session;

        if (!uuid) {
            ctx.body = {
                code: 401,
                message: 'unauthorized',
            };
            return;
        }

        const userInfo = await this.ctx.service.user.getUserById({ id: uuid });

        if (userInfo) {
            ctx.body = {
                code: 200,
                message: 'success',
                data: userInfo
            }
        } else {
            ctx.body = {
                code: 500,
                message: 'error',
            }
        }
    }
}
復(fù)制代碼

service

egg官方文檔對(duì)于service的描述是這樣的:

簡(jiǎn)單來(lái)說(shuō)辞做,Service 就是在復(fù)雜業(yè)務(wù)場(chǎng)景下用于做業(yè)務(wù)邏輯封裝的一個(gè)抽象層琳要,提供這個(gè)抽象有以下幾個(gè)好處:

  • 保持 Controller 中的邏輯更加簡(jiǎn)潔。
  • 保持業(yè)務(wù)邏輯的獨(dú)立性秤茅,抽象出來(lái)的 Service 可以被多個(gè) Controller 重復(fù)調(diào)用稚补。
  • 將邏輯和展現(xiàn)分離,更容易編寫(xiě)測(cè)試用例框喳。

也就是controller中要盡量保持clean课幕,然后,可以復(fù)用的業(yè)務(wù)邏輯被統(tǒng)一抽出來(lái)五垮,放到service中乍惊,被多個(gè)controller進(jìn)行復(fù)用。

我們將CRUD操作放仗,全部提取到service中润绎,封裝成一個(gè)個(gè)通用的CRUD方法,來(lái)提供給其他service進(jìn)行嵌套的時(shí)候調(diào)用诞挨,或者提供給controller進(jìn)行業(yè)務(wù)邏輯調(diào)用莉撇。

比如:讀取用戶信息的過(guò)程:

// app/service/user.js

module.exports = class UserService extends Service {
    // 通過(guò)id獲取用戶信息
    async getUserById = ({
        id,
    }) => {
        const { ctx } = this;

        let userInfo = {};
        try {
            userInfo = await ctx.model.User.findAll({
                where: {
                    id,
                },
                // 查詢操作的時(shí)候,加入這個(gè)參數(shù)可以直接拿到對(duì)象類型的查詢結(jié)果惶傻,否則還需要通過(guò)方法調(diào)用解析
                raw: true,
            });
        } catch (err) {
            ctx.logger.error(err);
        }

        return userInfo;
    }
}
復(fù)制代碼

sequelize事務(wù)

之前有說(shuō)到棍郎,在建立模型的時(shí)候,我們建立了UserGroup之間的關(guān)聯(lián)關(guān)系达罗,并且通過(guò)了一個(gè)關(guān)聯(lián)表進(jìn)行兩者之間的關(guān)聯(lián)坝撑。

由于我們沒(méi)有建立兩者之間的外鍵關(guān)聯(lián),所以在寫(xiě)入的時(shí)候粮揉,我們要進(jìn)行邏輯的關(guān)聯(lián)寫(xiě)入巡李。

如果我們需要新建一個(gè)用戶,并且為這個(gè)用戶新建一個(gè)默認(rèn)的group扶认,由于組和用戶有著多對(duì)多的關(guān)系侨拦,所以這里我們采用belongsToMany來(lái)建立關(guān)系。一個(gè)用戶可以屬于多個(gè)組辐宾,并且一組也可以包含多個(gè)用戶狱从。

在建立的時(shí)候,需要按照一定的順序叠纹,寫(xiě)入三張表季研,一旦某個(gè)寫(xiě)入操作失敗之后,需要對(duì)于之前的寫(xiě)入操作進(jìn)行回滾誉察,防止DB中產(chǎn)生垃圾數(shù)據(jù)与涡。這里需要用到事務(wù)機(jī)制進(jìn)行寫(xiě)入控制,并且人工保證寫(xiě)入順序。

// app/service/user.js

module.exports = class UserService extends Service {
    async setUser = ({
        name,
    }) => {
        const { ctx } = this;
        let transaction;

        try {
            // 這里需要注意驼卖,egg-sequelize會(huì)將sequelize實(shí)例作為app.model對(duì)象
            transaction = await ctx.model.transaction();

            // 創(chuàng)建用戶
            const user = await ctx.model.User.create({
                name,
            }, {
                transaction,
            });

            // 創(chuàng)建默認(rèn)組
            const group = await ctx.model.Group.create({
                name: 'default',
            }, {
                transaction,
            });

            const userId = user && user.getDataValue('id');
            const groupId = group && group.getDataValue('id');

            if (!userId || !groupId) {
                throw new Error('創(chuàng)建用戶失敗');
            }

            // 創(chuàng)建用戶和組之間的關(guān)聯(lián)
            const associate = await ctx.mode.GroupUser.create({
                user_id: userId,
                group_id: groupId,
            }, {
                transaction,
            });

            await transaction.commit();

            return userId;
        } catch (err) {
            ctx.logger.error(err);
            await transaction.rollback();
        }
    }
}
復(fù)制代碼

通過(guò)sequelize提供的事務(wù)功能氨肌,可以將串聯(lián)寫(xiě)入過(guò)程中的錯(cuò)誤進(jìn)行回滾,保證了每次寫(xiě)入操作的原子性酌畜。

關(guān)聯(lián)查詢

既然我們已經(jīng)創(chuàng)建了關(guān)聯(lián)關(guān)系怎囚,那么如果通過(guò)關(guān)聯(lián)關(guān)系,查詢到對(duì)應(yīng)的數(shù)據(jù)庫(kù)內(nèi)容呢桥胞?

在多對(duì)多的關(guān)聯(lián)條件下恳守,如果我們要查詢某個(gè)用戶的所有分組信息,需要通過(guò)用戶id來(lái)查詢其關(guān)聯(lián)的所有group埠戳。

// service

async getGroupByUserId = ({
    id,
}) => {
    const { ctx } = this;
    const group = await ctx.model.User.findAll({
        attributes: ['project.id', 'project.name'],
        include: [
            {
                model: ctx.model.Group,
                as: 'project',
                // 指定關(guān)聯(lián)表查詢屬性井誉,這里表示不需要任何關(guān)聯(lián)表的屬性
                attributes: [],
                through: {
                    // 指定中間表的屬性,這里表示不需要任何中間表的屬性
                    attributes: []
                }
            }
        ],
        where: {
            id,
        },
        raw: true,
        // 這個(gè)需要和上面的中間表屬性配合整胃,表示不忽略include屬性中的attributes參數(shù)
        includeIgnoreAttributes: false,
    });
}
復(fù)制代碼

通過(guò)上面的關(guān)聯(lián)查詢方法颗圣,可以得到這樣的一條SQL語(yǔ)句:

SELECT `project`.`id`, `project`.`name` FROM `users` AS `user` LEFT OUTER JOIN ( `group_user` AS `project->group_user` INNER JOIN `groups` AS `project` ON `project`.`id` = `project->group_user`.`group_id`) ON `user`.`id` = `project->group_user`.`user_id` WHERE `user`.`id` = 1;
復(fù)制代碼

對(duì)應(yīng)的查詢結(jié)果:

[ { id: 1, name: 'default' } ]
復(fù)制代碼

而在一對(duì)多和多對(duì)一的關(guān)系下,其本質(zhì)和多對(duì)多基本上是一致的屁使,是在多的方向存儲(chǔ)一個(gè)冗余字段在岂,來(lái)保存其對(duì)應(yīng)的唯一元素的主鍵,無(wú)論是何種關(guān)系蛮寂,其默認(rèn)在sequelize中實(shí)現(xiàn)的數(shù)據(jù)模型蔽午,都是范式化的,如果需要反范式來(lái)提高數(shù)據(jù)庫(kù)效率酬蹋,還是需要自己去做冗余的及老。

hooks

在數(shù)據(jù)庫(kù)查詢的過(guò)程中,難免需要在真正的CRUD前后進(jìn)行一些數(shù)據(jù)的處理范抓。

考慮到這樣的一個(gè)場(chǎng)景:

在客戶端骄恶,我們前端存儲(chǔ)的用戶名并不是通過(guò)name來(lái)表示的,而是通過(guò)nickName字段來(lái)進(jìn)行表示的匕垫,在每次進(jìn)行讀寫(xiě)操作之前僧鲁,例如這里允許用戶自己修改自己的名字,當(dāng)請(qǐng)求發(fā)送到服務(wù)端之后象泵,交予service進(jìn)行處理寞秃。

// app/model/user
const User = app.model.define({
    // ...
}, {
    hooks: {
        beforeUpdate: (user, options) => {
            const name = user.nickName;
            delete user.nickName;
            user.name = name;
        }
    }
});
復(fù)制代碼

我們?cè)诙x模型的時(shí)候,直接定義好這個(gè)hook偶惠,beforeUpdate會(huì)在User模型每次調(diào)用update之前春寿,調(diào)用這個(gè)hook。這個(gè)hook會(huì)傳入update操作傳入的參數(shù)實(shí)例忽孽,可以直接對(duì)這個(gè)實(shí)例進(jìn)行修改绑改,保證實(shí)際update操作的實(shí)例是正確的馋缅。

hooks可以使用的地方很多,這里只是簡(jiǎn)單介紹一下使用的方法绢淀,hooks中間也可以包含異步操作,但是要注意瘾腰,如果包含異步操作的話皆的,需要返回一個(gè)Promise。我們還可以在進(jìn)行具有副作用的操作之前蹋盆,對(duì)于用戶權(quán)限進(jìn)行校驗(yàn)费薄。

hooks的使用是需要了解到其功能,然后根據(jù)自己的業(yè)務(wù)場(chǎng)景栖雾,靈活地進(jìn)行使用的楞抡。

總結(jié)

egg提供了非常多的可擴(kuò)展空間,除了使用其作為前端頁(yè)面的部署環(huán)境之外析藕,還可以承擔(dān)一些model層的工作召廷,有興趣的小伙伴可以試下通過(guò)egg實(shí)現(xiàn)前后端分離的全棧開(kāi)發(fā)工作~

在實(shí)際業(yè)務(wù)場(chǎng)景的實(shí)踐過(guò)程中,sequelize的很多解決方案都要從官方文檔中一個(gè)字一個(gè)字的查找账胧,有些問(wèn)題甚至需要去翻issue才能找到對(duì)應(yīng)的處理方法竞慢,不知道為什么官方文檔會(huì)有那么多版的中文翻譯。治泥。實(shí)踐的方案卻特別少筹煮,前端的小伙伴們大部分還是熱衷于MongoDB。確實(shí)關(guān)系型數(shù)據(jù)的操作相較于NoSQL還是比較復(fù)雜的居夹。不過(guò)解決問(wèn)題的過(guò)程雖然煩惱败潦,但是結(jié)果總還是愉悅的。

sequelize還有很多需要挖掘的地方准脂,它本身提供的很多功能在這次迭代的過(guò)程中都沒(méi)有用到劫扒。比如scopemigration意狠,有機(jī)會(huì)可以嘗試下一些新的功能和實(shí)現(xiàn)方案粟关。

參考:https://juejin.im/post/5c2db28de51d453529627ef4

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市环戈,隨后出現(xiàn)的幾起案子闷板,更是在濱河造成了極大的恐慌,老刑警劉巖院塞,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遮晚,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拦止,警方通過(guò)查閱死者的電腦和手機(jī)县遣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)糜颠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人萧求,你說(shuō)我怎么就攤上這事其兴。” “怎么了夸政?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵元旬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我守问,道長(zhǎng)匀归,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任耗帕,我火速辦了婚禮穆端,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仿便。我一直安慰自己体啰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布嗽仪。 她就那樣靜靜地躺著狡赐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钦幔。 梳的紋絲不亂的頭發(fā)上枕屉,一...
    開(kāi)封第一講書(shū)人閱讀 51,155評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音鲤氢,去河邊找鬼搀擂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛卷玉,可吹牛的內(nèi)容都是我干的哨颂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼相种,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼威恼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起寝并,我...
    開(kāi)封第一講書(shū)人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤箫措,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后衬潦,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體斤蔓,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年镀岛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弦牡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片友驮。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驾锰,靈堂內(nèi)的尸體忽然破棺而出卸留,到底是詐尸還是另有隱情,我是刑警寧澤椭豫,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布艾猜,位于F島的核電站,受9級(jí)特大地震影響捻悯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜淤毛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一今缚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧低淡,春花似錦姓言、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至猪杭,卻和暖如春餐塘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背皂吮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工戒傻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜂筹。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓需纳,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親艺挪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子不翩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容