Java應用結構規範

序言
在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(前端請求層)
  • 統一異常處理切面
@RestControllerAdvicepublicclassRestExceptionHandler{@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;publicinterface 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 --><includeresource="org/springframework/boot/logging/logback/defaults.xml" /><propertyresource="application.properties"></property><propertyname="APP_NAME"value="toms" /><propertyname="LOG_PATH"value="${user.home}/${APP_NAME}/logs" /><propertyname="LOG_FILE"value="${LOG_PATH}/toms-root.log" /><appendername="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><rollingPolicyclass="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><appendername="CONSOLE"class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${CONSOLE_LOG_PATTERN}</pattern><charset>utf8</charset></encoder></appender><!--業務日誌--><appendername="TOMS-BIZ-APPENDER"class="ch.qos.logback.core.rolling.RollingFileAppender"><File>${LOG_PATH}/toms-biz.log</File><rollingPolicyclass="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日誌--><appendername="TOMS-HSF-APPENDER"class="ch.qos.logback.core.rolling.RollingFileAppender"><File>${LOG_PATH}/toms-hsf.log</File><rollingPolicyclass="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><!-- 通用錯誤日誌 --><appendername="TOMS-ERROR-APPENDER"class="ch.qos.logback.core.rolling.RollingFileAppender"><File>${LOG_PATH}/toms-error.log</File><filterclass="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter><rollingPolicyclass="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><!-- 異常日誌 --><appendername="TOMS-EXCEPTION-APPENDER"class="ch.qos.logback.core.rolling.RollingFileAppender"><File>${LOG_PATH}/toms-exception.log</File><rollingPolicyclass="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><loggername="HSF"level="${logback.info.level}"additivity="false"><appender-refref="TOMS-HSF-APPENDER"/></logger><loggername="BIZ"level="${logback.info.level}"additivity="false"><appender-refref="TOMS-BIZ-APPENDER"/><appender-refref="TOMS-ERROR-APPENDER"/></logger><loggername="EXCEPTION"level="${logback.info.level}"additivity="false"><appender-refref="TOMS-EXCEPTION-APPENDER"/><appender-refref="TOMS-ERROR-APPENDER"/></logger><rootlevel="INFO"><appender-refref="CONSOLE" /></root></configuration>
  • 單位轉化工具類
publicclassUnitConvertUtils{/** * 米和千米的進率 */publicstaticfinaldouble RATE_OF_METRE_AND_KILOMETRE = 1000d;publicstaticfinalint INT_RATE_OF_METRE_AND_KILOMETRE = 1000;/** * 分和元的進率 */publicstaticfinaldouble RATE_OF_FEN_AND_YUAN = 100d;/** * 立方厘米和立方米的進率 */publicstaticfinaldouble INT_RATE_OF_CM3_AND_M3 = 1000000d;/** * 米轉千米 * * @param toConvert * @return 異常返回null */publicstatic Double convertMetre2Kilometre(Long toConvert){if (toConvert == null) {returnnull; }return toConvert / RATE_OF_METRE_AND_KILOMETRE; }/** * 千米轉米 * * @param toConvert * @return 異常返回null */publicstatic Long convertKilometre2Metre(Double toConvert){if (toConvert == null) {returnnull; } BigDecimal bigDecimal = BigDecimal.valueOf(toConvert); BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_METRE_AND_KILOMETRE);return bigDecimal.multiply(factorBigDecimal).longValue(); }/** * 元轉分 * * @param toConvert * @return 異常返回null */publicstatic Long convertYuan2Fen(Double toConvert){if (toConvert == null) {returnnull; } BigDecimal bigDecimal = BigDecimal.valueOf(toConvert); BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_FEN_AND_YUAN);return bigDecimal.multiply(factorBigDecimal).longValue(); }/** * 元轉分 * * @param toConvert * @return 異常返回null */publicstatic Long convertYuan2Fen(String toConvert){if (toConvert == null) {returnnull; } BigDecimal bigDecimal = BigDecimal.valueOf(ConvertUtils.convertString2Double(toConvert)); BigDecimal factorBigDecimal = BigDecimal.valueOf(RATE_OF_FEN_AND_YUAN);return bigDecimal.multiply(factorBigDecimal).longValue(); }/** * 分轉元 * * @param price * @return */publicstatic String convertFen2Yuan(Long price){if (price == null) {returnnull; }return BigDecimal.valueOf(price).divide(new BigDecimal(RATE_OF_FEN_AND_YUAN)).toString(); }/** * 里程米轉換為千米 * * @param distance * @return */publicstatic Double meter2Kilometer(Long distance){if (distance == null) {returnnull; } BigDecimal meter = BigDecimal.valueOf(distance); BigDecimal kilometer = meter.divide(new BigDecimal(INT_RATE_OF_METRE_AND_KILOMETRE));return kilometer.doubleValue(); }/** * 立方厘米轉立方米 * * @param volume * @return */publicstatic String convertCm32M3(Long volume){if (volume == null) {returnnull; }return BigDecimal.valueOf(volume).divide(new BigDecimal(INT_RATE_OF_CM3_AND_M3)).toString(); }}

開發者評測局特別節目暨無影評測大賽頒獎典禮
重磅來襲!
阿里雲開發者社群重磅評測欄目《開發者評測局》暨無影評測大賽頒獎典禮重磅開播。CSDN TOP 1 博主“處女座程式猿”、清華大學教授卓晴,蘇寧消金安全運維總經理顧黃亮等來自開發者、高校、企業的參賽代表嘉賓與無影內部團隊展開了深度圓桌論壇,共話“雲時代雲辦公”。點選閱讀原文檢視完整影片!

相關文章