六个思考维度:DDD + SpringBoot工程九层结构图解与实战

这篇具有很好参考价值的文章主要介绍了六个思考维度:DDD + SpringBoot工程九层结构图解与实战。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我微信「java_front」一起交流学习文章来源地址https://www.toymoban.com/news/detail-447159.html


1 整体思想

计算机领域有一句话:计算机中任何问题都可通过增加一个虚拟层解决。这句体现了分层思想重要性,分层思想同样适用于Java工程架构。

分层优点是每层只专注本层工作,可以类比设计模式单一职责原则,或者经济学比较优势原理,每层只做本层最擅长的事情。

分层缺点是层之间通信时,需要通过适配器,翻译成本层或者下层可以理解的信息,通信成本有所增加。

我认为工程分层需要从六个维度思考:

(1) 单一

每层只处理一类事情,满足单一职责原则

(2) 降噪

信息在每一层进行传输,满足最小知识原则,只向下层传输必要信息

(3) 适配

每层都需要一个适配器,翻译信息为本层或者下层可以理解的信息

(4) 纵向

纵向做隔离,同一个领域内业务要在本领域内聚

(5) 横向

横向做编排,应用层聚合多个领域进行业务编排

(6) 数据

数据对象尽量纯净,尽量使用基本类型


1.2 九层结构

SpringBoot工程可以分成九层:

  • 工具层:util
  • 整合层:integration
  • 基础层:infrastructure
  • 领域层:domain
  • 应用层:application
  • 门面层:facade
  • 客户端:client
  • 控制层:controller
  • 启动层:boot

六个思考维度:DDD + SpringBoot工程九层结构图解与实战


1.3 微服务与九层结构

微服务和九层架构表述的是不同维度概念。微服务重点描述系统与系统之间交互关系,九层架构重点描述一个工程不同模块之间交互关系,这一点不要混淆。

微服务架构设计中通常分为前台、中台、后台:


六个思考维度:DDD + SpringBoot工程九层结构图解与实战


第一点上图所有应用均可采用九层结构。

第二点中台应用承载核心逻辑,暴露核心接口,中台并不要理解所有端数据结构,而是通过client接口暴露相对稳定的数据。

第三点针对面向B端、面向C端、面向运营三种端,各自拆分出一个应用,在此应用中进行转换、适配和裁剪,并且处理各自业务。

第四点什么是大中台、小前台思想?中台提供稳定服务,前台提供灵活入口。

第五点如果后续要做秒杀系统,那么也可以理解其为一个前台应用(seckill-front)聚合各种中台接口。


2 分层详解

第一步创建项目:

user-demo-service
    -user-demo-service-application
    -user-demo-service-boot
    -user-demo-service-client
    -user-demo-service-controller
    -user-demo-service-domain
    -user-demo-service-facade
    -user-demo-service-infrastructure
    -user-demo-service-integration
    -user-demo-service-util

2.1 util

工具层承载工具代码

不依赖本项目其它模块

只依赖一些通用工具包

user-demo-service-util
    -/src/main/java
        -date
            -DateUtil.java
        -json
            -JsonUtil.java
        -validate
            -BizValidator.java

2.2 infrastructure

基础层承载数据访问和entity

同时承载基础服务(ES、Redis、MQ)


2.2.1 项目结构

user-demo-service-infrastructure
    -/src/main/java
        -base
            -service
                -redis
                    -RedisService.java
                -mq
                    -ProducerService.java
        -player
            -entity
                -PlayerEntity.java
            -mapper
                -PlayerEntityMapper.java
        -game
            -entity
                -GameEntity.java
            -mapper
                -GameEntityMapper.java
    -/src/main/resources
        -mybatis
            -sqlmappers
                -gameEntityMapper.xml
                -playerEntityMapper.xml

2.2.2 本项目依赖

  • util

2.2.3 核心代码

创建运动员数据表:

