Java应用结构规范

2022 年 3 月 14 日 阿里技术


序言

在Java程序开发中,命名和应用分层无疑是广大后端同胞的两大“痛点”,本文提供一种基于领域模型的轻量级应用分层结构设计,供大家参考。下面按分层结构、分层明细、调用关系、各层规范和通用代码工具展开介绍。


一  分层结构



  • web(前端请求层)

通过调用业务层服务,处理前端的请求。

  • biz(业务层)

提供封装好的能力,并通过对能力进行组装、编排,进行业务逻辑处理。

  • dal(数据层)

对底层数据源进行增删改查操作。

  • client(外部请求层)

定义暴露给其他应用的接口。

  • common(外部公共层)

定义暴露给外部的公共类。

  • facade(外观层)

通过调用业务层服务,处理外部应用的请求。

二  分层明细


web(前端请求层)

子包
描述
controller

对接前端的控制器
model

前端请求相关的实体类
request
前端传入的请求
vo
返回给前端的实体类
convert

controller请求转化为service请求的转化类、
dto转化为vo的转化类

biz(业务层)

子包
描述
service

查询服务和域服务
query
查询服务
fulfilOrder
举例:履约单服务
ability

域能力
fulfilOrder
举例:履约单域能力
manager

对应底层数据模型的通用逻辑处理器
remote

外部服务
message
producer
消息发送器
diamond

动态配置
tair

缓存服务
config

业务层配置项,如:bean配置、hsf配置
common

内部公共类
constansts
仅内部使用的常量
convert
dto和do的转化器、service请求转化为manager请求的转化器
enums
仅内部使用的枚举
model.dto
用于业务处理的实体类载体
model.request
service和ability的请求类
model.option
查询的拓展条件,用于判断返回值的填充内容
utils
工具类

dal(数据层)

子包
描述
mapper

数据处理器

adb
adb的数据处理器

tddl
 xdb的数据处理器
model

前端请求相关的实体类
dataobject
数据实体类
query
数据查询条件
config

数据层配置项,如:mybatis配置、tddl配置、sequence配置

client(外部请求层)

子包
描述
api

暴露给外部的hsf接口

common(外部公共层)

子包
描述
constansts

暴露给外部的常量
enums

暴露给外部的枚举
exception

暴露给外部的异常
model

暴露给外部的实体类
dto
暴露给外部的dto
request
暴露给外部的请求
result
暴露给外部的返回结果
to
暴露给外部的消息实体

facade(外观层)

子包
描述
api
impl
hsf的实现类
convert

dto和外部dto的转化器、外部的请求转化为service请求的转化器
message



listener
消息的监听器

consumer
消息的处理器

start(启动类)

qatest(测试类)


三  调用关系



注意点:

  • 服务和服务直接可以互相调用;

  • 服务可以调用多个域的域能力;

  • 域能力是封装好的最小颗粒度的能力,不可互相调用;

  • 查询服务直接调用manager,不调用域能力;

四  各层规范


web(前端请求层)

  • 定义统一的异常处理切面:处理业务异常和其他运行时异常;


biz(业务层)

  • 内部服务不做异常处理和返回result封装类,异常都抛给web层和facade层处理。

  • 查询服务和其他服务区分开,单独放在一个包中;

  • 能力唯一对应一个域,且是封装好的最小颗粒度的能力。

  • 外部服务要在remote中做好异常处理和封装;

  • 业务层中的common类为仅在应用内部使用的公共类;

dal(数据层)

  • mapper要按不同类型的数据源分开存放,如adb和xdb。


common(外部公共层)

  • common只存放暴露给外部的实体类、常量和枚举;

  • 暴露给外部的dto只保留外部必要的字段,其他字段如feature等不可存在。


facade(外观层)

  • 定义统一的异常处理切面:处理业务异常和其他运行时异常;

  • facade层的hsf实现类只做简单的参数校验和转化,不要写业务逻辑。


五  通用代码和工具


web(前端请求层)

  • 统一异常处理切面

   
   
     
