开发框架搭建考量(开发框架搭建考量分析)
lipiwang 2024-11-17 13:27 18 浏览 0 评论
Version:0.9 StartHTML:0000000105 EndHTML:0000064633 StartFragment:0000000141 EndFragment:0000064597
本文梳理了在搭建开发框架时的一些考量及具体的处理方法。
框架搭建目标
- 整体代码规范化
- 重复代码自动化
- 复杂关系精简化
- 公共代码统一化
尽量保证开发人员的核心关注点在业务逻辑。
尽量避免非业务问题影响开发进度。
整体代码规范化
规范的作用不是为了规范而规范,也不是对不按规范的人做出惩罚。是为了方便沟通。
- 方便新员工能根据规范文档快速熟悉代码结构
- 方便接手其他人的代码时,只需要了解业务就可以
- 方便和其他人沟通时,不需要关注业务之外的内容
可以从下面几个方面做出规范:
- 统一格式化
- 一种方案是使用相同的IDE,一种方案是使用相同的格式化配置
- 保证在代码合并或代码review时,不会因为代码格式化问题导致冲突或难以对比
- 统一三方库的使用
- 统一使用一种功能的库,比如:持久化框架选定了Mybatis就不要再用Hibernate,安全框架选定了SpringSecurity就不要使用Shiro
- 这会增加团队成员的学习成本,以及后续的维护成本
- 统一代码风格
- 命名方式的统一:命名实际上是个很重要但是一直不被重视的工作。好的命名能极大的降低沟通成本。至少要保证包层级的名称的语义。我接触的项目里,对DTO来说,有的项目里叫PO,有的叫VO,导致一个开发人员接收另一个项目时,理解上就有了难度。
- 职责区分:SRP是一个看起来简单,但是很难做好的设计原则。比如:还是拿DTO来说,有的项目直接会直接将Entity作为DTO来使用,可以避免DTO与Entity直接的数据字段处理。实际上这里的DTO即做了DTO的工作,又做了Entity的工作。逻辑简单时是比较爽,但是因为两个对象的职责不一样,进化频率也不一样。当DTO字段调整时,就会对后面的DAO操作产生影响。
- RESTful规范化:现在大部分的项目都会使用RESTful。如果使用了RESTful,那就按照RESTful的规范来。尽量提高代码语义,不要使用个四不像。比如:就使用POST和GET。
- 非业务字段独立:业务相关对象和非业务相关对象区分开。例如:对于查询来说,可能需要进行分页。对于分页控制字段建议整合为PageInfo对象来统一处理,而不要作为独立的字段加到DTO对象里。一是不方便管理,二是将不同场景的字段混合到了一起,不易于区分。
- 接口携带版本号:接口携带版本号,可以基于版本来进行平滑升级。例如:可以保留/api/user/v1/login,同时发布/api/user/v2/login,待/api/user/v1/login不再使用后,在删除/api/user/v1/login
- 代码架构匹配
- 架构设计时,实际触及到的是系统、子系统、层级、模块;而具体到代码,只体现了系统(一个个的服务)和层级(Controller,Service,DAL)。模块的映射关系消失了。
- 为了提高代码与架构的匹配度,降低沟通成本。可以在代码层面通过添加一层包的方式,来映射模块关系。
- 接口与实现的层级关系(推荐)
- 传统MVC框架是按照Controller、Service和DAO的方式来分层的。如果有接口,一般情况下接口定义与实现是在同一层的,比如Service和ServiceImpl,都在Service层。实际上此种结构是有问题的。
- 假设现在有两套Service的实现,代码在项目中如何存储?
- 实际上Service和ServiceImpl分属不同的层,Service是接口层,ServiceImpl是实现层(虽然可能感觉不符合常识,不过确实如此)。两套ServiceImpl对应到Maven项目中,就是两个模块。当要替换实现时,通过Maven的项目结构调整依赖即可。
重复代码自动化
在代码规范化的前提下,基于代码生成工具(比如IDEA的EasyCode)构建一套完整的代码模板,基于代码模板快速的生成对应的代码。提高开发效率。
对于库表结构调整后对代码的影响,可以基于代码结构的调整来尽量减少对现有代码的影响。假设使用Mybatis作为持久化框架,有三个方案:
- 基于MybatisPlus(其实就是接口继承,具体参考MybatisPlus文档)
- 基于MybatisProvider
- 基于Mapper接口继承
基于MybatisProvider
- 自动生成Mapper和Provider,Mapper不使用XML,而使用注解的方式来使用SQL
- Provider中的方法对参数进行反射来处理字段信息,构建对应的sql
- 当表结构调整后,只需要调整对应的Bean的字段即可
// Mapper
@SelectProvider(type = IssueProvider.class, method = "list")
Page<IssueVO> list(IssueVO issue);
// Provider
// Issue是对应的Bean,字段调整后,在Issue中添加对应的字段即可(可以生成,可以手动添加)
public String list(Issue issue) {
SQL sql = new SQL();
sql.SELECT("*");
sql.FROM("issue");
BeanMap beanMap = BeanMap.create(issue);
for (Object key : beanMap.keySet()) {
Object val = beanMap.get(key);
if (val != null) {
if (val instanceof String && ((String) val).contains("%")) {
sql.WHERE("`" + FieldUtils.camelToLine(key + "") + "`" + "like #{" + key + "}");
} else {
sql.WHERE("`" + FieldUtils.camelToLine(key + "") + "`" + "=#{" + key + "}");
}
}
}
return sql.toString();
}
- 优势:
- 改动量比较小
- 劣势:
- 反射对性能的影响
- 基于注解的方式,习惯性问题
- IDEA对Provider的方式的支持没有XML高,XML支持自动完成,Provider中不支持
基于Mapper接口继承
- Mybatis生成的Mapper和XML文件分别为独立的目录
- 自定义的Mapper继承生成的Mapper,在自定义Mapper中新增自定义接口方法,对应的sql添加到对应的自定义XML文件中
- 自定义XML文件可以基于NameSpace来使用生成的XML文件的Result
- 当表结构调整后,重新生成对应的Mapper和XML即可
- 优势:
- 符合习惯
- 劣势:
- 改动量相对多一点,不过是生成的,所以基本可以忽略
复杂关系精简化
一般项目中的库表设计基本都是按照关联表的方式进行设计的:
- 主表+子表的关联关系
- 表中冗余其它表的字段
可以结合场景考虑,做一些结构简化和结构化。简化为对单表的操作,可以基于上面的模板来自动生成代码。
主表+子表的关联关系
此种方式适用于主表和子表需要单独修改的场景。如果主表和子表是聚合关系,即子表依赖于主表存在,且需要一起调整,甚至子表不需要调整,实际可以简化此种关联关系。
因为此种关联关系涉及到了联表查询,联表查询是无法基于工具生成的。通过简化此种关系,可以基于工具来提高开发效率。
表中冗余其它表的字段
有些情况下,可能会将两个逻辑上分离的对象整合为一个对象来处理,简化操作负责度。例如订单中可能直接就包含购买的应用信息。此种情况实际是为了操作的便利性,同时兼顾数据库特性,将结构化的数据扁平化了。
数据扁平化本身问题不大,不过弱化了代码语义。在正常理解里,订单包含订单明细,订单明细中是商品信息。
可以通过下面的方法来解决上面提到的两个问题。
解决方案
Mysql5.7开始支持Json。上述两种情况,都可以基于Json的方式来处理。即数据库字段可以使用json类型。
// 支持基于json的查询,请自行google
create table order
(
......
item_info_json json not null comment 'json',
);
public class Order {
private ItemInfo itemInfoJson;
}
在Mybatis层面,通过TypeHandler来处理Json与对象之间的自动转换。
public interface OrderDao {
// 配置转换handler
@Results(id = "jsonResult", value = {
@Result(property = "itemInfoJson", column = "item_info_json", typeHandler = JsonTypeHandler.class)
})
@Select("select * from order where rec_id = #{recId}")
Order selectByPrimaryKey(@Param("recId") String recId);
@InsertProvider(type = OrderProvider.class, method = "insert")
void insert(Order model);
......
}
public class OrderProvider {
public String insert(Order order) {
SQL sql = new SQL();
sql.INSERT_INTO("order");
BeanMap beanMap = BeanMap.create(order);
for (Object key : beanMap.keySet()) {
Object val = beanMap.get(key);
if (val != null) {
if ((key + "").endsWith("Json")) { // 根据后缀判定是否需要转换
sql.VALUES("`" + FieldUtils.camelToLine(key + "") + "`", "#{" + key + ", typeHandler=com.iwhalecloud.common.mybatis.JsonTypeHandler}");
} else {
sql.VALUES("`" + FieldUtils.camelToLine(key + "") + "`", "#{" + key + "}");
}
}
}
return sql.toString();
}
转换类处理逻辑:
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
private static final Gson gson = new Gson();
private Class<T> clazz;
public JsonTypeHandler(Class<T> clazz) {
if (clazz == null) {
throw new IllegalArgumentException("Type argument cannot be null");
} else {
this.clazz = clazz;
}
}
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, this.toJson(parameter));
}
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
return this.toObject(rs.getString(columnName), this.clazz);
}
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return this.toObject(rs.getString(columnIndex), this.clazz);
}
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return this.toObject(cs.getString(columnIndex), this.clazz);
}
private String toJson(T object) {
try {
return object instanceof String ? (String)object : gson.toJson(object);
} catch (Exception var3) {
throw new RuntimeException(var3);
}
}
private T toObject(String content, Class<?> clazz) {
if (content != null && !content.isEmpty()) {
try {
return clazz == String.class ? content : gson.fromJson(content, clazz);
} catch (Exception var4) {
throw new RuntimeException(var4);
}
} else {
return null;
}
}
}
公共代码统一化
对于多个项目中使用到的代码,需要根据场景进行公共化统一管理,避免公共代码散落在各个服务中,难以维护。
根据场景的不同,可以有几种管理方式:
- 基于公共jar包的管理
- 基于git源代码的管理
- 基于公共服务的管理
基于公共jar包的管理
此方案是最常规的方案,如果几个服务中使用到了公共的代码,比如:一些工具类。这种情况下就可以将这些类独立为公共项目,通过jar包的方式来进行管理。
基于git源代码的管理
如果公共的代码修改频率比较高,可以基于git的subtree来处理公共代码的管理问题。
- 创建一个项目,用于存放需要公用的代码,正常创建即可
- 在需要使用同步代码的项目中执行如下命令(只需要执行一次):
# module是取的别名
git remote add -f module ${上面的项目git地址}
# 将这个项目拉取到 src/module目录下
git subtree add --prefix=src/module module master --squash
- 在公共项目中修改模块代码后,正常push即可
- 需要同步的项目,执行如下命令(如果需要同步共享代码,则执行):
git subtree pull --prefix=src/module module master
基于公共服务的管理
如果公用的逻辑是一个独立的功能,后续可以作为服务对外提供服务。那可以考虑将这些代码独立为服务来对外提供服务。
总结
本文从几个维度来考量在搭建一个项目框架时需要考虑的问题,以及对应的解决方案。
相关推荐
- 《每日电讯报》研发数字工具,教你更有效率地报道新闻
-
为鼓励新闻编辑部持续创新,《每日电讯报》正在尝试有战略地研发数字工具。网站的数字媒体主任马尔科姆o科尔斯(MalcolmColes)表示,《每日电讯报》正试图去“创建一些可持续资产”,以便于让记者们...
- html5学得好不好,看掌握多少标签
-
html5你了解了多少?如果你还是入门阶段的话,或者还是一知半解的话,那么我们专门为你们收集的html5常用的标签大全对你就很有帮助了,你需要了解了html5有哪些标签你才能够更好的。驾驭html5...
- 前端分享-少年了解过iframe么(我想了解少年)
-
iframe就像是HTML的「内嵌画布」,允许在页面中加载独立网页,如同在画布上叠加另一幅动态画卷。核心特性包括:独立上下文:每个iframe都拥有独立的DOM/CSS/JS环境(类似浏...
- 做SEO要知道什么是AJAX(人能看到但搜索引擎看不到的内容)
-
一个明显的,人能看到但搜索引擎不能看到的内容是AJAX。那么什么是AJAX呢?其实,了解过的基本上也都清楚,AJAX不是新的编程语言,而是一种使用现有标准的新方法。AJAX最大的优点是在不重新加...
- 介绍最前沿的人工智能创新,‘无反向传播’神经网络训练方法?
-
图像由GoogleImageFX生成前言:本文整理自NoProp原始论文与实践代码,并结合多个公开实现细节进行了全流程复现。对神经网络训练机制的探索仍在不断演进,如果你也在研究反向传播之...
- 说说我们对HTML6的期许(对html的看法)
-
HTML5概述HTML5是HTML语言最受欢迎的版本之一,它支持音频和视频、离线存储、移动端、和标签属性等等。还提供了article,section,header这样的标签来帮助开发者更好...
- 浏览器中在线预览pdf文件,pdf.mjs插件实现web预览pdf
-
背景:本来只是淘宝上卖卖袜子,想着扩展一下业务,准备做同名“来家居”海外袜子馆外贸项目,碰到pdf在线预览的需求,就找了pdf.js插件进行实践后把此方法记录下来,可以通过多种方法来实现,每种方法都有...
- SVG 在前端的7种使用方法,你还知道哪几种?
-
本文简介点赞+关注+收藏=学会了技术一直在演变,在网页中使用SVG的方法也层出不穷。每个时期都有对应的最优解。所以我打算把我知道的7种SVG的使用方法列举出来,有备无患~如果你还...
- HTML5常用标签大全(html5em标签)
-
HTML前端开发最终取决于掌握标签的多少HTML大概有七八百个标签楼主这里给大家总结了下HTML常用标签标签描述<!--...-->定义注释。<!DOCTYPE>定义文档类型...
- "伪君子Snoop Dogg!"... WHAT?| MetroDaily 24/7
-
TUE.01-新作品-虽说年纪大了会有点糊涂,但是最近SnoopDogg的这波操作实在是让粉丝们有点迷,甚至有人表示没想到他是这样的"伪君子"......而这一切都源于他近日在IG上Po出的一...
- 莎夏·班克斯盼望表哥Snoop Dogg为其作出场曲
-
NXT女子冠军莎夏·班克斯(SashaBanks)近日接受了迈阿密先驱报采访,访谈纪要如下:关于她出众的形象:“我一向喜欢与众不同。为了能让人眼前一亮,我的装束总是非常前卫、非常抢眼,这样才能让观众...
- 喜欢Snoop!全球第一间「史努比博物馆」海外分馆在东京!
-
1950年起,由美國漫畫家CharlesM.Schulz創作的作品《Snoopy》史努比,其鮮明的可愛角色與幽默的劇情內容,至今仍成為許多大朋友與小朋友心中的最愛。為了紀念作者所設立的全球首...
- Vetements 推出 Snoop Dogg 肖像「天价」T-Shirt
-
Vetements的CEOGuramGvasalia早前才透露品牌经营策略的秘密–Vetements如何成为人人热议的话题品牌。但似乎他仍有更多需要解释的东西–这个法国奢侈品牌最新...
- 狗爷Snoop Dogg的《I Wanna Thank Me》巡回演唱会旧金山站
-
西海岸匪帮说唱歌手SnoopDogg在《IWannaThankMe》巡回演唱会旧金山站表演(图片来自ICphoto)西海岸匪帮说唱歌手SnoopDogg(图片来自ICphoto)西海...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)