序言
在Java程序開(kāi)發(fā)中,命名和應(yīng)用分層無(wú)疑是廣大后端同胞的兩大“痛點(diǎn)”,本文提供一種基于領(lǐng)域模型的輕量級(jí)應(yīng)用分層結(jié)構(gòu)設(shè)計(jì),供大家參考。下面按分層結(jié)構(gòu)、分層明細(xì)、調(diào)用關(guān)系、各層規(guī)范和通用代碼工具展開(kāi)介紹。一 分層結(jié)構(gòu)
web(前端請(qǐng)求層)
通過(guò)調(diào)用業(yè)務(wù)層服務(wù),處理前端的請(qǐng)求。
biz(業(yè)務(wù)層)
提供封裝好的能力,并通過(guò)對(duì)能力進(jìn)行組裝、編排,進(jìn)行業(yè)務(wù)邏輯處理。
dal(數(shù)據(jù)層)
對(duì)底層數(shù)據(jù)源進(jìn)行增刪改查操作。
client(外部請(qǐng)求層)
定義暴露給其他應(yīng)用的接口。
common(外部公共層)
定義暴露給外部的公共類。
facade(外觀層)
通過(guò)調(diào)用業(yè)務(wù)層服務(wù),處理外部應(yīng)用的請(qǐng)求。
二 分層明細(xì)
web(前端請(qǐng)求層)
包子包描述
controller
對(duì)接前端的控制器
model
前端請(qǐng)求相關(guān)的實(shí)體類
request前端傳入的請(qǐng)求
vo返回給前端的實(shí)體類
convert
controller請(qǐng)求轉(zhuǎn)化為service請(qǐng)求的轉(zhuǎn)化類、dto轉(zhuǎn)化為vo的轉(zhuǎn)化類
biz(業(yè)務(wù)層)
包子包描述
service
查詢服務(wù)和域服務(wù)
query查詢服務(wù)
fulfilOrder舉例:履約單服務(wù)
ability
域能力
fulfilOrder舉例:履約單域能力
manager
對(duì)應(yīng)底層數(shù)據(jù)模型的通用邏輯處理器
remote
外部服務(wù)
messageproducer消息發(fā)送器
diamond
動(dòng)態(tài)配置
tair
緩存服務(wù)
config
業(yè)務(wù)層配置項(xiàng),如:bean配置、hsf配置
common
內(nèi)部公共類
constansts僅內(nèi)部使用的常量
convertdto和do的轉(zhuǎn)化器、service請(qǐng)求轉(zhuǎn)化為manager請(qǐng)求的轉(zhuǎn)化器
enums僅內(nèi)部使用的枚舉
model.dto用于業(yè)務(wù)處理的實(shí)體類載體
model.requestservice和ability的請(qǐng)求類
model.option查詢的拓展條件,用于判斷返回值的填充內(nèi)容
utils工具類
dal(數(shù)據(jù)層)
包子包描述
mapper
數(shù)據(jù)處理器
adbadb的數(shù)據(jù)處理器
tddl xdb的數(shù)據(jù)處理器
model
前端請(qǐng)求相關(guān)的實(shí)體類
dataobject數(shù)據(jù)實(shí)體類
query數(shù)據(jù)查詢條件
config
數(shù)據(jù)層配置項(xiàng),如:mybatis配置、tddl配置、sequence配置
client(外部請(qǐng)求層)
包子包描述
api
暴露給外部的hsf接口
common(外部公共層)
包子包描述
constansts
暴露給外部的常量
enums
暴露給外部的枚舉
exception
暴露給外部的異常
model
暴露給外部的實(shí)體類
dto暴露給外部的dto
request暴露給外部的請(qǐng)求
result暴露給外部的返回結(jié)果
to暴露給外部的消息實(shí)體
facade(外觀層)
包子包描述
apiimplhsf的實(shí)現(xiàn)類
convert
dto和外部dto的轉(zhuǎn)化器、外部的請(qǐng)求轉(zhuǎn)化為service請(qǐng)求的轉(zhuǎn)化器
message
listener消息的監(jiān)聽(tīng)器
consumer消息的處理器
start(啟動(dòng)類)
qatest(測(cè)試類)
三 調(diào)用關(guān)系
注意點(diǎn):
服務(wù)和服務(wù)直接可以互相調(diào)用;
服務(wù)可以調(diào)用多個(gè)域的域能力;
域能力是封裝好的最小顆粒度的能力,不可互相調(diào)用;
查詢服務(wù)直接調(diào)用manager,不調(diào)用域能力;
四 各層規(guī)范
web(前端請(qǐng)求層)
定義統(tǒng)一的異常處理切面:處理業(yè)務(wù)異常和其他運(yùn)行時(shí)異常;
biz(業(yè)務(wù)層)
內(nèi)部服務(wù)不做異常處理和返回result封裝類,異常都拋給web層和facade層處理。
查詢服務(wù)和其他服務(wù)區(qū)分開(kāi),單獨(dú)放在一個(gè)包中;
能力唯一對(duì)應(yīng)一個(gè)域,且是封裝好的最小顆粒度的能力。
外部服務(wù)要在remote中做好異常處理和封裝;
業(yè)務(wù)層中的common類為僅在應(yīng)用內(nèi)部使用的公共類;
dal(數(shù)據(jù)層)
mapper要按不同類型的數(shù)據(jù)源分開(kāi)存放,如adb和xdb。
common(外部公共層)
common只存放暴露給外部的實(shí)體類、常量和枚舉;
暴露給外部的dto只保留外部必要的字段,其他字段如feature等不可存在。
facade(外觀層)
定義統(tǒng)一的異常處理切面:處理業(yè)務(wù)異常和其他運(yùn)行時(shí)異常;
facade層的hsf實(shí)現(xiàn)類只做簡(jiǎn)單的參數(shù)校驗(yàn)和轉(zhuǎn)化,不要寫業(yè)務(wù)邏輯。
五 通用代碼和工具
web(前端請(qǐng)求層)
統(tǒng)一異常處理切面
@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(業(yè)務(wù)層)
統(tǒng)一日志打印工具類
import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public interface AllLoggers {
/** * 應(yīng)用日志 */ Logger APPLICATION = LoggerFactory.getLogger('APPLICATION');
/** * 異常日志 */ Logger EXCEPTION = LoggerFactory.getLogger('EXCEPTION');
/** * 業(yè)務(wù)日志 */ 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>
<!--業(yè)務(wù)日志--> <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>
<!-- 通用錯(cuò)誤日志 --> <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>
單位轉(zhuǎn)化工具類
public class UnitConvertUtils {
/** * 米和千米的進(jìn)率 */ public static final double RATE_OF_METRE_AND_KILOMETRE = 1000d; public static final int INT_RATE_OF_METRE_AND_KILOMETRE = 1000;
/** * 分和元的進(jìn)率 */ public static final double RATE_OF_FEN_AND_YUAN = 100d;
/** * 立方厘米和立方米的進(jìn)率 */ public static final double INT_RATE_OF_CM3_AND_M3 = 1000000d;
/** * 米轉(zhuǎn)千米 * * @param toConvert * @return 異常返回null */ public static Double convertMetre2Kilometre(Long toConvert) { if (toConvert == null) { return null; } return toConvert / RATE_OF_METRE_AND_KILOMETRE; }
/** * 千米轉(zhuǎn)米 * * @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(); }
/** * 元轉(zhuǎn)分 * * @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(); }
/** * 元轉(zhuǎn)分 * * @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(); }
/** * 分轉(zhuǎn)元 * * @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(); }
/** * 里程米轉(zhuǎn)換為千米 * * @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(); }
/** * 立方厘米轉(zhuǎn)立方米 * * @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(); }
}
開(kāi)發(fā)者評(píng)測(cè)局特別節(jié)目暨無(wú)影評(píng)測(cè)大賽頒獎(jiǎng)典禮重磅來(lái)襲!