CREATE TABLE `player` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `player_id` varchar(256) NOT NULL COMMENT '运动员编号',
  `player_name` varchar(256) NOT NULL COMMENT '运动员名称',
  `height` int(11) NOT NULL COMMENT '身高',
  `weight` int(11) NOT NULL COMMENT '体重',
  `game_performance` text COMMENT '最近一场比赛表现',
  `creator` varchar(256) NOT NULL COMMENT '创建人',
  `updator` varchar(256) NOT NULL COMMENT '修改人',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

运动员实体对象,gamePerformance字段作为string保存在数据库,体现了数据层尽量纯净,不要整合过多业务,解析任务应该放在业务层:

public class PlayerEntity {
    private Long id;
    private String playerId;
    private String playerName;
    private Integer height;
    private Integer weight;
    private String creator;
    private String updator;
    private Date createTime;
    private Date updateTime;
    private String gamePerformance;
}

运动员Mapper对象:

@Repository
public interface PlayerEntityMapper {
    int insert(PlayerEntity record);
    int updateById(PlayerEntity record);
    PlayerEntity selectById(@Param("playerId") String playerId);
}

2.3 integration

本层调用外部服务,转换外部DTO成为本项目可以理解对象。


2.3.1 项目结构

本项目调用用户中心服务:

user-demo-service-integration
    -/src/main/java
        -user
            -adapter
                -UserClientAdapter.java
            -proxy
                -UserClientProxy.java
            -vo                                    // 本项目对象
                -UserSimpleAddressVO.java
                -UserSimpleContactVO.java
                -UserSimpleBaseInfoVO.java

2.3.2 本项目依赖

  • util

2.3.3 核心代码

(1) 外部服务
// 外部对象
public class UserInfoClientDTO implements Serializable {
    private String id;
    private String name;
    private Date createTime;
    private Date updateTime;
    private String mobile;
    private String cityCode;
    private String addressDetail;
}

// 外部服务
public class UserClientService {

    // RPC
    public UserInfoClientDTO getUserInfo(String userId) {
        UserInfoClientDTO userInfo = new UserInfoClientDTO();
        userInfo.setId(userId);
        userInfo.setName(userId);
        userInfo.setCreateTime(DateUtil.now());
        userInfo.setUpdateTime(DateUtil.now());
        userInfo.setMobile("test-mobile");
        userInfo.setCityCode("test-city-code");
        userInfo.setAddressDetail("test-address-detail");
        return userInfo;
    }
}

(2) 本项目对象
// 基本对象
public class UserBaseInfoVO {
    private UserContactVO contactInfo;
    private UserAddressVO addressInfo;
}

// 地址值对象
public class UserAddressVO {
    private String cityCode;
    private String addressDetail;
}

// 联系方式值对象
public class UserContactVO {
    private String mobile;
}

(3) 适配器
public class UserClientAdapter {

    public UserBaseInfoVO convert(UserInfoClientDTO userInfo) {
        // 基础信息
        UserBaseInfoVO userBaseInfo = new UserBaseInfoVO();
        // 联系方式
        UserContactVO contactVO = new UserContactVO();
        contactVO.setMobile(userInfo.getMobile());
        userBaseInfo.setContactInfo(contactVO);
        // 地址信息
        UserAddressVO addressVO = new UserAddressVO();
        addressVO.setCityCode(userInfo.getCityCode());
        addressVO.setAddressDetail(userInfo.getAddressDetail());
        userBaseInfo.setAddressInfo(addressVO);
        return userBaseInfo;
    }
}

(4) 调用外部服务
public class UserClientProxy {

    @Resource
    private UserClientService userClientService;
    @Resource
    private UserClientAdapter userIntegrationAdapter;

    // 查询用户
    public UserBaseInfoVO getUserInfo(String userId) {
        UserInfoClientDTO user = userClientService.getUserInfo(userId);
        UserBaseInfoVO result = userIntegrationAdapter.convert(user);
        return result;
    }
}