@RestControllerAdvicepublic class RestExceptionHandler {

@ResponseStatus(HttpStatus.OK) @ExceptionHandler(Exception.class) public Result system(HttpServletRequest req, Exception e) { AllLoggers.EXCEPTION.error("RestExceptionHandler.system|servlet:{}|method:{}|code:{}|msg:{}", req.getServletPath(),req.getMethod(), e.getMessage(), e); return Result.error(ResultCode.BASE.SYSTEM_ERROR); }
@ResponseStatus(HttpStatus.OK) @ExceptionHandler(BusinessException.class) public Result business(HttpServletRequest req, BusinessException e) { AllLoggers.EXCEPTION.error("RestExceptionHandler.business|servlet:{}|method:{}|code:{}|msg:{}", req.getServletPath(),req.getMethod(), e.getMessage(), e); return Result.error(e.getErrorCode(), e.getErrorMessage()); }}

biz(业务层)

  • 统一日志打印工具类

   
   
     
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public interface AllLoggers {
/** * 应用日志 */ Logger APPLICATION = LoggerFactory.getLogger("APPLICATION");
/** * 异常日志 */ Logger EXCEPTION = LoggerFactory.getLogger("EXCEPTION");
/** * 业务日志 */ Logger BIZ = LoggerFactory.getLogger("BIZ");
/** * hsf日志 */ Logger HSF = LoggerFactory.getLogger("HSF");
/** * 入口日志 */ Logger MTOP = LoggerFactory.getLogger("MTOP");
}


<?xml version="1.0" encoding="UTF-8"?><configuration>    <!-- https://github.com/spring-projects/spring-boot/blob/v1.5.13.RELEASE/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml -->    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
<property resource="application.properties"></property> <property name="APP_NAME" value="toms" /> <property name="LOG_PATH" value="${user.home}/${APP_NAME}/logs" /> <property name="LOG_FILE" value="${LOG_PATH}/toms-root.log" />
<appender name="APPLICATION" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_FILE}/toms-root.log</file> <encoder> <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern> <charset>UTF-8</charset> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LOG_PATH}/logs_saved/toms-root.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <maxHistory>5</maxHistory> <maxFileSize>1GB</maxFileSize> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> </appender>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> <charset>utf8</charset> </encoder> </appender>
<!--业务日志--> <appender name="TOMS-BIZ-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${LOG_PATH}/toms-biz.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <FileNamePattern>${LOG_PATH}/logs_saved/toms-biz.%d{yyyy-MM-dd}.%i.log</FileNamePattern> <maxHistory>5</maxHistory> <maxFileSize>2GB</maxFileSize> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> <encoder> <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern> <charset>UTF-8</charset> </encoder> </appender>
<!--hsf日志--> <appender name="TOMS-HSF-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${LOG_PATH}/toms-hsf.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <FileNamePattern>${LOG_PATH}/logs_saved/toms-hsf.%d{yyyy-MM-dd}.%i.log</FileNamePattern> <maxHistory>5</maxHistory> <maxFileSize>2GB</maxFileSize> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> <encoder> <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern> <charset>UTF-8</charset> </encoder> </appender>
<!-- 通用错误日志 --> <appender name="TOMS-ERROR-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${LOG_PATH}/toms-error.log</File> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <FileNamePattern>${LOG_PATH}/logs_saved/toms-error.%d{yyyy-MM-dd}.%i.log</FileNamePattern> <maxHistory>5</maxHistory> <maxFileSize>2GB</maxFileSize> <totalSizeCap>10GB</totalSizeCap> </rollingPolicy> <encoder> <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern> <charset>UTF-8</charset> </encoder> </appender>
<!-- 异常日志 --> <appender name="TOMS-EXCEPTION-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${LOG_PATH}/toms-exception.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${LOG_PATH}/logs_saved/toms-exception.%d{yyyy-MM-dd}.log</FileNamePattern> <maxHistory>5</maxHistory> </rollingPolicy> <encoder> <pattern><![CDATA[%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%level] [traceId:%X{EAGLEEYE_TRACE_ID}] [%class:%line] - %m %n ]]> </pattern> <charset>UTF-8</charset> </encoder> </appender>
<logger name="HSF" level="${logback.info.level}" additivity="false"> <appender-ref ref="TOMS-HSF-APPENDER"/> </logger>
<logger name="BIZ" level="${logback.info.level}" additivity="false"> <appender-ref ref="TOMS-BIZ-APPENDER"/> <appender-ref ref="TOMS-ERROR-APPENDER"/> </logger>
<logger name="EXCEPTION" level="${logback.info.level}" additivity="false"> <appender-ref ref="TOMS-EXCEPTION-APPENDER"/> <appender-ref ref="TOMS-ERROR-APPENDER"/> </logger>
<root level="INFO"> <appender-ref ref="CONSOLE" /> </root></configuration>


  • 单位转化工具类

   
   
     
