Mysql的schemaless之道

问题

为什么会想到Mysql做schemaless化呢?不知道大家在平时的业务开发中有没有这种感觉,建不完的表,建不完的模型,然后再为每个模型写一个仓库实现, 将相关的业务逻辑写在仓库实现里, 总感觉繁琐而重复性,每次来了新的需求,总觉得是在做Copy And Paste的工作,全无创造的激情以及成就感。这也许 就是网上流行的词汇–CRUD BOY..这种低效而重复性的劳动绝对不会让自己的编程技艺得到提高。在这样的背景之下,我也强迫自己跳出舒适区,站在另外的 角度重新思考这个问题,看能否创造出更有效率的编程实践。而对Mysql进行schemaless化就是这次思考的产物。

思考

schemaless化其实是对Mysql的反模式使用。使用过NoSql的数据库都会觉得NoSql数据库更加的灵活方便,哪怕是最像关系型数据库的MongoDB,它其实 也不要求用户维护一份Schema的表结构设计,随着开发的迭代,需求的变更,表结构是可以自由扩充,但是在MYSQL的世界里面,这就比较困难了,在遇到 需要变更表结构的时候,常规的做法无外呼增加相关的字段,或者新建一张表做表关联,又或者是重新创建一张暂新的数据表结构,特别是在面向多变业务的 开发中,一个数据库中的表会野蛮的增长,技术债务也会慢慢积累下来。那么,到底有没有可能在MYSQL这种关系型的数据存储里,既保留它的优点,同时 又让它灵活的扩展呢?直到我在网上看到如下的两篇文章,才让我茅塞顿开。

  1. 中文翻译:FriendFeed 如何使用 MySQL 来存储无模式的数据

  2. 中文翻译:Schemaless:Uber基于MySQL的可扩展数据库

FriendFeed公司的实践让人印象深刻,对于Uber的实践显然是受到了FriendFeed公司的启发,不过有点复杂,目前我还未完全理解它的设计。希望以后 可以在另一篇文章里分析一下它。不过文章的内容已经给了我极大的启发点,加之近段时间,自己也一直在思考关于领域驱动和ORM的关系,突然之间解决了 一些一直困扰我的问题。

总的来说,设计思想是将业务实体的信息统一由text类型的列来存储,从而最大程度的提高了业务实体的可扩展性,然后将需要进行检索的实体字段,单独建立 索引表,从而实现索引和存储的分离,这样的好处是避免了常规的数据变更操作造成的长时间锁表,唯一需要维护的就是实体相关的索引表了,所以FriendFeed 公司有一个专门的“清洁工”进程来保证索引的持久性和一致性,写入丢失的索引,清理失效的旧索引,优先清理最近更新的实体,所以实际上维护索引的一致性非常快(几秒钟).

实践

以Laravel框架作为实践的环境,无模式的设计意味着我们有一个基实体类Entity,它长这样:


use Illuminate\Database\Eloquent\Model;

class Entity extends Model 
{
    protected $table = 'entities';

    protected $attributes = [
        'id' => '',
        'entity_type' => '',
        'entity_content' => '',
        'status' => 0,
    ];

    ...
}

由于laravel的ORM遵循ActiveRecord模式,所以必须得让Entity继承于Model,来获得操作数据库的能力。 其他的业务实体均需继承于它,显然为了管理业务实体类,需要创建一个EntityManager,它负责管理所有的业务实体。


use Illuminate\Support\Manager;

class EntityManager extends Manager
{
    public function __construct($app)
    {
        parent::__construct($app);
    }

    public function getDefaultDriver()
    {
        return '';
    }
}

EntityManager必须是单例:

$this->app->singleton('entity', function($app){
    return new EntityManager($app);
})

增加Facade支持:


use Illuminate\Support\Facades\Facade;

class EntityFacade extends Facade 
{
    public static function getFacadeAccessor()
    {
        return 'entity';
    }
}

add in config/app.php

'Entity' => EntityFacade::class,

那么其他的业务实体得继承于Entity

class BusinessEntity extends Entity 
{

}

将BusinessEntity纳入EntityManager的管理

Entity::extend('business', function(){
    return new BusinessEntity;
});

以上就是实现的基本骨架,另外还有一些细节需要完善,比如对于后台的基础管理我们可以只需引入一个仓库实现即可,无需 创建一一对应的实体仓库类,因为通过EntityManager,可以根据不同的请求参数,动态注入不同的业务实体到仓库 实现层中。而对于API服务,需要根据实际的业务需求,创建相关的索引表,完成高效的数据查询。

以下是完整的代码.