谈谈我的框架设计经验

本文作者分享了如何设计一个简单的任务分片框架,旨在实现业务代码和功能代码的解耦,提高代码可维护性和开发效率。通过提供接口或抽象类、自定义注解和配置文件三种方式支持业务消费,以及手动传入数据和自动加载数据两种生产方式。此外,作者强调了组件化、分层设计和善用设计模式在框架设计中的重要性,以确保框架的健壮性、扩展性和性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不出意外,很多技术人应该都有写个框架给别人用的想法,自己会不会用暂且不说,肯定用过别人写的,比如Spring这种主流的web框架,如果debug Spring源码,一步步跳来跳去,看着眼花缭乱,可能会觉得设计个框架确实还是蛮复杂的,但是如果先不去深究源码,尝试着让自己去设计一个ioc框架、aop框架,理论上也不会有太多挑战吧。

  那如果再加点条件,设计一个健壮的、高扩展性的、高性能的框架,这恐怕就不是简单敲几行代码就能搞定的了,可能还需要有适合的业务场景来反推框架朝着更加完善的方向演化,如果写了一个框架,没有业务使用,其实也就只能当成demo来看了,老实说,这篇文章,我只打算以一个demo级别的框架为例来大致说下我是怎么设计一个简单的框架的。

两年前我写过一个demo级别的框架,我叫它“任务分片框架”,名字是根据理解起的,先说下这个框架的业务模型,我认为所有的一切业务处理都可以看做是“任务”,有些任务可能需要处理相对大量的数据,假设现在是单机环境,如果希望加快数据的处理,可能就要考虑使用多个线程同时处理这些数据,可以是同样的任务处理不同的数据段 — 我习惯将不同的数据段称为“分片”,这也对应了名字重的“任务分片”。

可能有人要问了,直接写多线程代码不就行了,干嘛费事写一个框架?好吧,这个问题问的没有问题,我来解释一下,这里主要有两个原因:

第一、希望能将业务代码和功能代码解耦,这样的好处很显然,业务是业务,功能是功能,那业务看起来就非常清晰,可维护性就相对较高。

第二、既然业务和功能解耦了,那“功能”这部分代码也就可以复用了,业务逻辑是千变万化的,但是功能逻辑都是大同小异的,有些时候真正复杂的不是业务,而是功能,如果不考虑复杂的技术实现,自然开发效率也会得到提高。

那么,我是怎么设计这个框架的?

框架的本质是一堆功能代码,最终是要被业务使用的,那就首先要想好怎么被业务使用,或者说怎么被业务消费,其实我写了也有几个小框架了,虽然只是团队内部使用,不过依然有自己的一套实践经验,简单总结一下我使用到的技巧,可供参考,一般情况下这里有3种方式:

第一、提供一个接口或者抽象类

接口一般代表的是规范、抽象类一般代表的是模板,需要根据具体情况具体选择,由于java的单继承,所以尽量使用接口,业务最终会实现框架提供的接口,在框架内部做一些通用的对业务来说是透明的处理,最终运行时框架会回调这个接口的实现来处理业务。

第二、自定义注解

上面第一种情况在代码层面还是有一定侵入的,不过一般可以接受。

不过还有一种更加优雅的方式,那就是自定义注解,我的“任务分片框架”就是使用的这种方式,我定义了一个@ShardTask的方法注解用来标识这是一个需要分片的任务,框架内部会开多个线程来调用@ShardTask注解的方法去处理业务数据,对业务来说,就没必要关心具体怎样提高的处理效率,只需要知道框架可以帮忙干好这件事就行了。

使用自定义注解这种方式显然会更加优雅,但是多少会增加框架的一些逻辑处理,比如要增加扫描注解的代码的逻辑,那扫描的时机是在框架初始化时,还是真正执行时懒加载也是需要考虑的点,不管哪种方式都应该保证只扫描一次,重复的扫描只会增加没必要的成本。

第三、提供一个配置文件或者配置类

如果是文件,比如property文件,可能会定义大量key=value这样的任务配置,key是任务名称,value是具体的任务类或者方法。

如果使用的是配置类,就可以定义一个组件TaskManager,顾名思义,任务管理器,他的职责就是任务的“大管家”,里面维护着任务名称和任务的映射关系,然后业务就可以手动把自己需要用到的任务register进来,当框架需要使用某个任务的信息时,把任务名称告知“大管家”,“大管家”会帮忙快速找到这个任务。    

当然,如果考虑到以后可能会频繁调整任务名称和任务的映射关系,使用配置文件会是一种更好的方式。

不管哪种方式,思路都是一致的,至少得提供一种支持,也可以全部支持,全部支持的好处是可以让业务有多种选择。

上面说的是业务消费的方式,既然有消费,必然也有生产。

这里的生产也就是业务传入待处理数据的方式,一般情况下考虑两种方式:

第一种是让业务方手动传入数据,框架接收到数据后继续后续处理即可,但是需要尽可能多考虑到潜在的一些问题,比如,如果数据量太大怎么办,会不会OOM,如果多个线程同时生产,会不会有线程不安全的问题等等,这些细节的考量也是设计框架的需要注意的。如果使用这种方式一般会提供一个执行器,比如这里的TaskExecutor来接收用户传入的数据进行处理。

第二种是提供对现有开源组件的支持,比如说现在数据存储在mysql里面,可不可以不让业务手动从mysql里读取数据出来再交给框架,能不能让这步操作由框架自动完成,也就是说框架里首先要加入对mysql这种数据源的支持,那除了mysql之外,redis、es能不能也支持一下,可能以后还会有新的扩展需求,那么这里就需要考虑使用一些设计模式,用工厂抽象各种数据源,用模板设计模式来分离通用和变化的逻辑,提高框架的扩展性等等。

业务数据生产和消费的接入方式常见的方式差不多就这样。

这个框架虽然是demo级别的,但还是比较考验基本功的,同时也有一定的学习价值,首先基于生产消费这种模型,分别对两端进行设计,消费端有3种常见的方式,提供抽象类或接口、自定义注解和提供配置文件或配置类,生产端有2种常见的方式,手动传入和自动加载。

除此以外,还需要注意对一些细节的处理,如何保证线程安全性、如何保证扩展性、如何保证良好的性能等等,如果延伸到设计一个分布式任务分片框架又将会更加复杂。

再聊聊框架设计时需要注意的几个重点

第一、组件化

如果把框架看作一个整体,那这个整体可以拆分成一个个组件,每个组件都是可以独立存在的部分,每个组件可以独立由不同人开发,组件与组件之间面向抽象,尽可能少的耦合,这其实也是面向对象设计的基本思想。

组件化的好处不仅仅可以帮助框架设计者保持思维清晰、提高框架的可维护性,最大的好处还在于如果将来单机环境没办法支撑海量的任务处理压力了,由于每个组件天然的独立性,意味着可以将某些组件分开部署到不同的机器上,同时也可以对每个组件镜像部署,组件与组件之间只是由原来单机环境的方法调用改为分布式环境的网络调用,可能是rpc,也可能是http,根据具体情况具体选型即可。

一般情况下,至少需要提供XXXManager、XXXExecutor、XXXInitializer这几类常用的组件,每个组件具备各自的职责,如果说XXXManager是“管家”,XXXExecutor就是“干活的人”,XXXInitializer就是“提前做一些准备工作的人”,所有组件共同履行各自的职责来支撑框架的整体功能,如果考虑到扩展性,可能还会引入XXXHandler、XXXListener等等一些组件,各种组件的命名一般使用类似XXXer这类可以翻译为“XXX器”的英文单词,用来突显“组件化”这种特点。

第二、分层设计

比较有代表性的如dubbo这种rpc框架,一次rpc调用对应着一系列步骤

接力,那么dubbo在设计时就把这些步骤划分出了层次。我把你传送过去,http://dubbo.apache.org/zh-cn/docs/dev/design.html。

这种分层的思想十分常见,再比如基本都熟知的tcp/ip网络协议,就是一个分层设计的典型例子。

分层的结果是层与层解耦,层与层之间面向抽象设计,每层都是可替换的,每层的修改都不会影响到其它层,虽然会增加一定的复杂度,但是带来的好处是完全值得的。

第三、善用设计模式

设计模式是前人在实践中总结的,用来解决各种一般场景的最佳实践,用好了设计模式对提高代码的可读性、可维护性、可扩展性都非常有帮助,但是如果只是一味地生搬硬套,可能只会给编程增加一些负担,并不会带来什么明显的好处。

除了上面这些,并发编程的一些概念、一些常见的问题及解决方案都应该比较了解,这样至少不会忽视一些潜在的问题,最终引发生产事故,严重者可能会删库跑路。

暂时先写到这,我个人对软件开发比较注重“道”这个层面,因为我觉得“道”清晰了,“术”在实践中是个很自然的过程,关于框架设计,我把个人认为应该注意的一些点基本上都提到了,现在确实没法把“术”这个层面写的特别细致,因为框架这种东西强调的是实践,有了上面的认识,只要基本功足够扎实,大致研究一下目前主流框架的源码,基本上写一个demo级别的小框架问题不大。

如果以前没写过框架,一定要尝试着写,其实工作中不只是crud,只要善于发现、思考、总结,多多少少都能找到一些可以改进的地方,比如能不能优雅地消除重复代码、能不能将功能代码和业务代码解耦等等,一旦不再局限于功能实现,一个新的世界自然而然就出现了。


欢迎关注我微信公众号《倔强的文哥》(一个表面冷酷,内心热乎的互联网码农),不定时分享各种Java技术经验、面试热题、Python实用小技巧。  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值