2.4 domain

2.4.1 概念说明

通过三组对比理解领域层:

  • 领域对象 VS 数据对象
  • 领域对象 VS 业务对象
  • 领域层 VS 应用层

(1) 领域对象 VS 数据对象

数据对象使用基本类型保持纯净:

public class PlayerEntity {
    private Long id;
    private String playerId;
    private String playerName;
    private Integer height;
    private Integer weight;
    private String creator;
    private String updator;
    private Date createTime;
    private Date updateTime;
    private String gamePerformance;
}

领域对象需要体现业务含义:

public class PlayerQueryResultDomain {
    private String playerId;
    private String playerName;
    private Integer height;
    private Integer weight;
    private GamePerformanceVO gamePerformance;
}

public class GamePerformanceVO {
    // 跑动距离
    private Double runDistance;
    // 传球成功率
    private Double passSuccess;
    // 进球数
    private Integer scoreNum;
}

(2) 领域对象 VS 业务对象

业务对象同样会体现业务,领域对象和业务对象有什么不同?最大不同是领域对象采用充血模型聚合业务。

运动员新增业务对象:

public class PlayerCreateBO {
    private String playerName;
    private Integer height;
    private Integer weight;
    private GamePerformanceVO gamePerformance;
    private MaintainCreateVO maintainInfo;
}

运动员新增领域对象:

public class PlayerCreateDomain implements BizValidator {
    private String playerName;
    private Integer height;
    private Integer weight;
    private GamePerformanceVO gamePerformance;
    private MaintainCreateVO maintainInfo;

    @Override
    public void validate() {
        if (StringUtils.isEmpty(playerName)) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (null == height) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (height > 300) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (null == weight) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (null != gamePerformance) {
            gamePerformance.validate();
        }
        if (null == maintainInfo) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        maintainInfo.validate();
    }
}

(3) 领域层 VS 应用层

第一个区别:领域层关注纵向,应用层关注横向。领域层纵向做隔离,本领域业务行为要在本领域内处理完。应用层横向做编排,聚合和编排领域服务。

第二个区别:应用层可以更加灵活组合不同领域业务,并且可以增加流控、监控、日志、权限,分布式锁,相较于领域层功能更为丰富。


2.4.2 项目结构

user-demo-service-domain
    -/src/main/java
        -base
            -domain
                -BaseDomain.java
            -event
                -BaseEvent.java
            -vo
                -BaseVO.java
                -MaintainCreateVO.java
                -MaintainUpdateVO.java
        -player
            -adapter
                -PlayerDomainAdapter.java
            -domain
                -PlayerCreateDomain.java          // 领域对象 
                -PlayerUpdateDomain.java
                -PlayerQueryResultDomain.java
            -event                                // 领域事件
                -PlayerUpdateEvent.java
                -PlayerMessageSender.java
            -service                              // 领域服务
                -PlayerDomainService.java
            -vo                                   // 值对象
                -GamePerformanceVO.java                
        -game
            -adapter
                -GameDomainAdapter.java        
            -domain
                -GameCreateDomain.java
                -GameUpdateDomain.java
                -GameQueryResultDomain.java
            -service
                -GameDomainService.java

2.4.3 本项目依赖

  • util
  • client

领域对象进行业务校验,所以需要依赖client模块:

  • BizException
  • ErrorCodeBizEnum

2.4.4 核心代码

// 修改领域对象
public class PlayerUpdateDomain extends BaseDomain implements BizValidator {
    private String playerId;
    private String playerName;
    private Integer height;
    private Integer weight;
    private String updator;
    private Date updatetime;
    private GamePerformanceVO gamePerformance;
    private MaintainUpdateVO maintainInfo;

    @Override
    public void validate() {
        if (StringUtils.isEmpty(playerId)) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (StringUtils.isEmpty(playerName)) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (null == height) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (height > 300) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (null == weight) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (null != gamePerformance) {
            gamePerformance.validate();
        }
        if (null == maintainInfo) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        maintainInfo.validate();
    }
}

