Go语言DDD实战初级篇

导读

范畴驱动规划(DDD)最简洁的描绘或许是:怎么在明确的限界上下文中创立通用言语的模型。经过 DDD思维规划开发的软件,在范畴专家、开发者和软件本身之间不存在“翻译”,三者经过在限界上下文下的通用言语直接表明。而这个系列则是咱们团队对 DDD 形式的探索和落地,旨在能帮助大家逐渐揭开DDD的神秘面纱。

全文5259字,预计阅览时间14分钟。

一、限界上下文

1.1 前语

DDD分为战略规划和战术规划,战略规划便是区别子域和限界上下文的进程。范畴区别为子域的通用区别形式是把范畴区别为 中心子域、支撑子域、通用子域。咱们在落地进程中常常会很简略区别出中心子域,一般规划mvp的时分mvp便是中心子域。可是范畴区别出中心子域、支撑子域和通用子域之后就算区别完结了吗?

1.2 子域和限界上下文

实际上子域也是范畴,一个公司不同部分重视的是一个大范畴的不同子范畴,在你重视的范畴内也需求做这种子域的区别。

比方百度这个大公司,有许多部分,这些部分都属于互联网范畴,可是每个部分又有自己重视的范畴,比方游戏部分重视的是游戏范畴、搜索部分重视的是搜索范畴。

不同部分的范畴还能够再持续区别出自己重视的范畴的中心域和支撑子域,所以整体上,范畴的区别就像一棵树。咱们回到自己重视的范畴,根据这个范畴做区别。咱们会把这个重视的范畴区别为中心域、支撑域和通用域,一般每个域都由一个小团队负责(康威定律)。

假如一个团队的作业是支撑域,那么这个支撑域便是他们的中心域,他们能够对此再做详尽的区别,何时区别到头呢?用一个详细的限界上下文处理这个叶子范畴的一切问题,而且范畴通用言语在这个上下文中没有二义性,那么就算区别到头了。

Go语言DDD实战初级篇

区别到树叶的范畴都是待处理的问题,也叫问题域,而限界上下文呢便是用来处理这个域内一切问题的模型。

针对限界上下文与范畴的对应联系Vernon给出了主张,最好是1:1的联系,当然也有其他说法如1:N,N:N,本人认同Vernon的说法,假如子域对应多个限界上下文,那么只能说该子域还能够再区别为子子域,由子子域去对应每个限界上下文。区别好子域和限界上下文后,限界上下文的主题便是处理这个子范畴的问题,手段便是DDD战术建模,工具便是范畴通用言语,约束便是范畴通用言语不能有二义性。

1.3 区别范畴(限界上下文)的根据

  • 通用言语:在做范畴区别之前一定要一致范畴通用言语,假如一个名词在用言语描绘的时分在不同语境有不同意义,那么就应该在不同语境中创立不同上下文。比方book,在写作阶段便是草稿,在出书阶段便是一个出书物,在购买阶段是一个书本类产品,在发货阶段是一个物流订单。那么就应该依照书的人物进行归类,区别出上下文。正所谓,在商言商,在范畴就应该说范畴的通用言语。

  • 范畴责任:不同范畴想要达到的方针是不相同的,每个范畴都有自己终究要完结的作业,即经过范畴常识,完结范畴活动,最后完结范畴责任。

  • 范畴人物:不同范畴的人物也不尽相同,前端范畴里或许需求ue人物、fe人物。后端范畴里或许需求java研制、dba等人物。一起上边举的book的比方,book在不同范畴的人物也是不相同的。再通俗一点,你在学校是学生人物,上班是职工人物。

  • 范畴常识:不同范畴包括的常识是不相同的,比方后端和前端,后端或许需求了解服务器相关的常识、前端需求了解的是界面相关的常识。

  • 范畴活动:不同范畴的责任也是不同的,在范畴内进行的活动也不相同,比方前端需求构建前端页面活动。后端处理数据库交互活动。范畴活动会使用到范畴常识,假如进行范畴活动的时分却不具备这个范畴的常识,那么阐明范畴区别是不合理的。

  • 范畴重视点:不同范畴重视点不同,拿person举例,person有身份证信息、年纪、身高、体重、作业、专业等信息,可是在不同范畴对person的重视点是不相同的,银行办信用卡不需求身高体重信息、参加奥运会却重视身高体重,相亲时不会重视身份证信息,但却重视你的作业、年纪等。

1.4 落地经验

在落地进程中咱们遇到了一个建模问题:

