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(前端请求层)

  • 统一异常处理切面
@RestControllerAdvice
public 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();
    }
}



目录
相关文章
|
7月前
|
人工智能 算法 Java
Java与AI驱动区块链:构建智能合约与去中心化AI应用
区块链技术和人工智能的融合正在开创去中心化智能应用的新纪元。本文深入探讨如何使用Java构建AI驱动的区块链应用,涵盖智能合约开发、去中心化AI模型训练与推理、数据隐私保护以及通证经济激励等核心主题。我们将完整展示从区块链基础集成、智能合约编写、AI模型上链到去中心化应用(DApp)开发的全流程,为构建下一代可信、透明的智能去中心化系统提供完整技术方案。
470 3
|
7月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
625 8
|
8月前
|
人工智能 Java API
Java与大模型集成实战:构建智能Java应用的新范式
随着大型语言模型(LLM)的API化,将其强大的自然语言处理能力集成到现有Java应用中已成为提升应用智能水平的关键路径。本文旨在为Java开发者提供一份实用的集成指南。我们将深入探讨如何使用Spring Boot 3框架,通过HTTP客户端与OpenAI GPT(或兼容API)进行高效、安全的交互。内容涵盖项目依赖配置、异步非阻塞的API调用、请求与响应的结构化处理、异常管理以及一些面向生产环境的最佳实践,并附带完整的代码示例,助您快速将AI能力融入Java生态。
1346 12
|
8月前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
1398 1
|
9月前
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。
|
9月前
|
机器学习/深度学习 人工智能 自然语言处理
Java 大视界 -- Java 大数据机器学习模型在自然语言生成中的可控性研究与应用(229)
本文深入探讨Java大数据与机器学习在自然语言生成(NLG)中的可控性研究,分析当前生成模型面临的“失控”挑战,如数据噪声、标注偏差及黑盒模型信任问题,提出Java技术在数据清洗、异构框架融合与生态工具链中的关键作用。通过条件注入、强化学习与模型融合等策略,实现文本生成的精准控制,并结合网易新闻与蚂蚁集团的实战案例,展示Java在提升生成效率与合规性方面的卓越能力,为金融、法律等强监管领域提供技术参考。
|
9月前
|
存储 监控 数据可视化
Java 大视界 -- 基于 Java 的大数据可视化在企业生产运营监控与决策支持中的应用(228)
本文探讨了基于 Java 的大数据可视化技术在企业生产运营监控与决策支持中的关键应用。面对数据爆炸、信息孤岛和实时性不足等挑战,Java 通过高效数据采集、清洗与可视化引擎,助力企业构建实时监控与智能决策系统,显著提升运营效率与竞争力。
|
SQL 存储 Java
Java 应用与数据库的关系| 学习笔记
快速学习 Java 应用与数据库的关系。
365 0
Java 应用与数据库的关系| 学习笔记
|
SQL 存储 Java
Java 应用与数据库的关系| 学习笔记
快速学习 Java 应用与数据库的关系。
320 0
Java 应用与数据库的关系| 学习笔记
|
SQL 存储 关系型数据库
Java应用与数据库的关系|学习笔记
快速学习Java应用与数据库的关系
Java应用与数据库的关系|学习笔记