// 比赛表现值对象
public class GamePerformanceVO implements BizValidator {

    // 跑动距离
    private Double runDistance;
    // 传球成功率
    private Double passSuccess;
    // 进球数
    private Integer scoreNum;

    @Override
    public void validate() {
        if (null == runDistance) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (null == passSuccess) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (Double.compare(passSuccess, 100) > 0) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (null == runDistance) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (null == scoreNum) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
    }
}

// 修改人值对象
public class MaintainUpdateVO implements BizValidator {

    // 修改人
    private String updator;
    // 修改时间
    private Date updateTime;

    @Override
    public void validate() {
        if (null == updator) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
        if (null == updateTime) {
            throw new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT);
        }
    }
}

// 领域服务
public class PlayerDomainService {

    @Resource
    private UserClientProxy userClientProxy;
    @Resource
    private PlayerRepository playerEntityMapper;
    @Resource
    private PlayerDomainAdapter playerDomainAdapter;
    @Resource
    private PlayerMessageSender playerMessageSender;

    public boolean updatePlayer(PlayerUpdateDomain player) {
        AssertUtil.notNull(player, new BizException(ErrorCodeBizEnum.ILLEGAL_ARGUMENT));
        player.validate();

        // 更新运动员信息
        PlayerEntity entity = playerDomainAdapter.convertUpdate(player);
        playerEntityMapper.updateById(entity);

        // 发送更新消息
        playerMessageSender.sendPlayerUpdatemessage(player);

        // 查询用户信息
        UserSimpleBaseInfoVO userInfo = userClientProxy.getUserInfo(player.getMaintainInfo().getUpdator());
        log.info("updatePlayer maintainInfo={}", JacksonUtil.bean2Json(userInfo));
        return true;
    }
}

2.5 application

本层关注横向维度聚合领域服务,引出一种新对象称为聚合对象。因为本层需要聚合多个维度,所以需要通过聚合对象聚合多领域属性,例如提交订单需要聚合商品、物流、优惠券多个领域。

// 订单提交聚合对象
public class OrderSubmitAgg {

    // userId
    private String userId;

    // skuId
    private String skuId;

    // 购买量
    private Integer quantity;

    // 地址信息
    private String addressId;

    // 可用优惠券
    private String couponId;
}

// 订单应用服务
public class OrderApplicationService {

    @Resource
    private OrderDomainService orderDomainService;
    @Resource
    private CouponDomainService couponDomainService;
    @Resource
    private ProductDomainService productDomainService;

    // 提交订单
    public String submitOrder(OrderSubmitAgg orderSumbitAgg) {

        // 订单编号
        String orderId = generateOrderId();

        // 商品校验
        productDomainService.queryBySkuId(orderSumbitAgg.getSkuId());

        // 扣减库存
        productDomainService.subStock(orderSumbitAgg.getStockId(), orderSumbitAgg.getQuantity());

        // 优惠券校验
        couponDomainService.validate(userId, couponId);

        // ......

        // 创建订单
        OrderCreateDomain domain = OrderApplicationAdapter.convert(orderSubmitAgg);
        orderDomainService.createOrder(domain);
        return orderId;
    }
}

2.5.1 项目结构

user-demo-service-application
    -/src/main/java
        -player
            -adapter
                -PlayerApplicationAdapter.java
            -agg
                -PlayerCreateAgg.java
                -PlayerUpdateAgg.java
            -service
                -PlayerApplicationService.java
        -game
            -listener
                -PlayerUpdateListener.java            // 监听运动员更新事件

2.5.2 本项目依赖

  • util
  • domain
  • integration
  • infrastructure

2.5.3 核心代码

本项目领域事件交互使用EventBus框架:

// 运动员应用服务
public class PlayerApplicationService {