咱们的服务有两个人物运用:

  • 运营人员:运营人员要装备模型的各种规矩,可是频次相对较低。

  • 用户:用户会运用运营人员装备的规矩,运用频次较高。

在项目初期由于规划问题,终究抛弃了拆分这两个上下文,而是运用相同的上下文进行了建模。

这个问题的本质是咱们没有想好范畴区别,现在回头想想,咱们处理的是一个中心域,可是这个中心域又能够分为两个子域:一个是装备渠道子域,一个是用户运用子域。

两个子域的重视点是不同的,而且改动频次也不同,后续用户运用上下文会做横向扩展,咱们目前的单体架构尽管能做扩展,可是不符合单一责任准则,由于用户运用渠道集成了装备功用,而装备功用是不应该跟着用户功用进行扩展的。在拆分进程中,会有许多代码是重叠的,咱们的服务中就有许多Aggregate聚合,在两个上下文中有许多字段是相同的,但重复并不一定是错误,由于重复的代码重视点和改动频率是不相同的。这儿咱们介绍了使用人物进行重视点区别,从而区别子域和限界上下文的办法,实际上也能够根据其他条件对范畴进行区别,区别只需确保概念相对独立,重视点相对独立,区别后没有丢失问题就能够。

1.5 小结

  1. 范畴便是有一个规模,在这个规模内有不同的人物,每个人物都有该人物应该具备的范畴常识,各人物之间经过自己把握的常识完结彼此协作,完结一些范畴活动,产生一些范畴事情,终究完结范畴责任。
  2. 区别范畴的根据便是范畴责任(方针)、范畴重视点、完结责任需求的人物、人物需求的常识、人物需求执行的活动。
  3. 事情风暴的进程也是辨认范畴活动、范畴责任、范畴人物、范畴事情、范畴常识的进程。

二、实体

2.1 前语

实体是范畴驱动规划中非常重要的一个部分,Len Silerston 说:“实体是一个重要的概念,企业希望树立和存储的信息都是关于实体的信息”。在 DDD 中,实体的构建是重中之重。

2.2 什么是实体

实体,是谓词描绘的主体。它包含了其他范畴,如引起特色改动和状况搬迁的动作。一个典型的实体应该具有3个要素:

  • 身份标识

    • 通用类型:ID值没有事务意义,仅有即可。
    • 范畴类型:一般与各个边界上下文的实体目标有关。
  • 特色:阐明主体的静态特征,并持有数据与状况。能够区别为原子特色和组合特色。区别的根据是:该特色是否存在约束规矩、组合因子或属于自己的范畴行为。

    • 原子特色:
      • name
    • 组合特色:
      • price(num, unit);
      • 组合特色是一种很好的特质,当一个实体有了一些组合特色后, 一些细微的概念 对应的责任(基本校验、核算)将由各自的特色进行负责,而实体更重视自身概念。
  • 范畴行为

    • 改变状况的范畴行为:实体目标是答应调用者改动状况的,这样就产生了改变状况的范畴行为(办法名上不主张用set/get, 而是更具有事务意义的办法名,这样更具有范畴逻辑(加强))
    • 自给自足的范畴行为:目标只操作了自己的特色,而不依赖于外部特色。(如校验一份外卖的总金额、总数量 与外卖中各个单品的联系)
    • 互为协作的范畴行为:需求调用者供给必要的信息(一般经过办法参数传入),这样就形成了范畴目标之间互为协作的范畴行为。 增修正查。

2.3 构建实体的根据

在 DDD 规划中,咱们将开发者的视线从数据库移到了实体上,以往咱们在规划一个体系时,会重视要树立多少张表,而咱们在 DDD 中,则需求重视怎么树立实体,这两者的异同点在于:

  • 相同:在 mvc 的开发形式中,开发者经过阅览dao 层的表结构,就能了解到整个体系大致的架构与效果。相同的,在 DDD 中开发者经过阅览实体的特色,就能了解到整个体系大致的架构与效果。
  • 不同:在 DDD 中,一个实体的特色或许只由一张表组成,也或许由多张表组成,也或许是由mysql 和 redis 共同组成,在实体地点的范畴中,咱们并不关心它的底层(数据层)是怎么完成的,咱们只关心这个实体。

举个比方

关于一个学生信息办理体系而言,咱们规划了一个学生的实体。

type Student struct {
        ID     uint64
        Name   string
        Sex    string
        Class  string
        IsLate int
        Sign   *Sign
}
type Sign struct {
        SignTime time.Time
}
func (stu *Student) StudentSign() {
        isLate := TimeCheck()
        stu.IsLate = isLate
        // flush redis...
}

