谈谈设计思想(2)-延伸代码设计思想和微服务思想

抛🧱引🧱

延伸上一篇,本节我们讨论下在单个项目设计时,怎么利用领域模型驱动思想,设计单个项目结构.

需求

需求做一个数据查询工具,实现如下功能:
(1). 执行查询sql,展示数据,支持查询数据文件下载
(2). sql语义解析,不规范sql检查,大sql执行预警
(3). 无limit限制sql适配流式读取内容
(4). 对于执行操作人的历史记录,后续预警
(5). 一些不规范sql执行过慢,需要对此类sql执行限制

初步分析

模型分析一

简单的模型分析,核心模型:

  1. 命令发起方:定时,操作人员写sql
  2. sql执行器
  3. 数据输出模型,目前三种:邮件发送、页面展示、文件下载

进一步分析,结构化:

将上一步的模型图进一步抽象,首先转化为对应三个子领域,如下:

模型分析二

三个领域为:

  1. 执行器领域,
  2. 数据链接池管理领域
  3. 数据输出层领域

而sql发起层做为应用层,用于提供对外提供接口.所以下面重点在三个子领域建设上.

首先我们回过头来,看看我们的需求列表,

  1. (1)、(3) 可以规划执行器模块中
  2. 而对于(5)中sql执行的限制,其行为和获取数据库链接类似,可以规划到数据库链池领域中
  3. 而(1)中的展示显然归属于输出层
  4. 对于(2)、(4)中的需求,介于各个领域外,又都有交集,进而延伸出拦截层,用于做一些扩展逻辑

因此我们在细化结构图:


查询分析三

添加细节

任何项目前期一般都会考虑设计整体框架,但其实在实施中有很多排期,如前期核心功能、二期监控预警、三期辅助功能等等,下面将项目中的一些辅助功能添加:


模型分析四

根据我们刚才的分析,主模块已定,此时,其他小需求,就可以通过拦截器模块来不断扩展.

代码结构定义

分析到此,工程主体结构已比较清楚,下面定义各领域层的接口定义

查询类图

这样,基本结构已清晰

  1. executer:负责sql执行,jdbc的逻辑包装
  2. output:负责数据输出
  3. interceptor: 拦截层负责业务扩展
  4. dataSource+slowPool:数据库连接资源管理层

看下定义的包接口:


包机构定义

结构串联

要将定义好的各模块串联起来,可能比简单的业务逐级调用controller->biz->service->dao这种方式麻烦一点.像我们熟悉的很多框架,如spring、mybatis等都常使用动态代理模式进行串联(这种串联可以多看看大神们的源码,慢慢体会).

我这里采用装饰器对executer进行包装的方式,将executer、output和interceptor连接起来,内部通过execute()进行模版定义,实现顶层规范,如下:

public class SqlExecuterWrapper implements SqlExecuter {

    /**
     * 实际的sql执行类
     */
    private SqlExecuter sqlExecuter;

    private List<SqlInterceptor> sqlInterceptors;

    private DataOutput dataOutput;


    public SqlExecuterWrapper(SqlExecuter sqlExecuter, List<SqlInterceptor> sqlInterceptorList, DataOutput dataOutput) {
        this.sqlExecuter = sqlExecuter;
        this.sqlInterceptors = sqlInterceptorList;
        this.dataOutput = dataOutput;
    }


    @Override
    public ResultSet execute() {

        try {
            //1.前置拦截正序处理
            .......

            //2.执行实际执行器
            ResultSet resultSet = sqlExecuter.execute();

            //3. SQL后拦截拦截处理
           ......
            dataOutput.output(resultSet);

            //3. output后置拦截倒序处理
              ......

        } finally {

            //关闭执行器资源
          ......
      }
        return null;
    }


.......

对于数据连接池层的控制,封装连接池工厂,并对原有底层连接池进行包装,满足我们的需求,并且对于原有链接池的常用方法我们要进行保留,由于方法较多,不再一一重写,此处采用动态代理方式实现:

  1. 链接池包装类
public class DataSourcePoolWrapper extends DruidDataSource implements SlowPools {

    private DruidDataSource druidDataSource;

    public DataSourcePoolWrapper(DruidDataSource druidDataSource) {
        this.druidDataSource = druidDataSource;
    }

    public DruidPooledConnection getConnection(boolean isSlowSql) throws SQLException {
        return getConnection(isSlowSql, 0);
    }


    public DruidPooledConnection getConnection(boolean isSlowSql, long maxWaitMillis) throws SQLException {

        if (isSlowSql) {
            if (tryOpenSlowQuery(0)) {
                DruidPooledConnection connection = druidDataSource.getConnection(maxWaitMillis);
                return new DataSourceConnectionWrapper(connection.getConnectionHolder(), connection, this);
            } else {
                throw new KaelException("慢sql获取资源失败,稍后重试");
            }
        } else {
            DruidPooledConnection connection = druidDataSource.getConnection(maxWaitMillis);
            return new DataSourceConnectionWrapper(connection.getConnectionHolder(), connection, null);
        }
    }


    @Override
    public boolean tryOpenSlowQuery(long waitTime) {
        return false;
    }

    @Override
    public void release() {

    }
}
  1. 动态代理实现类:
public class KaelDataSourceProxy implements MethodInterceptor {

    private final DataSourcePoolWrapper druidDataSource;

    public KaelDataSourceProxy(DruidDataSource druidDataSource) {
        this.druidDataSource = new DataSourcePoolWrapper().setDruidDataSource(druidDataSource);
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        try {
            //Object类常用方法调用
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {

            //接口默认方法调用
                return invokeDefaultMethod(proxy, method, args);
            } else if (DataSourcePoolWrapper.class.equals(method.getDeclaringClass())) {
            //DataSourcePoolWrappe定义特有属性方法调用
                return methodProxy.invoke(druidDataSource, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
            //默认原有链接池方法调用
        return methodProxy.invoke(druidDataSource.getDruidDataSource(), args);
    }
  .......
}

总结

这样,数据查询工具的主体结构我们已经定义完成了.基本满足目前我们大部分需求.虽然看起来比我们直接写一个个service堆出来的代码麻烦,但对于后期的扩展维护,将会很好处理.
有时间建议大家多看看各类大神们的开源项目框架源码,其中领域思想处理巧妙让人称奇(推荐spring、mybatis、tomcat、dubbo,cloud等等).

延伸下一部分

本节总结了下,单个项目的领域驱动设计分析,下面跳出单个项目,我们来看下在整个分布式系统中微服务定义中的领域驱动设计思想及相关常用知识.

上一节:谈谈设计思想(1)-从我认识的支付架构说起
下一节: 谈谈设计思想(3)-延伸出微服务架构总结

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,085评论 1 32
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,423评论 0 4
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,171评论 0 9
  • 李絮小时候特别喜欢辛德瑞拉的故事,漂亮的水晶鞋,华贵的南瓜马车,英俊温柔的王子,每一样都另李絮无法自控地迷醉...
    LLunule阅读 348评论 0 0
  • 《日日夜夜》 1黑夜不会艳 白日免疲倦 好花不常现 杂草满田园 红黑不可变 信念守心间 不知那可怜 铺天盖地卷 直...
    向昕阅读 299评论 3 3