    @Resource
    private LogDomainService logDomainService;
    @Resource
    private PlayerDomainService playerDomainService;
    @Resource
    private PlayerApplicationAdapter playerApplicationAdapter;

    public boolean updatePlayer(PlayerUpdateAgg agg) {
        // 运动员领域
        boolean result = playerDomainService.updatePlayer(agg.getPlayer());
        // 日志领域
        LogReportDomain logDomain = playerApplicationAdapter.convert(agg.getPlayer().getPlayerName());
        logDomainService.log(logDomain);
        return result;
    }
}

// 比赛领域监听运动员变更事件
public class PlayerUpdateListener {

    @Resource
    private GameDomainService gameDomainService;

    @PostConstruct
    public void init() {
        EventBusManager.register(this);
    }

    @Subscribe
    public void listen(PlayerUpdateEvent event) {
        // 更新比赛计划
        gameDomainService.updateGameSchedule();
    }
}

2.6 facade + client

设计模式中有一种Facade模式,称为门面模式或者外观模式。这种模式提供一个简洁对外语义,屏蔽内部系统复杂性。

client承载数据对外传输对象DTO,facade承载对外服务,必须满足最小知识原则,无关信息不必对外透出。这样做有两个优点:

  • 简洁性:对外服务语义明确简洁
  • 安全性:敏感字段不能对外透出

2.6.1 项目结构

(1) client
user-demo-service-client
    -/src/main/java
        -base
            -dto
                -BaseDTO.java
            -error
                -BizException.java
                -BizErrorCode.java
            -event
                -BaseEventDTO.java
            -result
                -ResultDTO.java
        -player
            -dto
                -PlayerCreateDTO.java
                -PlayerQueryResultDTO.java
                -PlayerUpdateDTO.java
            -enums
                -PlayerMessageTypeEnum.java
            -service
                -PlayerClientService.java

(2) facade
user-demo-service-facade
    -/src/main/java
        -player
            -adapter
                -PlayerFacadeAdapter.java
            -impl
                -PlayerClientServiceImpl.java
        -game
            -adapter
                -GameFacadeAdapter.java
            -impl
                -GameClientServiceImpl.java

2.6.2 本项目依赖

client不依赖本项目其它模块,这一点非常重要:因为client会被外部引用,必须保证本层简洁和安全。

facade依赖本项目三个模块:

  • domain
  • client
  • application

2.6.3 核心代码

(1) DTO

以查询运动员信息为例,查询结果DTO只封装强业务字段,运动员ID、创建时间、修改时间等业务不强字段无须透出:

public class PlayerQueryResultDTO implements Serializable {
    private String playerName;
    private Integer height;
    private Integer weight;
    private GamePerformanceDTO gamePerformanceDTO;
}

(2) 客户端服务
public interface PlayerClientService {
    public ResultDTO<PlayerQueryResultDTO> queryById(String playerId);
}

(3) 适配器
public class PlayerFacadeAdapter {

    // domain -> dto
    public PlayerQueryResultDTO convertQuery(PlayerQueryResultDomain domain) {
        if (null == domain) {
            return null;
        }
        PlayerQueryResultDTO result = new PlayerQueryResultDTO();
        result.setPlayerId(domain.getPlayerId());
        result.setPlayerName(domain.getPlayerName());
        result.setHeight(domain.getHeight());
        result.setWeight(domain.getWeight());
        if (null != domain.getGamePerformance()) {
            GamePerformanceDTO performance = convertGamePerformance(domain.getGamePerformance());
            result.setGamePerformanceDTO(performance);
        }
        return result;
    }
}

(4) 服务实现

本层可以引用applicationService,也可以引用domainService,因为对于类似查询等简单业务场景,没有多领域聚合,可以直接使用领域服务。

public class PlayerClientServiceImpl implements PlayerClientService {

    @Resource
    private PlayerDomainService playerDomainService;
    @Resource
    private PlayerFacadeAdapter playerFacadeAdapter;