public class UnitConvertUtils {
/** * 米和千米的进率 */ public static final double RATE_OF_METRE_AND_KILOMETRE = 1000d; public static final int INT_RATE_OF_METRE_AND_KILOMETRE = 1000;
/** * 分和元的进率 */ public static final double RATE_OF_FEN_AND_YUAN = 100d;
/** * 立方厘米和立方米的进率 */ public static final double INT_RATE_OF_CM3_AND_M3 = 1000000d;
/** * 米转千米 * * @param toConvert * @return 异常返回null */ public static Double convertMetre2Kilometre(Long toConvert) { if (toConvert == null) { return null; } return toConvert / RATE_OF_METRE_AND_KILOMETRE; }
/** * 千米转米 * * @param toConvert * @return 异常返回null */ public static Long convertKilometre2Metre(Double toConvert) { if (toConvert == null) { return null; }
BigDecimal bigDecimal = BigDecimal.valueOf(toConvert); BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_METRE_AND_KILOMETRE);
return bigDecimal.multiply(factorBigDecimal).longValue(); }
/** * 元转分 * * @param toConvert * @return 异常返回null */ public static Long convertYuan2Fen(Double toConvert) { if (toConvert == null) { return null; }
BigDecimal bigDecimal = BigDecimal.valueOf(toConvert); BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_FEN_AND_YUAN);
return bigDecimal.multiply(factorBigDecimal).longValue(); }
/** * 元转分 * * @param toConvert * @return 异常返回null */ public static Long convertYuan2Fen(String toConvert) { if (toConvert == null) { return null; }
BigDecimal bigDecimal = BigDecimal.valueOf(ConvertUtils.convertString2Double(toConvert)); BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_FEN_AND_YUAN);
return bigDecimal.multiply(factorBigDecimal).longValue(); }
/** * 分转元 * * @param price * @return */ public static String convertFen2Yuan(Long price) { if (price == null) { return null; }
return BigDecimal.valueOf(price).divide(new BigDecimal(RATE_OF_FEN_AND_YUAN)).toString(); }
/** * 里程米转换为千米 * * @param distance * @return */ public static Double meter2Kilometer(Long distance) { if (distance == null) { return null; }
BigDecimal meter = BigDecimal.valueOf(distance); BigDecimal kilometer = meter.divide(new BigDecimal(INT_RATE_OF_METRE_AND_KILOMETRE)); return kilometer.doubleValue(); }
/** * 立方厘米转立方米 * * @param volume * @return */ public static String convertCm32M3(Long volume) { if (volume == null) { return null; }
return BigDecimal.valueOf(volume).divide(new BigDecimal(INT_RATE_OF_CM3_AND_M3)).toString(); }
}




开发者评测局特别节目暨无影评测大赛颁奖典礼
重磅来袭!


阿里云开发者社区重磅评测栏目《开发者评测局》暨无影评测大赛颁奖典礼重磅开播。CSDN TOP 1 博主“处女座程序猿”、清华大学教授卓晴,苏宁消金安全运维总经理顾黄亮等来自开发者、高校、企业的参赛代表嘉宾与无影内部团队展开了深度圆桌论坛,共话“云时代云办公”。点击阅读原文查看完整视频!


登录查看更多
0

相关内容

