领域模型-DDD是什么

共计 3252 个字符,预计需要花费 9 分钟才能阅读完成。

DDD是什么?

DDD 是领域驱动设计 (Domain Driven Design) 的缩写,是一种用于软件开发的设计方法论。领域驱动设计通过将软件系统看做一个由多个领域对象组成的复杂系统,在了解业务领域的基础上,更关注于设计领域对象之间的关系和交互行为,帮助开发者更好地理解业务需求,构建出更能反映业务入口的软件系统。

领域驱动设计强调深入领域知识,将领域模型作为通用语言,并将其契合在业务分析和软件设计过程中,而不是简单地转换领域模型和业务需求为代码。它具有一些核心概念,例如:

  • 领域对象和值对象: DDD 中表示业务领域的核心概念,领域对象是具有唯一标识符并且有状态的实体,而值对象则是描述领域对象属性的不可变对象。
  • 领域服务: DDD 中用于解决跨领域对象的复杂问题的服务,它们把领域模型和其他行为分离出来,增强了模型的语义表达能力。
  • 聚合和聚合根: DDD 中将相互关联的领域对象组合成聚合,并且为该聚合定义一个聚合根作为该聚合的访问入口。

领域驱动设计还包括一系列的技术实现和工具,例如事件驱动设计、面向服务的架构(SOA)和面向 REST 的架构等。它具有广泛的适用性,并且在处理复杂的业务问题时尤其有效。

关于DDD的认知误区

通常的看法是,DDD更适合大型的软件系统。其实不然,DDD是一种可以借鉴的思想,而非严格遵循的方法论。它无关系统大小,而是教我们怎么做好软件。像实体、值对象这样的概念,在大部分项目里都会用到的,这些概念并不是存在于大型系统中,而是对任何系统都是一样存在的,因为它体现了软件模型的本质。

分而治之,拥抱变化

DDD是一个分而治之的过程,也是一系列分而治之的方法论。

基本思想是将整个软件系统划分为多个领域对象和聚合,每个聚合都有自己的聚合根。在此过程中,每个聚合都会被尝试划分成子领域,以更好地表示业务的不同方面。这使得开发者能够更深入地了解业务领域,并更好地设计出对业务有意义的软件系统。

在这个过程中,DDD 鼓励开发者和领域专家紧密合作,将领域模型用于业务规划、用户故事、需求和系统建模。使用通用语言来设计领域模型,将业务的语义嵌入到模型之中,从而实现与业务可逆的设计。

对于大型或复杂的软件项目,DDD 帮助开发者将软件系统的各个领域对象拆分为更小的部分,然后优化单独领域对象的设计,并在整个系统中协调它们的实现。这是分而治之方法的关键,即对于大型问题,将其拆分为易于理解的小型问题,然后进行分别解决和集成,从而降低或隐藏业务复杂度,使系统有更好的扩展性,以拥抱变化。

领域模型

领域模型通常有两种:贫血模型和充血模型

下面以计算计程车费用,使用优惠券的简单业务场景来介绍两种模型的区别

贫血模型

其中所有主要的业务逻辑被放置在 Service 中,而 Model 只是用于存储数据,没有任何其他的行为和逻辑。因此,它只包含数据和很少的行为,但是可能需要包括许多与数据有关的方法,并可能缺乏明确的边界。

package dao
// 计程车订单
type Order struct {
    ID       int
    Distance int
    Status   string
    Fare     int
}

package repo
type OrderRepository struct{}

func (r *OrderRepository) Save(order *Order) error {
    // db 操作
}

func (r *OrderRepository) Find(orderId int) dao.Order {
    // db 操作
}

package service
type OrderService struct {
    repo *OrderRepository
}
// 生成订单
func (o *OrderService) Fare(distance int) dao.Order {
    order := o.repo.Find(orderID)
    var fare int
    if distance <= 10 {
        fare = 100 + distance*10
    } else {
        fare = 200 + (distance-10)*8
    }
    order.Fare = fare
    o.repo.Save(&order)
    return order
}

过段时间,增加了优惠券的活动上线了

func (o *OrderService) FareUseCoupon(distance int) dao.Order {
    order := orm.GetOrder(orderID)
    var fare int
    if distance <= 10 {
        fare = 100 + distance*10
    } else {
        fare = 200 + (distance-10)*8
    }
    order.Fare = fare

    // 增加优惠券的逻辑略...

    o.repo.Save(&order)
    return order
}

如果后续再来个限时折扣的活动呢?

上述示例中,Order 结构体只用于存储数据,没有包含任何的业务逻辑。而OrderRepository ` 结构体中只包含与数据相关的方法,例如获取存储在数据库中的成员、保存成员信息等。

OrderService 结构体中包含了所有的业务逻辑和数据操作,因此这是一个贫血模型。

充血模型

充血模型是一种包含领域逻辑的模型,它封装了行为和数据,即实体和相关的方法。充血模型设计将业务逻辑尽可能纳入领域对象中,将数据和领域逻辑捆绑在一起,使系统更具有表达能力,易于理解和维护。

在DDD里,一般推荐使用充血模型。

package entity
// 计程车订单
type Order struct {
    ID       int
    Distance int
    Status   string
    Fare     int
}

// 计算订单价格
func (o *Order) Fare(distance int) {
    if distance <= 10 {
        fare = 100 + distance*10
    } else {
        fare = 200 + (distance-10)*8
    }
    order.Fare = fare
}
// 使用优惠券
func (o *Order) UseCoupon(coupon entity.Coupon) {
    // 逻辑略...
}

package service

type OrderService struct {
    orderRepo  *OrderRepository
    couponRepo *CouponRepository
}
// 生成订单
func (o *OrderService) Fare(distance int, couponName string) entity.Order {
    order := o.orderRepo.Find(orderID)
    coupon := o.couponRepo.Find(couponName)
    order.Fare(distance)
    order.UseCoupon(coupon)
    o.repo.Save(&order)
    return order
}

在这个示例中,OrderCoupon 结构体中包含了具体的业务逻辑,例如 Fare() 方法用于计算订单未使用优惠券的价格,而 UseCoupon() 方法则用于使用不同的优惠券。SubPrice()方法用于计算使用了优惠券后的价格。

OrderService 结构体实现了业务逻辑和数据访问的分离,它使用了 OrderRepository 的实例和 CouponService 的实例来处理所有的业务逻辑和数据操作。这就是一个充血模型,它将业务逻辑和数据操作都封装在了结构体中。

相比之下,贫血模型将数据和行为分开,即只定义数据对象或者说只定义纯数据结构,然后将相关逻辑实现为独立函数。在这个示例中,除了定义订单对象Order以外并没有定义包含行为的方法,而是定义了计算费用、使用优惠券等函数。我们通过调用这些函数并传递相关数据对象的属性来完成计算总费用,使用优惠券等操作。

充血模型和贫血模型各有优点和适用场景。充血模型将数据、行为和逻辑封装在一起,可避免出现过多的全局变量和方法调用。而贫血模型则更加轻量级和直观,减少了对象方法的调用次数,容易理解和维护。

正文完
 
Dustin
版权声明:本站原创文章,由 Dustin 2023-02-19发表,共计3252字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。