Egg 框架简述 (三):持久层方案

Egg 框架模型简述 (三)

  1. 简单的骨架认知
  2. 插件使用(Plugins)
  3. 持久层方案(egg-sequelize)
  4. Worker 和 高效负载均衡
  5. Agent 代理角色
  6. 定时任务

笔者的其他文章推荐: 《JS 函数式编程思维简述》

3. 持久层方案(egg-sequelize)

官方文档:https://eggjs.org

       二维表是一种非常容易描述对象状态的结构,比如我们有一些宠物小精灵的数据需要操作:

image

他们在数据表中大概长这个样子:

id name type level prob createdAt
001 妙蛙种子 草,毒 1 46.2% 2018-12-12 10 : 23 : 55
006 喷火龙 火,飞行 3 12.6% 2018-12-03 13 : 02 : 23
054 可达鸭 水,超能 1 21.7% 2018-11-11 21 : 57 : 06
... ... ... ... ... ...

了解面向对象的同学都知道,类型(class)对实例(instance)而言起到了规范和约束的作用。对于二维表的设计结构,我们也可以将其类比做一种类型建模,每一条数据都可以当作是该类型约束下的一个实际的用例。对这样的用例数据,我们最常使用的操作便是 CRUD (数据新增、查询、修改和删除)。

应用中操作数据的方式

       我们在应用中,通常通过两种方式对数据进行操作:直接使用结构化查询语言(SQL) 以及 使用数据对象模型。二者的差异是:

操作 优势 劣势
SQL查询 操作灵活,利于优化执行过程 硬编码,不利于扩展,对开发人员要求高
对象操作 扩展性强,不关注持久层类型 学习成本提升,使用框架的数据操作优化方案,灵活性较弱

在项目实际应用过程中,普遍会采用后者,以操作数据对象模型的方式,对数据库进行操作。随着时间推移,也衍生出了一些综合平衡二者优劣势的折中型持久层数据操作方案(被称之为是半自动化):既可以通过SQL的方式编写操作命令,又能够由框架解析应用层的对象模型,进行操作处理。

Egg 的选择

Sequelize 中文API:https://itbilu.com/nodejs/npm/VkYIaRPz-.html#api-instance-models

       在 Node.js 社区存在着许多 ORM (对象关系映射) 框架,其中 Sequelize 便是一个使用广泛,支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多个数据源的优秀框架。
       Sequelize 支持通过数据模型对象来操作持久层,亦支持原始的查询语句操作。对于 Egg 而言,Sequelize 是以插件的方式引入应用的,在引入之后,他会在创建一个基本数据模型对象 Model, 并挂载到 appctx 对象上,以便于使用。基本上我们需要遵循如下步骤:

image

注意,在真实的项目中我们需要考虑更多关于数据应用环境以及数据迁移、升降级。此时我们应在 配置插件 之后,进行详细的 Migrations 配置,可参考:https://eggjs.org/zh-cn/tutorials/sequelize.html#初始化数据库和-migrations

使用步骤一:安装插件 ( 示例基于mysql数据库 )

安装 egg-sequelizemysql2 插件,插件在提供 Sequelize 能力的同时,会将操作对象进行相应的挂载。

$ npm i --save egg-sequelize mysql2

使用步骤二:启用插件

在项目中的 ./config/plugin.ts 位置,启用相应的插件。

import { EggPlugin } from 'egg';

const plugin: EggPlugin = {
    
    // 启用插件: sequelize
    sequelize: {
        enable: true,
        package: 'egg-sequelize',
    },
}

使用步骤三:配置插件

在配置文件 ./config/config.{env}.ts 中,对插件进行初始化。

import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg';

export default (appInfo: EggAppInfo) => {
    const config = {} as PowerPartial<EggAppConfig>;
    // 配置 sesquelize 连接项
    config.sequelize = {
      dialect: 'mysql',
      host: '你的IP',
      port: 你的端口号,
      database: '你的数据库',
      username: '你的名字',
      password: '你的密码',
      timezone: '+08:00', // 东8区设置
      pool: { // 连接池
        max: 10,
        min: 1,
        idle: 10000,
      },
      retry: { max: 3 },
    };
    
    return {
        ...config,
    };
}

使用步骤四:创建模型

此时,我们需要根据数据库中的表模型,建立相应的应用对象模型。我们将对象模型置于 app/model/ 位置。例如我们上边的数据库表 pokemon 的对应模型 app/model/pokemon.ts

const moment = require('moment');

export default (app) => {
    const {
        STRING, DATE, NOW, INTEGER,
    } = app.Sequelize;
    
    // 模型函数返回的对象,会挂载到 Model 对象上
    const Pokemon = app.model.define('pokemon', {
        id: {
            type: INTEGER,
            autoIncrement: true,
            primaryKey: true,
        },
        name: STRING,
        type: STRING, // 类别的部分实际上应该提取成另一个单独的表进行关系描述
        level: INTEGER,
        prob: STRING,
        // 处理 sequelize 中的时区格式化
        createdAt: {
              type: DATE,
              get createdAt() {
                return moment(Pokemon.getDataValue('createdAt')).format('YYYY-MM-DD HH:mm:ss');
              },
              defaultValue: NOW,
        },
    }, {
        tableName: 'pokemon',
        timestamps: false,
    });
    return Pokemon;
};

使用步骤五:开始使用

此时,我们便可以在 ControllerService 部分,进行数据库操作了!

// app/service/pokemon.ts
import { Service } from 'egg';

export default class PokemonService extends Service {

    /**
     * 获取相应ID的数据
     * @param {*} param0
     */
    public async queryById(id: number) {
        const {ctx} = this;
        return await ctx.model.pokemon.findById( id );
    }
    
    // ...
}

项目目录结构

自此,我们的项目目录结构也相应有所变化:

// 这是一个 egg 项目的目录结构
├─ app
│  ├─ controller
│  │   ├─ pokemon.ts
│  │   └─ home.ts
│  ├─ service
│  │   ├─ pokemon.ts
│  │   └─ home.ts
│  ├─ model
│  │   ├─ pokemon.ts
│  │   └─ user.ts
│  ├─ middleware
│  │   └─ xtoken.ts
│  └─ router.ts
├─ config
│  ├─ config.default.ts
│  ├─ config.prod.ts
│  ├─ config.local.ts
│  └─ plugin.ts

而项目应用结构也更加明晰了:


image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342