行业资讯 2025年08月6日
0 收藏 0 点赞 517 浏览 5108 个字
摘要 :

文章目录 一、DDD究竟是什么? 二、从用户注册案例看传统开发模式 三、DDD的正确打开方式——以用户注册为例 四、DDD的关键设计要素 (一)聚合根(Aggregate Root) (……




  • 一、DDD究竟是什么
  • 二、从用户注册案例看传统开发模式
  • 三、DDD的正确打开方式——以用户注册为例
  • 四、DDD的关键设计要素
    • (一)聚合根(Aggregate Root)
    • (二)领域服务与应用服务
    • (三)领域事件(Domain Events)
  • 五、传统开发与DDD的差异对比
  • 六、电商下单案例:DDD的实际应用
    • (一)传统写法(贫血模型)
    • (二)DDD写法(充血模型)
  • 七、DDD的适用场景

我们常常会听到领域驱动设计(DDD,Domain-Driven Design )这个术语,但很多人对它的理解还停留在表面,网络上的相关文章大多晦涩难懂。今天,咱们就深入浅出地聊聊DDD,看看它究竟是什么,以及如何在实际项目中应用。

一、DDD究竟是什么?

简单来说,DDD是一种构建复杂系统的软件开发方法,它的核心在于将代码结构与业务领域的实际需求紧密结合。和传统开发对着需求文档写代码不同,DDD强调拉着业务方一起绘制领域模型,让代码能够精准地反映业务本质。用大白话讲,就是用代码还原业务的真实情况,而不只是单纯实现功能。

二、从用户注册案例看传统开发模式

为了让大家更好地理解,我们以一个简单的用户注册功能为例。在这个案例中,业务规则规定用户名必须唯一,密码要满足复杂度要求,注册成功后还得记录日志。

在传统开发模式下,可能会写出这样的代码:

@Controller
public class UserController {
    public void register(String username, String password) {
        // 校验密码
        // 检查用户名
        // 保存数据库
        // 记录日志
        // 所有逻辑混在一起
    }
}

有些开发者可能会说,代码肯定不能这么写,得进行分层,比如分成controller、service、dao层。于是,代码就变成了这样:

// Service层:仅有流程控制,业务规则散落在各处
public class UserService {
    public void register(User user) {
        // 校验规则1:写在工具类里
        ValidationUtil.checkPassword(user.getPassword()); 
        // 校验规则2:通过注解实现
        if (userRepository.exists(user)) { ... }
        // 数据直接传递到DAO
        userDao.save(user); 
    }
}

虽然代码进行了分层,流程看起来也清晰了不少,但这并不意味着就是DDD。在这段代码里,User对象只是用来承载数据的“贫血模型”,业务逻辑被分散到了各个外部模块。

三、DDD的正确打开方式——以用户注册为例

在DDD中,一些业务逻辑会被内聚到领域对象中。还是以用户注册为例,密码规则的校验就可以放到User对象里。用专业的话来说,就是业务规则被封装在领域对象内部,对象不再仅仅是“数据袋子” ,而是“充血模型”。具体代码如下:

// 领域实体:业务逻辑内聚
public class User {
    public User(String username, String password) {
        // 密码规则内聚到构造函数
        if (!isValidPassword(password)) { 
            throw new InvalidPasswordException();
        }
        this.username = username;
        this.password = encrypt(password);
    }

    // 密码复杂度校验是实体的职责
    private boolean isValidPassword(String password) { ... }
}

从这段代码可以看出,校验密码的逻辑下沉到了User领域实体对象中,这就是DDD和传统开发的一个重要区别。

四、DDD的关键设计要素

(一)聚合根(Aggregate Root)

在实际业务场景中,比如用户(User)和收货地址(Address)有关联的情况。按照传统方式,会在Service中分别管理User和Address。而在DDD里,会将User作为聚合根,由它来控制Address的增删操作。代码示例如下:

public class User {
    private List<Address> addresses;

    // 添加地址的逻辑由聚合根控制
    public void addAddress(Address address) {
        if (addresses.size() >= 5) {
            throw new AddressLimitExceededException();
        }
        addresses.add(address);
    }
}

通过这种方式,业务逻辑更加集中,管理也更加方便。

(二)领域服务与应用服务

  1. 领域服务:主要处理跨多个实体的业务逻辑。比如说转账操作,涉及到两个账户,这种核心业务逻辑就由领域服务来处理。
  2. 应用服务:负责协调流程,比如调用领域服务,再加上发送消息等操作。具体代码示例如下:
// 领域服务:处理核心业务逻辑
public class TransferService {
    public void transfer(Account from, Account to, Money amount) {
        from.debit(amount); // 账户扣款逻辑内聚在Account实体
        to.credit(amount);
    }
}

// 应用服务:编排流程,不包含业务规则
public class BankingAppService {
    public void executeTransfer(Long fromId, Long toId, BigDecimal amount) {
        Account from = accountRepository.findById(fromId);
        Account to = accountRepository.findById(toId);
        transferService.transfer(from, to, new Money(amount));
        messageQueue.send(new TransferEvent(...)); // 基础设施操作
    }
}

从代码中可以看出,领域服务专注于业务逻辑的实现,应用服务则负责流程的编排。

(三)领域事件(Domain Events)

领域事件是用事件来明确表达业务的变化。比如用户注册成功后,会触发UserRegisteredEvent事件。代码示例如下:

public class User {
    public void register() {
        // ...注册逻辑
        this.events.add(new UserRegisteredEvent(this.id)); // 记录领域事件
    }
}

这样,当业务发生变化时,可以通过这些事件进行后续的处理。

五、传统开发与DDD的差异对比

下面我们通过表格来直观地对比一下传统开发和DDD的区别:

维度 传统开发 DDD
业务逻辑归属 分散在Service、Util、Controller等多个地方 内聚在领域实体或领域服务中
模型作用 主要作为数据载体,即贫血模型 是携带行为的业务模型,也就是充血模型
技术实现影响 数据库表结构驱动代码设计 业务需求驱动数据库表结构设计

六、电商下单案例:DDD的实际应用

为了进一步加深大家对DDD的理解,我们再来看一个电商下单的案例。假设业务需求是用户下单时要校验库存、使用优惠券、计算实付金额并生成订单。

(一)传统写法(贫血模型)


// Service层:大杂烩式下单
public class OrderService {
    @Autowired private InventoryDAO inventoryDAO;
    @Autowired private CouponDAO couponDAO;
    
    public Order createOrder(Long userId, List<ItemDTO> items, Long couponId) {
        // 1. 校验库存(散落在Service)
        for (ItemDTO item : items) {
            Integer stock = inventoryDAO.getStock(item.getSkuId());
            if (item.getQuantity() > stock) {
                throw new RuntimeException(\"库存不足\");
            }
        }
        
        // 2. 计算总价
        BigDecimal total = items.stream()
                .map(i -> i.getPrice().multiply(i.getQuantity()))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        // 3. 应用优惠券(规则写在工具类)
        if (couponId != null) {
            Coupon coupon = couponDAO.getById(couponId);
            total = CouponUtil.applyCoupon(coupon, total); // 优惠逻辑隐藏在Util
        }
        
        // 4. 保存订单(纯数据操作)
        Order order = new Order();
        order.setUserId(userId);
        order.setTotalAmount(total);
        orderDAO.save(order);
        return order;
    }
}

这种传统写法存在一些问题,比如库存校验、优惠计算等逻辑分散在Service、Util、DAO中,Order对象只是数据载体,当需求变更时,修改代码就像“考古”一样困难。

(二)DDD写法(充血模型)


// 聚合根:Order(承载核心逻辑)
public class Order {
    private List<OrderItem> items;
    private Coupon coupon;
    private Money totalAmount;

    // 构造函数内聚业务逻辑
    public Order(User user, List<OrderItem> items, Coupon coupon) {
        // 1. 校验库存(领域规则内聚)
        items.forEach(item -> item.checkStock());
        
        // 2. 计算总价(业务逻辑在值对象)
        this.totalAmount = items.stream()
                .map(OrderItem::subtotal)
                .reduce(Money.ZERO, Money::add);
        
        // 3. 应用优惠券(规则在实体内部)
        if (coupon != null) {
            validateCoupon(coupon, user); // 优惠券使用规则内聚
            this.totalAmount = coupon.applyDiscount(this.totalAmount);
        }
    }

    // 优惠券校验逻辑(业务归属清晰)
    private void validateCoupon(Coupon coupon, User user) {
        if (!coupon.isValid() || !coupon.isApplicable(user)) {
            throw new InvalidCouponException();
        }
    }
}

// 领域服务:协调下单流程
public class OrderService {
    public Order createOrder(User user, List<Item> items, Coupon coupon) {
        Order order = new Order(user, convertItems(items), coupon);
        orderRepository.save(order);
        domainEventPublisher.publish(new OrderCreatedEvent(order)); // 领域事件
        return order;
    }
}

采用DDD写法后,库存校验封装在了OrderItem值对象中,优惠券规则内聚在Order实体内部方法里,计算逻辑由Money值对象保证精度。当业务发生变化时,只需要修改领域对象即可。比如产品提出新需求:优惠券需满足“订单满100减20”,且仅限新用户使用。传统开发方式需要修改Service层和Util类,而DDD只需要修改Order.validateCoupon()方法。

七、DDD的适用场景

DDD虽然强大,但并非适用于所有场景。在业务复杂的系统,像电商、金融、ERP系统,以及需求频繁变更的互联网业务中,DDD能够发挥出巨大的优势。但对于简单的CRUD操作,比如管理后台、数据报表这类功能,使用DDD反而会增加开发成本,有些小题大做。

可以这样判断是否适合使用DDD:当修改业务规则时,只需要调整领域层代码,而不需要改动Controller或DAO,那就说明DDD在这个项目中得到了较好的落地。

总之,希望通过本文,大家对DDD有了更清晰的认识。如果在阅读过程中有任何疑问,欢迎在评论区留言讨论。

微信扫一扫

支付宝扫一扫

版权: 转载请注明出处:https://www.zuozi.net/10466.html

管理员

相关推荐
2025-08-06

文章目录 一、Reader 接口概述 1.1 什么是 Reader 接口? 1.2 Reader 与 InputStream 的区别 1.3 …

988
2025-08-06

文章目录 一、事件溯源 (一)核心概念 (二)Kafka与Golang的优势 (三)完整代码实现 二、命令…

465
2025-08-06

文章目录 一、证明GC期间执行native函数的线程仍在运行 二、native线程操作Java对象的影响及处理方…

348
2025-08-06

文章目录 一、事务基础概念 二、MyBatis事务管理机制 (一)JDBC原生事务管理(JdbcTransaction)…

456
2025-08-06

文章目录 一、SnowFlake算法核心原理 二、SnowFlake算法工作流程详解 三、SnowFlake算法的Java代码…

517
2025-08-06

文章目录 一、本地Jar包的加载操作 二、本地Class的加载方法 三、远程Jar包的加载方式 你知道Groo…

832
发表评论
暂无评论

还没有评论呢,快来抢沙发~

助力内容变现

将您的收入提升到一个新的水平

点击联系客服

在线时间:08:00-23:00

客服QQ

122325244

客服电话

400-888-8888

客服邮箱

122325244@qq.com

扫描二维码

关注微信客服号