    @Override
    public ResultDTO<PlayerQueryResultDTO> queryById(String playerId) {
        PlayerQueryResultDomain resultDomain = playerDomainService.queryPlayerById(playerId);
        if (null == resultDomain) {
            return ResultCommonDTO.success();
        }
        PlayerQueryResultDTO result = playerFacadeAdapter.convertQuery(resultDomain);
        return ResultCommonDTO.success(result);
    }
}

2.7 controller

facade服务实现可以作为RPC提供服务,controller则作为本项目HTTP接口提供服务,供前端调用。

controller需要注意HTTP相关特性,敏感信息例如登陆用户ID不能依赖前端传递,登陆后前端会在请求头带一个登陆用户信息,服务端需要从请求头中获取并解析。


2.7.1 项目结构

user-demo-service-controller
    -/src/main/java
        -controller
            -player
                -PlayerController.java
            -game
                -GameController.java

2.7.2 本项目依赖

  • facade

2.7.3 核心代码

@RestController
@RequestMapping("/player")
public class PlayerController {

    @Resource
    private PlayerClientService playerClientService;

    @PostMapping("/add")
    public ResultDTO<Boolean> add(@RequestHeader("test-login-info") String loginUserId, @RequestBody PlayerCreateDTO dto) {
        dto.setCreator(loginUserId);
        ResultCommonDTO<Boolean> resultDTO = playerClientService.addPlayer(dto);
        return resultDTO;
    }

    @PostMapping("/update")
    public ResultDTO<Boolean> update(@RequestHeader("test-login-info") String loginUserId, @RequestBody PlayerUpdateDTO dto) {
        dto.setUpdator(loginUserId);
        ResultCommonDTO<Boolean> resultDTO = playerClientService.updatePlayer(dto);
        return resultDTO;
    }

    @GetMapping("/{playerId}/query")
    public ResultDTO<PlayerQueryResultDTO> queryById(@RequestHeader("test-login-info") String loginUserId, @PathVariable("playerId") String playerId) {
        ResultCommonDTO<PlayerQueryResultDTO> resultDTO = playerClientService.queryById(playerId);
        return resultDTO;
    }
}

2.8 boot

boot作为启动层承载启动入口


2.8.1 项目结构

所有模块代码均必须属于com.user.demo.service子路径:

user-demo-service-boot
    -/src/main/java
        -com.user.demo.service
            -MainApplication.java

2.8.2 依赖本项目

  • 所有模块

2.8.3 核心代码