数学软件ACM事务处理(TOMS)的目的是交流有关数学软件开发、评估和使用的重要研究成果。此外,TOMS还出版了机器可读的计算机软件,这些软件被整合到ACM的算法中;这些软件可以用任何广泛使用的编程语言编写,但是考虑到广泛的可用性和在TOMS中发表的研究的适用性。在研究论文和软件中,TOMS都寻求具有持久价值的贡献,在这些贡献中,技术质量、与重要计算的相关性、兴趣和新颖性都是很高的,并且展示是有效的。官网链接:https://toms.acm.org/index.cfm
军事知识图谱构建技术
专知会员服务
122+阅读 · 2022年4月8日
【干货书】Python参考手册,210页pdf
专知会员服务
63+阅读 · 2021年4月30日
专知会员服务
26+阅读 · 2021年3月7日
专知会员服务
27+阅读 · 2021年2月17日
专知会员服务
58+阅读 · 2021年1月17日
【Manning新书】C++并行实战,592页pdf,C++ Concurrency in Action
【2020新书】数据结构与数据表示指南,112页pdf
专知会员服务
82+阅读 · 2020年10月6日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
95+阅读 · 2019年12月4日
“C 不再是一种编程语言!”
CSDN
0+阅读 · 2022年4月4日
Gradle 与 AGP 构建 API: 进一步完善您的插件!
谷歌开发者
0+阅读 · 2022年1月5日
hyengine - 面向移动端的高性能通用编译/解释引擎
开源 Java 微服务应用程序框架 KivaKit 简介
InfoQ
0+阅读 · 2021年12月28日
开源微服务编排框架:Netflix Conductor
阿里技术
1+阅读 · 2021年12月2日
是时候聊一聊ProxySQL功能测试了
InfoQ
2+阅读 · 2021年11月17日
Effective Java 在工作中的应用总结
阿里技术
0+阅读 · 2021年9月17日
R工程化—Rest API 之plumber包
R语言中文社区
11+阅读 · 2018年12月25日
使用tinc构建full mesh结构的VPN
运维帮
68+阅读 · 2018年12月1日
国家自然科学基金
2+阅读 · 2014年12月31日
国家自然科学基金
3+阅读 · 2013年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
2+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
国家自然科学基金
1+阅读 · 2009年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
国家自然科学基金
2+阅读 · 2008年12月31日
Arxiv
28+阅读 · 2021年9月26日
A Survey on Data Augmentation for Text Classification
VIP会员
相关VIP内容
军事知识图谱构建技术
专知会员服务
122+阅读 · 2022年4月8日
【干货书】Python参考手册,210页pdf
专知会员服务
63+阅读 · 2021年4月30日
专知会员服务
26+阅读 · 2021年3月7日
专知会员服务
27+阅读 · 2021年2月17日
专知会员服务
58+阅读 · 2021年1月17日
【Manning新书】C++并行实战,592页pdf,C++ Concurrency in Action
【2020新书】数据结构与数据表示指南,112页pdf
专知会员服务
82+阅读 · 2020年10月6日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
95+阅读 · 2019年12月4日
相关资讯
“C 不再是一种编程语言!”
CSDN
0+阅读 · 2022年4月4日
Gradle 与 AGP 构建 API: 进一步完善您的插件!
谷歌开发者
0+阅读 · 2022年1月5日
hyengine - 面向移动端的高性能通用编译/解释引擎
开源 Java 微服务应用程序框架 KivaKit 简介
InfoQ
0+阅读 · 2021年12月28日
开源微服务编排框架:Netflix Conductor
阿里技术
1+阅读 · 2021年12月2日
是时候聊一聊ProxySQL功能测试了
InfoQ
2+阅读 · 2021年11月17日
Effective Java 在工作中的应用总结
阿里技术
0+阅读 · 2021年9月17日
R工程化—Rest API 之plumber包
R语言中文社区
11+阅读 · 2018年12月25日
使用tinc构建full mesh结构的VPN
运维帮
68+阅读 · 2018年12月1日
相关基金
国家自然科学基金
2+阅读 · 2014年12月31日
国家自然科学基金
3+阅读 · 2013年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
2+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
国家自然科学基金
1+阅读 · 2009年12月31日
国家自然科学基金
0+阅读 · 2009年12月31日
国家自然科学基金
2+阅读 · 2008年12月31日
Top
微信扫码咨询专知VIP会员