以上实体的结构能够简略概括为:

  1. 身份标识:ID
  2. 特色:Name、Sex、Class、IsLate、值目标(Sign)
  3. 范畴行为:Sign()

在咱们的数据库规划中,Student 的基础信息,或许只包括了ID、Name、Sex、Class 这四个字段,那IsLate 字段呢?咱们将学生 IsLate 特色写进缓存里,便利某些督查办理体系的高频查询,一起咱们经过 Sign()办法进行学生报到状况的改变,咱们在 Sign 办法中进行校验后,修正这个学生实体的 IsLate 特色。

补充:

值目标也是实体目标的特色之一,它没有身份标识,也不行改动。比方上面的报到,学生在今日报到之后,创立的报到记载,便是学生的值目标,这条记载创立了,就不行改动了(排除黑入教务体系篡改个人数据的状况)。值目标更多的信息,会在后边提到。

三、值目标

3.1 前语

值目标是实体的一个重要组成部分,怎么正确运用值目标,也是 DDD范畴驱动规划的一个难题。本文将介绍值目标的概念与运用办法。

3.2 概念

值目标是实体目标的特色,一般代表重量、性质、联系、场所、时间或方位/姿势。当实体特色需求表现出其特色的意义,并为这个意义供给相关功用,能够设置为值目标。比方一家公司地点的省/市/区/大街能够组成值目标表明这家公司的地址特色。

3.3 特色

  • 值目标不行变。建模优先考虑值目标,由于值目标没有身份表明的担负,本身不行变。值目标本身最多是不行变性。
  • 值目标具有的往往是“自给自足的范畴行为”。这些范畴行为能够让值目标的表现才能变得愈加丰厚,愈加智能。

3.4 范畴行为

那什么是值目标的范畴行为呢?

  • 自我验证:值目标自我组合,能减少实体类的验证。
  • 自我组合: 值目标会触及对数据值的运算,为了增强值目标的运算才能,能够在内部进行数据组合。如 金额的单位换算。
  • 自我运算: 依照事务规矩对特色值运算的行为。如经纬度核算。
// NewCoordinateVo 初始化坐标值目标
func NewCoordinateVo(LongitudeStr string, LatitudeStr string) (*VoCoordinate, error) {
  // 自我验证
  Longitude, err := strconv.ParseFloat(LongitudeStr, 64)
  if err != nil {
    return nil, fmt.Errorf("Longitude_input_err")
  }
  Latitude, err := strconv.ParseFloat(LatitudeStr, 64)
  if err != nil {
    return nil, fmt.Errorf("Latitude_input_err")
  }
  return &VoCoordinate{
    Longitude: Longitude,
    Latitude:  Latitude,
  }, nil
}

3.5 F&Q

1、比较于一般特色,值目标有哪些优势呢?

能够展现范畴概念;学生实体的年纪,string与Name、int与Age比较,明显后者愈加直观得表现了事务意义。能够封装清楚明了的范畴概念;比方关于一个经销商4s店店方位经度和纬度都是这个4s店实体实体店特色,可是组成一个坐标值目标更能展现实体店范畴概念。更好的封装利于自我范畴行为的验证才能。确保每次生成得值目标都是正确的。

2. 那么一个范畴的概念咱们用实体仍是值目标呢?能够根据几点来判别?

事务对它持平的判别是根据值仍是身份标识。前者是值目标,后者是实体。当咱们从图书馆判别一本书是否相同,即使名字相同也并非同一本书,在体系中,只要id相同才是同一本书;但咱们判别一个方位,当经纬度相同的时分便是同一个方位。这个时分图书便是界说为实体,坐标界说为值目标。

确认目标的特色值是否会发生改动,假如改动了,究竟是产生一个彻底不同的目标,仍是维持相同的身份标识。在职工的出勤记载事务场景中,根据持平性进行判别时,能够使命出勤记载值持平的便是同一条记载,但假如职工提出补卡,对记载状况修正对时分,其同一性就只能经过仅有的身份标识进行判别,这意味这应该被界说为实体。

生命周期是手动的。值目标没有身份标识,意味着无需办理其生命周期。可是实体无需重视。

多个判别条件是层层递进的,要确认一个范畴概念究竟是实体仍是值目标,需求谨慎判别,综合考量。

—— END——

推荐阅览

百度工程师带你玩转正则

Diffie-Hellman密钥洽谈算法探求

贴吧低代码高性能规矩引擎规划

浅谈权限体系在多利熊事务应用

分布式体系关键途径推迟分析实践

百度工程师教你玩转规划形式(装修器形式)