@MapperScan("com.user.demo.service.infrastructure.*.mapper")
@SpringBootApplication
public class MainApplication {
    public static void main(final String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

3 文章总结

3.1 六个维度

(1) 单一

每层只处理一类事情,util只承载工具对象,integration只处理外部服务,每层职责单一且清晰

(2) 降噪

如无必要勿增实体,例如查询结果DTO只透出最关键字段,例如运动员ID、创建时间、修改时间等业务不强字段无须透出

(3) 适配

service、facade、intergration层都存在适配器,翻译信息为本层或者下层可以理解的信息

(4) 纵向

domain service内聚本领域业务

(5) 横向

application service编排多个领域业务

(6) 数据

数据对象要纯净,尽量使用基本类型


3.2 微服务与九层结构

微服务和九层结构表述的是不同维度概念。微服务重点描述系统与系统之间交互关系,九层结构重点描述一个工程不同模块之间交互关系。


欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我微信「java_front」一起交流学习

到了这里,关于六个思考维度:DDD + SpringBoot工程九层结构图解与实战的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 软件工程师,全面思考问题很重要

    为什么要全面思考问题         □ 在软件开发中,对一个问题思考得越全面,编写出的代码就会越严谨,出现bug的几率就越低;反之,如果没有对一个问题进行全面而深入的思考,编写出的代码就会漏洞百出,出现各种莫名其妙、无法复现的bug的几率也就急剧增加。   

    2024年02月08日
    浏览(30)
  • 关于一个硬件测试工程师的若干思考

    前段时间生病停更了一段时间,中间请了很长时间的病假在家养病,闲暇之余对工作产生了一丝丝思考。作为一个工作了1609天的入门硬测工程师,一时觉得工作枯燥无味,一时又对工作充满希望。硬件测试工程师!懂得都懂!大部分的工作是枯燥乏味的,在一家公司工作时间

    2024年02月02日
    浏览(31)
  • 物联网工程导论第二版思考题答案

    只总结了我的考试范围内的题目,可能不太全 第二章 1.无源RFID标签工作原理: 无源 RFID (被动标签)没有内装电池,在阅读器的读取范围之外时,电子标签处于无源状态,在阅读器的读出范围之内时,电子标签从阅读器发出的射频能量中提取其工作所需的电源。无源电子标

    2024年02月08日
    浏览(28)
  • SpringBoot进阶教程(七十六)多维度排序查询

    在项目中经常能遇到,需要对某些数据集合进行多维度排序的需求。对于集合多条件排序解决方案也有很多,今天我们就介绍一种,思路大致是设置一个分值的集合,这个分值是按照需求来设定大小的,再根据分值的大小对集合排序。 我们来模拟一个需求,现在需要查询一个

    2024年02月08日
    浏览(33)
  • 数仓工程师理解复杂业务的思考方法论

    模型设计框架(业务过程驱动)还是在经典的三层数据模型架构下去进行,概念模型、逻辑模型、物理模型 首先概念模型其实是业务过程(流程图),其中需要考虑到几个方面: 1.数据 业务覆盖 业务感知、全业务流程图 2.过程 建模过程 实操       3.服务 服务流程 流程把

    2024年02月10日
    浏览(27)
  • 吴恩达提示工程实战演练 - 如何引导GPT主动思考

    策略:明确完成任务所需的步骤 接下来的任务我想让GPT处理一段文本,且需要完成一下一系列任务: 1)将文本总结成摘要  2)将摘要翻译成英文 3)提取英文摘要中的名字 4)将翻译的英文摘要和提取的名字以JSON格式输出 每一步任务都是紧密相关的,且都是依赖上一步任务

    2024年02月09日
    浏览(42)
  • PyTorch:通过pth文件查看网络结构(查看输入输出维度)

    输出(部分截图)为: 待补充

    2024年02月16日
    浏览(24)
  • 上下文工程:基于 Github Copilot 的实时能力分析与思考

    与 ChatGPT 相比,GitHub Copilot 的强大之处在于,它构建了足够多的上下文,结合其对 LLM 的训练(或微调),可以写出非常精准的 生产级代码 。 Copilot 的可见上下文 在肉眼可见的级别里,即我们自身的使用感受,我们可以发现 Copilot 不仅是读取当前文件的源码,而是一系列相

    2024年02月15日
    浏览(36)
  • 卷积神经网络全解!CNN结构、训练与优化全维度介绍!

    本文全面探讨了卷积神经网络CNN,深入分析了背景和重要性、定义与层次介绍、训练与优化,详细分析了其卷积层、激活函数、池化层、归一化层,最后列出其训练与优化的多项关键技术:训练集准备与增强、损失函数、优化器、学习率调整、正则化技巧与模型评估调优。旨

    2024年02月12日
    浏览(26)
  • 机器人应用工程师的入职思考day3

    [1]学习高级弧焊内容,了解机器人在导轨与变位机等设备的配合下进行弧焊的方式 [2]了解专家焊内容,学会如何用两个机器人进行同步焊接 [3]场景应用 1、高级弧焊的运动学相关操作 2、两个或多个机器人之间进行同步焊接的方法 3、之前的疑惑点答疑 周二 2024/4/2 上午9:3

    2024年04月28日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包