本文的 原始地址 ,传送门
尼恩说在前面
在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:
DDD落地过程中遇到了哪些问题或者挑战?怎么解决的?
DDD 落地,遇到哪些 “拦路虎”?如何破局?
所以,这里尼恩给大家做一下系统化、体系化的梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”。也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典PDF》V145版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,后台回复:领电子书
一:DDD落地的7个步骤
领域驱动设计(Domain-Driven Design,DDD)是一种软件开发方法,旨在帮助开发者更好地理解和设计复杂领域,并将领域模型直接映射到软件架构中。
要在项目中成功落地DDD,您可以采用以下一般性的步骤:
第一步:理解领域:
首先,您需要深入了解项目所涉及的领域。
这包括与领域专家合作,探索业务需求,收集和整理领域知识。
领域知识将成为您的领域模型的基础。
第二步:分解问题域、划分领域:
将领域划分为子领域(sub domains),识别出主要的业务概念和关系。
每个子领域可以有自己的领域模型,并负责特定的业务功能。
第三步:定义限界上下文:
为每个子领域定义限界上下文(bounded context),
限界上下文是一个清晰定义了领域模型的边界的范围。
在限界上下文内,领域模型的概念是一致的,但不同限界上下文之间可以有不同的模型和语言。
界限上下文,基本可以对应到 落地层面的 微服务。
这就是 DDD 建模和 微服务架构, 能够成为孪生兄弟、 天然统一的原因。
具体的方法论和落地实操,请参考 《第34章视频 DDD学习圣经》
第四步:定义统一语言
DDD 战略设计的第一步就是统一语言,也叫通用语言(UBIQUITOUS LANGUAGE),用于定义上下文
的含义。
如果定义统一语言,不同的团队,可以使用不同的 工具,可以使用思维导图、excel表格等等。
比如说,在尼恩的 《DDD学习圣经》中,就提到了 领域分析、四色建模、事件风暴 等工具。
第步:创建领域模型:
设计DDD中的常用模型,如实体(Entity)、值对象(Value Object)、聚合根(Aggregate Root)、仓储(Repository)、领域事件(Domain Events)等,以便更好地表达领域模型。
使用领域知识创建领域模型,这是DDD的核心。
领域模型是一种反映领域中实体、值对象、聚合根、仓储等概念的抽象模型。
可以使用面向对象编程来表现领域模型,并使用通用语言来描述领域概念。
第六步:领域模型映射:
将领域模型映射到代码中,可以使用对象关系映射(ORM)工具或手动编码。
确保领域模型的设计反映了领域知识和业务规则。
包括两个维度的映射:
- 微服务层面的映射: BC到微服务的映射
- 微服务内部的映射:领域对象的映射 : 如 entity 的映射
第七步:开发代码,并且测试领域模型:
通过DDD 一键代码生成工具,生成DDD 领域模型的骨架代码,并且完成领域业务代码的开发。
编写单元测试和集成测试来验证领域模型的正确性。
使用模拟对象(Mocks)等技术来隔离领域模型的测试。
第八步:持续演化:
随着项目的推进,持续改进和演化领域模型。与领域专家保持紧密合作,根据业务需求进行调整和扩展。
反馈循环:随着项目的演进,接受来自实际使用的反馈,不断改进领域模型和架构。
第九步:效能提升
领域驱动设计是一种强大的方法,可以帮助解决复杂领域中的问题,但它也需要投入时间和精力来构建和维护领域模型。
(1)使用 DDD工具 提升 效能:
考虑使用专门的DDD工具或框架,如EventStorming、CQRS(Command Query Responsibility Segregation)、Event Sourcing等,以更好地支持领域驱动设计。
(2)团队协作 提升 效能:
要成功落地DDD,需要一定的学习和实践,同时也需要团队的共识和支持。
在整个项目中,确保团队成员之间的良好沟通和协作,特别是与领域专家的沟通,以确保领域模型的准确性。
(2)文档和培训:
编写文档来记录领域模型和限界上下文,以帮助团队成员理解和使用它们。还可以提供培训以确保团队对DDD的实践有足够的了解。
(4)监控和性能优化:
在生产环境中监控应用程序,确保领域模型的性能和可伸缩性。根据实际需求进行性能优化。
二 DDD落地 遇到的问题
DDD(领域驱动设计)落地过程中通常会遇到以下几方面的问题或挑战及相应解决方法:
2.1认知与理解层面 遇到的问题
2.2设计与架构层面 遇到的问题
-
- 2.1限界上下文划分困难
- 2.2.2聚合设计遇到困难
2.1 认知与理解层面 遇到的问题
团队成员对 DDD 理解不深或存在偏差。
DDD 是一种思想而非具体技术,与传统开发模式有差异,易导致理解困难,认为与传统分层架构类似,未把握其核心内涵3。
如果 解决认知与理解层面 的偏差?
- 可以 多 组织专业培训与学习交流活动,请专家讲解或分享成功案例;
- 可以 多开展团队内部讨论,对关键概念和原则进行深入探讨;
- 可以 多 进行代码示例分析与实践练习,通过实际操作加深理解。
2.2 设计与架构层面 遇到的问题
限界上下文划分困难
聚合设计挑战
2.2.1 限界上下文划分困难
限界上下文划分困难 ,指的是: 难以准确界定不同限界上下文的边界,导致领域模型混乱,不同业务概念和逻辑交织,影响系统的可维护性与扩展性。
与业务专家紧密合作,基于业务流程、组织架构、业务规则等因素,梳理出清晰的业务边界;运用事件风暴等方法,识别关键业务事件和流程,以此确定限界上下文的范围。
如何 解决 限界上下文划分困难的问题呢?
(1) 深入理解业务(隐性的业务显性化)
- 加强业务调研:与业务专家、领域用户进行充分沟通,通过访谈、问卷调查、实地观察等方式,全面了解业务流程、业务规则和业务目标。例如在电商系统中,要深入了解商品管理、订单处理、物流配送等各个环节的具体操作和相互关系,为限界上下文的划分提供充分的业务依据。
- 绘制业务流程图:将业务流程以图形化的方式表示出来,清晰展示业务活动的先后顺序、参与角色和数据流向。这有助于发现业务中的不同逻辑板块,为划分限界上下文提供直观的参考。比如,在绘制银行贷款审批业务流程图时,可明显看出贷款申请、风险评估、审批决策等不同阶段,这些阶段可作为划分限界上下文的重要依据。
- 建立领域知识图谱:梳理领域内的概念、实体及其关系,构建领域知识图谱,明确各个业务概念的边界和关联,帮助识别不同的限界上下文。如在医疗领域,可构建包含患者、医生、病历、诊断、治疗等概念的知识图谱,根据这些概念的紧密程度划分限界上下文。
- 进行原型设计:通过快速搭建系统原型,能够帮助 识别 隐性流程/隐性的业务。
(2 ) 运用相关方法和原则
基于业务能力划分:将具有相对独立、完整业务能力的部分划分为一个限界上下文。例如,在企业资源规划(ERP)系统中,采购管理、销售管理、库存管理等各自具备独特的业务能力,可分别作为不同的限界上下文。
遵循单一职责原则:每个限界上下文应具有单一的业务职责,避免功能过于复杂和混杂。以在线教育系统为例,课程管理限界上下文负责课程的创建、编辑、发布等与课程相关的单一职责,而用户管理限界上下文则专注于用户的注册、登录、信息管理等职责。
考虑业务变化频率:将业务变化频率相近的部分划分为同一个限界上下文。这样在业务需求发生变化时,可将影响范围控制在特定的限界上下文内,降低系统的维护成本。比如,在社交媒体平台中,动态发布和点赞评论功能的变化频率可能较高,而用户基本信息管理的变化频率相对较低,可将前者划分为一个限界上下文,后者划分为另一个限界上下文。
(3) 持续迭代优化
- 进行原型设计:通过快速搭建系统原型,收集用户反馈,不断调整和优化。例如,在开发一款项目管理软件时,可先基于初步的限界上下文划分进行原型开发,让项目团队试用,根据反馈对不合理的划分进行改进。
- 开展团队研讨:组织跨职能团队进行头脑风暴和研讨会议,让不同角色的人员从各自的专业角度对限界上下文的划分提出意见和建议。比如,开发人员、业务分析师、测试人员等共同参与讨论,从技术实现、业务逻辑、测试覆盖等多个维度审视划分方案,发现潜在问题并及时改进。
- 在实践中演进:随着项目的推进和业务的发展,不断对限界上下文进行评估和调整。当出现新的业务需求或业务流程发生变化时,及时审视现有的限界上下文划分是否仍然合理,必要时进行重新划分或调整边界。例如,当电商平台新增了跨境业务时,可能需要对原有的限界上下文进行调整,增加与跨境物流、海关清关等相关的限界上下文。
2.2.2 聚合设计遇到困难
聚合设计遇到困难:指的是聚合根的选择与聚合边界的确定不易把握。聚合根过大导致性能问题,过小则逻辑分散;还存在聚合内部一致性维护困难的情况。
以下是针对聚合设计中常见问题的解决方案:
1. 聚合根过大导致性能问题
聚合根过大可能导致性能问题,例如加载和操作大量关联对象时的性能瓶颈。
解决方案:
- 拆分聚合:将大聚合拆分为多个小聚合,每个聚合只包含少量实体和值对象。例如,一个订单聚合可以拆分为订单头聚合和订单明细聚合。
- 按需加载:采用懒加载(Lazy Loading)策略,仅在需要时加载关联对象。
- 使用CQRS:对于复杂的查询需求,可以采用CQRS(命令查询责任分离)模式,将读模型和写模型分离,优化查询性能。
2. 聚合根过小导致逻辑分散
聚合根过小可能导致逻辑分散,难以维护。
解决方案:
- 合理划分聚合边界:根据业务规则和不变性(Invariants)来划分聚合边界,确保聚合内部的实体和值对象紧密相关。
- 避免过度拆分:在拆分聚合时,避免过度拆分导致逻辑分散。聚合应尽量设计得小,但不应牺牲业务逻辑的完整性。
3. 聚合内部一致性维护困难
聚合内部一致性是聚合设计的核心目标,但维护一致性可能面临挑战。
解决方案:
- 明确不变性:在设计聚合时,明确聚合内部需要维护的不变性(即业务规则),并确保这些规则在聚合根的控制下。
- 通过聚合根操作:所有对聚合内部对象的操作都应通过聚合根进行,避免外部直接访问。
- 使用领域事件:对于复杂的业务逻辑,可以通过领域事件来解耦聚合之间的交互,同时维护最终一致性。
4. 聚合边界难以确定
确定聚合边界是聚合设计中的难点,尤其是当业务规则复杂时。
解决方案:
- 基于业务规则:聚合边界应根据业务规则和不变性来确定,而不是简单地根据对象的关联关系。
- 事件风暴:通过事件风暴(Event Storming)方法,识别业务事件和聚合边界,确保聚合内的对象紧密相关。
- 持续迭代:聚合设计不是一成不变的,可以根据业务需求和技术反馈进行调整。
5. 聚合之间的关联问题
聚合之间应尽量减少直接引用,避免耦合度过高。
解决方案:
- 通过ID引用:聚合之间应通过ID引用,而不是直接引用对象实例。
- 服务层协调:在需要跨聚合操作时,可以通过服务层进行协调,而不是在聚合内部直接操作。
6. 性能优化
聚合设计需要在性能和一致性之间找到平衡。
解决方案:
- 缓存策略:使用缓存(如Redis)来减少对聚合根及其关联对象的频繁加载。
- 异步处理:对于非关键业务逻辑,可以采用异步处理和最终一致性。
通过以上方法,可以有效解决聚合设计中的常见问题,确保聚合既能维护内部一致性,又能避免性能瓶颈和逻辑分散。
2.3 技术实现层面遇到的问题
2.3.1 Spring依赖注入问题
在DDD中,领域对象(实体和值对象)通常会包含业务逻辑和状态,但在Spring框架中,领域对象需要依赖其他服务或组件时,可能会遇到依赖注入问题。
例如,通过new
关键字创建领域对象时,Spring容器无法注入依赖。
解决方案:
- 使用ApplicationContextAware:通过实现
ApplicationContextAware
接口,领域对象可以直接访问Spring容器中的bean,从而获得所需的依赖服务。但这种方式会引入对Spring容器的强耦合。 - 将依赖作为参数传入:将依赖注入改为通过方法参数传递,这种方式更符合领域对象的独立性原则,有助于代码的可测试性和清晰性。
将依赖作为参数传入 的 Demo
这个demo 展示如何通过方法参数传递依赖,而不是直接在领域对象中注入Spring管理的服务。
场景描述
假设有一个Order
领域对象,它需要计算订单的总价。
计算总价时,Order
需要调用一个DiscountService
服务来获取折扣信息。
我们不希望在Order
中直接注入DiscountService
,而是通过方法参数传递。
1. 定义领域对象 Order
public class Order {
private String orderId;
private List<OrderItem> items;
public Order(String orderId, List<OrderItem> items) {
this.orderId = orderId;
this.items = items;
}
// 计算订单总价,依赖 DiscountService 通过参数传入
public double calculateTotalPrice(DiscountService discountService) {
double total = items.stream()
.mapToDouble(item -> item.getPrice() * item.getQuantity())
.sum();
// 调用 DiscountService 获取折扣
double discount = discountService.getDiscount(orderId);
return total * (1 - discount);
}
}
2. 定义OrderItem 值对象
public class OrderItem {
private String productId;
private double price;
private int quantity;
public OrderItem(String productId, double price, int quantity) {
this.productId = productId;
this.price = price;
this.quantity = quantity;
}
public double getPrice() {
return price;
}
public int getQuantity() {
return quantity;
}
}
3. 定义 DiscountService 服务
public interface DiscountService {
double getDiscount(String orderId);
}
@Service
public class DiscountServiceImpl implements DiscountService {
@Override
public double getDiscount(String orderId) {
// 模拟根据订单ID获取折扣的逻辑
return 0.1; // 10% 折扣
}
}
4. 使用 Order
和 DiscountService
@Service
public class OrderService {
private final DiscountService discountService;
@Autowired
public OrderService(DiscountService discountService) {
this.discountService = discountService;
}
public void processOrder() {
// 创建订单项
List<OrderItem> items = List.of(
new OrderItem("product1", 100.0, 2),
new OrderItem("product2", 50.0, 1)
);
// 创建订单
Order order = new Order("order123", items);
// 计算订单总价,传入 DiscountService
double totalPrice = order.calculateTotalPrice(discountService);
System.out.println("Total Price: " + totalPrice);
}
}
5. 启动应用程序
@SpringBootApplication
public class DddDemoApplication implements CommandLineRunner {
@Autowired
private OrderService orderService;
public static void main(String[] args) {
SpringApplication.run(DddDemoApplication.class, args);
}
@Override
public void run(String... args) {
orderService.processOrder();
}
}
运行结果 运行程序后,输出如下:
Total Price: 225.0
将依赖作为参数传入 的 Demo 关键点
- 领域对象独立:
Order
不直接依赖Spring容器,而是通过方法参数传入DiscountService
。 - 可测试性:可以轻松为
Order
编写单元测试,通过MockDiscountService
来测试calculateTotalPrice
方法。 - 清晰性:依赖关系明确,代码更易读和维护。
总结:通过将依赖作为方法参数传递,可以避免领域对象与Spring框架的强耦合,同时保持代码的清晰性和可测试性。这种方式符合DDD的设计原则,是解决Spring依赖注入问题的推荐方法。
2.3.2 大聚合根的加载性能问题
问题:
当一个聚合根包含大量关联实体或值对象,并且需要在应用程序中频繁加载和操作这些关联对象时,可能会导致性能下降2。
解决方法:
采用按需加载(Lazy Loading)策略,只在需要时加载相关对象;
将大聚合根的关联对象分页加载;
使用内存缓存如 Redis 等存储已加载的聚合根和关联对象;
利用事件驱动架构,当聚合根变化时发布事件,让其他部分按需获取数据。
大聚合根的加载性能问题 demo
以下是一个示例,展示如何解决大聚合根的加载性能问题,涵盖按需加载、分页加载、使用 Redis 缓存以及事件驱动架构等解决方法。
1 定义实体类
假设我们有一个 Order
聚合根,它包含多个 OrderItem
关联实体。
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
// 订单聚合根
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 按需加载关联对象
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<OrderItem> getOrderItems() {
return orderItems;
}
public void setOrderItems(List<OrderItem> orderItems) {
this.orderItems = orderItems;
}
}
// 订单项实体
@Entity
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productName;
private int quantity;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
}
按需加载(Lazy Loading):在 Order
和 OrderItem
实体类中,使用 @OneToMany
和 @ManyToOne
注解的 fetch = FetchType.LAZY
属性,确保关联对象只在需要时才会被加载。
2. 定义 Repository 接口
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order, Long> {
}
public interface OrderItemRepository extends JpaRepository<OrderItem, Long> {
}
3. 使用 Redis 缓存
使用 Redis 缓存:在 OrderService
类中,使用 RedisTemplate
从缓存中获取订单信息,如果缓存中没有,则从数据库中获取并将其存入缓存。
分页加载:
在 OrderService
类的 getOrderItemsByOrderId
方法中,模拟了分页查询关联对象的逻辑。
在实际应用中,可以使用 JPA 的分页查询方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Order getOrderById(Long id) {
// 先从缓存中获取
Order order = (Order) redisTemplate.opsForValue().get("order:" + id);
if (order == null) {
// 缓存中没有,从数据库中获取
Optional<Order> optionalOrder = orderRepository.findById(id);
if (optionalOrder.isPresent()) {
order = optionalOrder.get();
// 将订单存入缓存
redisTemplate.opsForValue().set("order:" + id, order);
}
}
return order;
}
public List<OrderItem> getOrderItemsByOrderId(Long orderId, int page, int size) {
// 这里简单模拟分页查询
// 在实际应用中,需要使用 JPA 的分页查询方法
Order order = getOrderById(orderId);
List<OrderItem> orderItems = order.getOrderItems();
int start = page * size;
int end = Math.min(start + size, orderItems.size());
return orderItems.subList(start, end);
}
}
4. 事件驱动架构
定义了 OrderUpdatedEvent
事件,当订单更新时,通过 OrderEventService
发布该事件。
通过 OrderEventListener
监听该事件,当事件发生时,清除缓存中的订单信息,确保下次获取的是最新数据。
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
// 订单更新事件
class OrderUpdatedEvent extends ApplicationEvent {
private final Long orderId;
public OrderUpdatedEvent(Object source, Long orderId) {
super(source);
this.orderId = orderId;
}
public Long getOrderId() {
return orderId;
}
}
// 订单服务,实现事件发布
@Service
public class OrderEventService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void updateOrder(Order order) {
// 更新订单逻辑
// ...
// 发布订单更新事件
eventPublisher.publishEvent(new OrderUpdatedEvent(this, order.getId()));
}
}
// 事件监听器
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class OrderEventListener {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@EventListener
public void handleOrderUpdatedEvent(OrderUpdatedEvent event) {
// 当订单更新时,清除缓存
redisTemplate.delete("order:" + event.getOrderId());
}
}
5. 控制器类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private OrderEventService orderEventService;
@GetMapping("/{id}")
public Order getOrder(@PathVariable Long id) {
return orderService.getOrderById(id);
}
@GetMapping("/{id}/items")
public List<OrderItem> getOrderItems(@PathVariable Long id, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) {
return orderService.getOrderItemsByOrderId(id, page, size);
}
@PostMapping("/{id}/update")
public void updateOrder(@PathVariable Long id) {
// 简单模拟更新订单
Order order = orderService.getOrderById(id);
orderEventService.updateOrder(order);
}
}
通过上面的方法,彻底解决大聚合根的加载性能问题。
2.3.3. 数据库与领域模型不匹配
领域模型和数据库模型之间可能存在不匹配,例如值对象需要序列化存储。
解决方案:
- 使用JSON字段或NoSQL数据库:对于复杂的值对象,可以将其序列化为JSON存储在关系数据库中,或者直接使用NoSQL数据库。
- 转换器模式:通过
Converter
类实现领域对象与数据库实体的双向转换。
2.3.4. 复杂查询性能低下
在DDD中,领域模型的复杂性可能导致查询性能问题。
解决方案:
- 单独构建读模型:使用CQRS(命令查询责任分离)模式,将读写模型分离,通过Elasticsearch或物化视图优化查询性能。
2.3.5. 领域事件的可靠性问题
领域事件可能丢失或重复消费。
解决方案:
- 消息队列事务:通过Rocketmq 事务消息发布 领域事件, 确保领域事件的发布和消费具有事务性。
- 幂等设计:在消费者端实现幂等逻辑,避免重复消费导致的问题。
2.4 团队协作与项目管理层面 遇到的问题
2.4.1 产研团队对业务理解不足的问题
业务研发团队缺乏对业务的深入了解,难以准确进行子领域和模块划分,把握数据归属等问题。
解决方法:
- 邀请业务专家对团队进行培训,分享业务知识和经验;
- 产研团队参与业务流程梳理和需求调研,加强对业务的理解;
- 建立业务知识库,方便团队成员随时查阅。
2.4.2 重构和业务演进的平衡的问题
业务持续演进,在进行 DDD 重构时,既要保证不影响现有业务,又要满足高优业务需求迭代 。
解决方法:
采用分阶段、小步快跑的方式进行重构;
制定合理的重构计划,优先处理对业务影响小、收益大的部分;
建立完善的测试和监控体系,及时发现和解决问题。
为何DDD如此之香?
DDD如此之香,那么多大厂对DDD如此痴迷, 背后 有深层次、根本性的原因
具体原因,参见尼恩在《DDD学习圣经》为大家深度总结的、下面的6点:
DDD现在非常火爆,是有其巨大生产价值,经济价值的, 绝不仅仅是一套概念那么简单。
DDD未来大势所趋,是大家 明年3月面试,所需要必须掌握的 核心经验、 重点经验。
尼恩结合一个工业级的DDD实操项目,在第34章视频《DDD的学习圣经》中,给大家彻底介绍一下DDD的实操、COLA 框架、DDD的落地实操。并且指导大家写入简历, 帮助大家彻底穿透DDD。
《从0到1,带大家精通DDD》系列文章
除了本文,尼恩输出了一个 《从0到1,带大家精通DDD》系列,帮助大家彻底掌握DDD,链接地址是:
《阿里大佬:DDD 落地两大步骤,以及Repository核心模式》
《极兔面试:微服务爆炸,如何解决?Uber 是怎么解决2200个微服务爆炸的?》