toby1024

toby1024 查看完整档案

杭州编辑中南大学  |  计算机科学与技术 编辑  |  填写所在公司/组织 toby1024.github.io/ 编辑
编辑

奔波~儿爸

个人动态

toby1024 发布了文章 · 2020-12-17

领域驱动实践二

  • 文章内容全部来自张逸《领域驱动设计实践(战略+战术)》

上文 领域驱动实践

演进后的分层架构代码模型

需求:创建订单,发送邮件通知,异步消息通知仓储系统。

image.jpeg

controller是北向网关,承担与用户界面的交互,同理,如果是dubbo,那么dubbo的api也可以视作是北向网关。

南向网关是系统访问外面资源用于支撑业务系统的适配。

  • 领域层:包含 PlaceOrderService、Order、Notification、OrderConfirmed 与抽象的 OrderRepository,封装了纯粹的业务逻辑,不掺杂任何与业务无关的技术实现。
  • 应用层:包含 OrderAppService 以及抽象的 EventBus 与 NotificationService,提供对外体现业务价值的统一接口,同时还包含了基础设施功能的抽象接口。
  • 基础设施层:包含 OrderMapper、RabbitEventBus 与 EmailSender,为业务实现提供对应的技术功能支撑,但真正的基础设施访问则委派给系统边界之外的外部框架或驱动器。

限界上下文与架构

认识限界上下文

限界上下文体现的是一个垂直的架构边界,主要针对后端架构层次的垂直切分。对基础设施,框架的技术选型属于架构设计的考量范围,但不属于限界上下文的代码模型,但这些基础设施,框架的选择仍要考虑他们与限界上下文代码模型的集成、构建与部署。

限界上下文之间应该严格谨守边界,防腐层(ACL)是抵御外部限界上下文变化的最佳场所。

image.png

外部资源访问的两种架构风格:

1:数据库共享架构

避免不同限界上下文关联表之间的外键约束。

应该根据领域逻辑知识去识别限界上下文,避免从数据库层面建模。

2:零共享架构

将两个限界上下文共享的外部资源彻底分离。

需要考虑通信的健壮性。

需要保证数据的一致性。

运维和监控的复杂度也会上升。

代码结构

image.png

看上面这个图,applicationService是可以不经过domainService直接访问repositories,这就说明了domainService这一层更多的职责应该是领域对象的业务编排,如果一个业务只是简单的持久化一个对象,可以不用经过domainService,这样代码更简洁,避免经过无谓的代码层次,降低可读性。

ordercontext  
    - gateways 
    - controllers                  北向网关 
        - OrderController 
        - messages                 dto 
            - CreateOrderRequest 
    - persistence                  南向网关(持久化实现) 
        - OrderMapper 
    - client                       南向网关(其他微服务/外部服务) 
        - NotificationClient 
    - mq                           mq实现 
        - RabbitEventBus 
    - application                      业务service 
        - OrderAppService 
    - interfaces                       基础设施,南向网关的抽象 
    - client 
        - NotificationService 
        - SendNotificationRequest 
    - mq 
        - EventBus 
    - domain                          领域服务 
        - PlaceOrderService 
        - Order 
        - OrderConfirmed 
        - Notification 
        - NotificationComposer 
    - repositories                    持久化的抽象(持久化也是南向网关的一种,但比较特殊,所以独立出来) 
        - OrderRepository

不同的上下文之间的通信需要进行隔离

image.png

架构风格分析

微服务架构风格

将限界上下文视为微服务。包括用户和外部系统在内的客户端需要通过api Gateway实现微服务的调用。

一个限界上下文未必一定要部署为一个微服务,如果是提供整个系统的公共基础功能,应该定义为公共组件。

微服务架构风格可以保证技术选择,发布节奏的自由,但也会有分布式系统的复杂度,数据一致性和运维部署的挑战。

事件驱动架构风格

将上下文协作模式抽象为发布/订阅事件。系统与外部系统之间需要引入消息中间件,以便于事件消息的传递

CQRS架构风格

在限界上下文层面将查询与命令分为两种不同的实现模式。系统暴露的 API 接口需要分解为命令接口和查询接口,接口类型不同,处理模式和执行方式都不相同。可以为 command 和 query 分别建立 module(领域驱动设计中的设计要素),使得它们的代码模型可以独自演化,毕竟命令和查询的领域模型是完全不同的。甚至可以为同一个领域的 command 和 query 各自建立专有的限界上下文。

BFF

Backends For Frontends,为前端提供的后端。可以看作是业务的聚合,用于承接ui和后端代码。

案例分析

前提:不要以一个码农的视角来看待DDD,不要以一个码农的视角来看项目。

如何主导一个项目的开发

1:清楚项目背景

2:先启阶段:确定系统范围,明确开发目标,预见开发风险,统一技术架构,制定迭代计划。

确定系统范围:包括哪些做,哪些不做;有哪些利益相关方参与。

明确开发目标:包括业务目标,系统性能目标等。

预见开发风险:包括人力资源风险,第三方支持风险,外部政策变化风险等。

统一技术架构:主要指技术架构选型,技术方案制定等。

制定迭代计划:需要有一个明确可执行的迭代计划。

先启阶段最好能有一个mvp计划,明确第一个正式提交版本内容。否则容易迷失在细枝末节的功能需求中。

识别问题域:核心子领域,通用子领域,支撑子领域

统一语言

编写有效用例:分为概要目标,用户目标和子功能image.png

识别参与者,参与者可能是人,也可能是系统、服务或模块。

通过参与者识别用例

使用事件风暴识别限界上下文,避免限界上下文边界过大(2PTs原则)

识别上下文映射(结合用例),避免循坏依赖,如果出现循坏依赖,可能是遗漏了限界上下文,或者上下文之间职责不清

下游调用上游

代码组织架构

image.png

1111.png

查看原文

赞 0 收藏 0 评论 0

toby1024 发布了文章 · 2020-11-30

领域驱动设计实践

  • 文章内容全部来自张逸《领域驱动设计实践(战略+战术)》

领域驱动是什么

领域驱动设计就是针对软件开发领域提出的一套系统与理论分析方法,是“一种思维方式,也是一组优先任务,它旨在加速那些必须处理复杂领域的软件项目的开发”。领域驱动设计贯穿了整个软件开发的生命周期,包括对需求的分析、建模、架构、设计,甚至最终的编码实现,乃至对编码的测试与重构。
领域驱动设计强调领域模型的重要性,并通过模型驱动设计来保障领域模型与程序设计的一致。从业务需求中提炼出统一语言(Ubiquitous Language),再基于统一语言建立领域模型;这个领域模型会指导着程序设计以及编码实现;最后,又通过重构来发现隐式概念,并运用设计模式改进设计与开发质量。
image

战略设计阶段

问题域方面

针对问题域,引入限界上下文(Bounded Context)和上下文映射(Context Map)对问题域进行合理的分解,识别出核心领域(Core Domain)与子领域(SubDomain),并确定领域的边界以及它们之间的关系,维持模型的完整性。

架构方面

通过分层架构来隔离关注点,尤其是将领域实现独立出来,能够更利于领域模型的单一性与稳定性;引入六边形架构可以清晰地表达领域与技术基础设施的边界;CQRS 模式则分离了查询场景和命令场景,针对不同场景选择使用同步或异步操作,来提高架构的低延迟性与高并发能力。

战术设计阶段

整个软件系统被分解为多个限界上下文(或领域)后,就可以分而治之,对每个限界上下文进行战术设计。
image

演进的领域驱动设计过程

image
战略设计会控制和分解战术设计的边界与粒度,战术设计则以实证角度验证领域模型的有效性、完整性与一致性,进而以演进的方式对之前的战略设计阶段进行迭代,从而形成一种螺旋式上升的迭代设计过程。

认识分层架构

经典分层架构

image
领域驱动设计分层架构
image

层次职责
用户界面/展现层负责向用户展现信息以及解释用户命令
应用层很薄的一层,用来协调应用的活动,它不包含业务逻辑,它不保留业务对象的状态,但它保有应用任务的进度状态
领域层本层包含关于领域的信息,这是业务软件的核心所在。在这里保留业务对象的状态,对业务对象和它们状态的持久化被委托给了基础设施层
基础设施层本层作为其他层的支撑库存在。它提供了层间的通信,实现对业务对象的持久化,包含对用户界面层的支撑库等作用

分层的依据与原则

分层的第一个依据是基于关注点为不同的调用目的划分层次。
分层的第二个依据是面对变化。
层与层之间的关系应该是正交的。
保证同一层的组件处于同一个抽象层次。

分层架构的演化

分层架构是一种架构模式,但终归它的目的是为了改进软件的架构质量,我们在运用分层架构时,必须要遵守架构设计的最高原则,即建立一个高内聚、松耦合的软件系统架构。

整洁架构

在架构设计时,我们应设计出干净的应用层和领域层,保持它们对业务逻辑的专注,而不掺杂任何具体的技术实现,从而完成领域与技术之间的完全隔离,这一思想被 Robert Martin 称之为整洁架构(Clean Architecture)。
image
领域模型就是业务逻辑的模型,它应该是完全纯粹的。
对下,例如,针对数据库、消息队列或硬件设备,可以认为是一个南向网关,对于当前限界上下文是一种输出的依赖;
对上,例如,针对 Web 和 UI,可以认为是一个北向网关,对于当前限界上下文是一种输入的依赖。

六边形架构

image
六边形架构通过内外两个六边形为系统建立了不同层次的边界。
image
微服务架构
image

查看原文

赞 0 收藏 0 评论 0

toby1024 发布了文章 · 2020-11-27

设计模式-创建型

image

单例模式

单例模式,顾名思义就是一个类只有一个实例。
单例主要的好处就是,1:可以解决资源访问冲突的问题。2:减少资源浪费。

单例的实现方式

1:饿汉式

在类加载的时候就实力化对象,不支持延迟加载。

public class HungryDemo {
    private AtomicInteger id = new AtomicInteger(0);
    public static HungryDemo instance = new HungryDemo();

    private HungryDemo() {
    }

    public static HungryDemo getInstance() {
        return instance;
    }

    public int getId() {
        return id.incrementAndGet();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(new Random().nextInt(10000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                HungryDemo instance = HungryDemo.getInstance();
                System.out.println(instance + "==>" + instance.getId());
            }).start();
        }
    }
}

2:懒汉式

支持延迟加载,在使用的时候才真正加载。

public class LazyDemo {
    private AtomicInteger id = new AtomicInteger(0);

    public static LazyDemo instance;

    private LazyDemo() {
    }

    public static synchronized LazyDemo getInstance() {
        if (Objects.isNull(instance)) {
            instance = new LazyDemo();
        }
        return instance;
    }

    public int getId() {
        return id.incrementAndGet();
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(new Random().nextInt(10000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                LazyDemo instance = LazyDemo.getInstance();
                System.out.println(instance + "==>" + instance.getId());
            }).start();
        }
    }
}

饿汉和懒汉的区别就是一个支持延迟加载,一个不支持。懒汉模式还要加锁,防止并发问题。从这一点来看,懒汉模式性能是不如饿汉模式,饿汉模式在类加载时就进行实力化,也可以提前将实力化过程中发生的资源不足等问题提前暴露,而不是等到业务访问后才发现无法进行初始化,引发线上事故。
但这两种都有各自的不足,所以现在有第三种方式。

3:双重检测

public class DoubleCheckDemo {
    private AtomicInteger id = new AtomicInteger(0);
    public static DoubleCheckDemo instance;

    private DoubleCheckDemo() {
    }

    public static DoubleCheckDemo getInstance() {
        if (Objects.isNull(instance)) {
            synchronized (DoubleCheckDemo.class) {
                System.out.println("加锁操作");
                if (Objects.isNull(instance)) {
                    instance = new DoubleCheckDemo();
                }
            }
        }
        return instance;
    }

    public int getId() {
        return id.incrementAndGet();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(new Random().nextInt(10000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                DoubleCheckDemo instance = DoubleCheckDemo.getInstance();
                System.out.println(instance + "==>" + instance.getId());
            }).start();
        }
    }
}

双重检测解决了懒汉模式的并发性能问题,同时支持懒加载。

4:静态内部类

静态内部类的实现比双重检测要更加简单,同时也能做到懒加载。

public class InnerClassDemo {

    private AtomicInteger id = new AtomicInteger(0);

    private InnerClassDemo() {
    }

    private static class InnerClassDemoHolder {
        private static final InnerClassDemo instance = new InnerClassDemo();
    }

    public static InnerClassDemo getInstance() {
        return InnerClassDemoHolder.instance;
    }

    public int getId() {
        return id.incrementAndGet();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(new Random().nextInt(10000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                InnerClassDemo instance = InnerClassDemo.getInstance();
                System.out.println(instance + "==>" + instance.getId());
            }).start();
        }
    }
}

InnerClassDemoHolder 是静态内部类,一开始并不会加载,等到调用getInstance()方法时才会被加载,这是一种无锁的实现,线程安全由jvm来保证。

5:枚举

采用枚举的方式来实现是最简单的方式。

public enum EnumDemo {
    INSTANCE;
    private AtomicInteger id = new AtomicInteger(0);

    public int getId() {
        return id.incrementAndGet();
    }
}
public class EnumDemoTest {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(new Random().nextInt(10000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                EnumDemo instance = EnumDemo.INSTANCE;
                System.out.println(instance + "==>" + instance.getId());
            }).start();
        }
    }
}

单例模式的问题

  • 单例对OOP特性的支持不友好,单例对继承,多态特性支持不友好,违背了面向抽象编程的思想。
  • 单例对代码的扩展性不好。
  • 单例对代码的测试性不好,单例往往硬编码进业务,后续mock不方便,需要在一开始就对单例进行包装。
  • 单例不支持有参数的构造函数,单例的设计是无状态的,就无法进行参数传递来构建实例。

单例的范围

如果需要在线程内实现单例,可以使用ThreadLocal并发工具类。

如果需要在集群环境下实现单例,可以借助外部共享存储,例如redis。当进行实例创建的时候,从外部存储加载对象,如果没有则创建后存储回去,这里需要分布式锁的接入,实现起来比较麻烦。

工厂模式

简单工厂

简单工厂可以看作是将对象的创建抽取出来独立的一种实现,这种实现方式比较简单,目的就是将创建对象的负责业务代码从业务中剥离出来,实现复用。
缺点是逻辑过于集中,不利于后续扩展。

工厂方法

工厂方法模式是简单工厂的进一步抽象。使用面向对象的多态性,保持了简单工厂的的优点。将不同的构建逻辑分开到不同的实现中,避免了修改单个逻辑影响所有的构建逻辑。

抽象工厂

抽象工厂提供一个抽象,而不是具体实现。符合面向抽象编程思想,增加工厂也不影响具体业务代码。

建造者模式

比较典型的实现是lombok的builder方法。

public class BuilderDemo {
    private String name;
    private int age;
    private String phone;
    private String email;

    private BuilderDemo(BuilderDemoBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.phone = builder.phone;
        this.email = builder.email;
    }

    public static class BuilderDemoBuilder {
        private String name;
        private int age;
        private String phone;
        private String email;

        public BuilderDemo build() {
            return new BuilderDemo(this);
        }

        public BuilderDemoBuilder setName(String name) {
            if (Objects.isNull(name)) {
                throw new IllegalArgumentException("name must not null");
            }
            this.name = name;
            return this;
        }

        public BuilderDemoBuilder setAge(int age) {
            this.age = age;
            return this;
        }

        public BuilderDemoBuilder setPhone(String phone) {
            if (Objects.isNull(name)) {
                throw new IllegalArgumentException("phone must not null");
            }
            this.phone = phone;
            return this;
        }

        public BuilderDemoBuilder setEmail(String email) {
            this.email = email;
            return this;
        }
    }

    public static void main(String[] args) {
        BuilderDemo build = new BuilderDemoBuilder().setAge(18).setEmail("email").setName("name").setPhone("13812340987").build();
        System.out.println(build.name);
    }
}

原型模式

基于已有对象原型创建对象。

实现方式分为深拷贝和浅拷贝。深拷贝需要递归负责,创建一个完全独立的对象。浅拷贝只是拷贝引用地址,适合不会变的对象。

查看原文

赞 0 收藏 0 评论 0

toby1024 发布了文章 · 2020-11-25

设计模式-行为篇

image

状态模式

有限状态机

1:状态转换较多,但每个状态的转换业务不复杂的,推荐使用查表法。
通过二维数组等方式确定下一个转换的状态,并处理对应业务
2:状态转换业务较复杂的,推荐使用状态模式,使用单独的类来定义状态和状态业务
3:业务非常简单,状态也很少,直接使用if else就可以实现,不需要过度设计

迭代器模式

通过模拟游标的滑动来遍历集合中的数据
迭代器需要实现 hasNext,currentItem,next三个方法,用于滑动游标和判断是否迭代结束
遍历过程中不支持元素的添加和删除操作,因为会引起未决结果

访问者模式

一个或多个操作应用到一组对象上,设计的意图是解耦操作和对象本身,保持类的职责单一、满足开闭原则已应对代码的复杂性。
推荐使用策略模式替代访问者模式。1:访问者模式使用重载实现,容易让访问者代码爆炸。2:不巧妙,不灵活,将一组操作封装在一起,增加功能时需要修改的代码太多。
支持Double Dispatch的语言不需要访问者模式,直接动态执行就可以了

备忘录模式

1:备份以便于恢复数据
2:不能破坏封装原则
3:低频率全量备份结合高频率增量备份(redis RDF和AOF)

命令模式

1:将行为封装成对象进行传递
2:和策略模式的区别在于,每个命令执行的是不同的业务;策略模式指同一个业务的不同实现。
例如:控制电灯的开,关属于命令;是交流电还是直流电给电灯供电属于策略。

解释器模式

典型的场景是编译器的语法解释。
解释器是针对特定的语句进行解释,调用特定的业务代码进行执行的过程。

中介模式

1:接口对象之间的交互关系,将多对多关系通过中介类转换为一对多。
2:中介模式和观察者模式的区别在于,观察者关系是单向固定的,中介则可以是双向的。
3:中介模式接收消息后进行业务编排调度。
4:副作用:可能会产生一个大而全的上帝类,包含类所有的业务代码。(是否可以用命令模式进行拆分?)

查看原文

赞 0 收藏 0 评论 0

toby1024 发布了文章 · 2020-11-25

mysql_covering_index

index

索引用于快速查找具有特定列值的行,如果没有索引,mysql必须从第一行开始,扫描全表找到对应的行。表越大,花费越多。如果表中有相关的索引,mysql可以快速确定要在数据文件中查找的位置。大多数mysql索引(primary key,unique,index和fulltext)存储在B-trees。空间数据类型的索引使用R-trees;MEMORY表还支持hash indexes;InnoDB对fulltext使用倒排表。

MySQL使用索引进行以下操作:

  • 通过where条件快速查询。
  • 最优匹配。如果可以在多个索引之间进行选择,则MySQL通常会使用查找最小行数的索引。
  • 如果表具有多列索引,那么优化器可以使用索引的任何最左前缀来查找行。举例来说,如果你有一个三列的索引 (col1, col2, col3),你可以索引的搜索有(col1), (col1, col2)以及(col1, col2, col3)。
  • 执行联接时从其他表中检索行。如果声明相同的类型和大小,MySQL可以更有效地在列上使用索引。在这种情况下, VARCHAR与 CHAR被认为是相同的,如果它们被声明为相同的大小。例如, VARCHAR(10)和 CHAR(10)是相同的大小,但是 VARCHAR(10)和 CHAR(15)不是。

    对于非二进制字符串列之间的比较,两个列应使用相同的字符集,不同的字符集将导致索引失效。例如,将utf8列与latin1列进行比较会排除使用索引。

    如果不能不通过转换直接比较值,则比较不同的列(例如,将字符串列与时间或数字列进行比较)可能会阻止使用索引。对于给定的值,如数值列的值为1,它可能比较等于在字符串列,例如任何数量的值 '1',' 1', '00001',或'01.e1',导致索引失效。
  • 在索引列使用MIN()或 MAX()。mysql预处理器将进行优化,预处理器会检查您是否正在索引出现的所有关键部分上使用。在这种情况下,MySQL为每个表达式执行一次键查找,并将其替换为常量,所有表达式都用常量替换完成后,查询将立即返回。
  • 排序或分组查询(order by, group by)使用最左匹配索引(order by key1, key2);如果倒序排序(order by key1, key2 desc),将按相反顺序使用索引key。
  • 某些情况下,mysql会直接从索引中获取数据,而不用查询表;例如:只查询索引列数据。注意:当开启列长事务时,可能导致该优化失效,回表查询。
  • 当全表扫描快于走索引查询时,mysql也不会走索引。

covering index

查询的所有列都包含在索引中时,mysql不会扫描表(即不会回表查询),这种情况mysql定义为covering index;InnoDb引擎下,开启事务时,将不会使用这种优化查询。

参考资料

8.5.2 Optimizing InnoDB Transaction Management

查看原文

赞 0 收藏 0 评论 0

toby1024 发布了文章 · 2020-11-25

领域驱动-事件风暴

佛,在信徒眼里是佛,是希望;在工艺品厂里,对于工人来说,就是一个活,是工作对象,是收入的来源;对于物流公司来说,是货,是责任担当,是运输的标的。
不同的事件主题关注的业务事件是不同,领域模型也是不同的。
在不同的领域模型中,统一语言。

事件风暴

事件风暴是一种快速探索复杂业务领域和对领域建模的实践。
事件风暴从领域关注的业务事件出发,经过团队的充分讨论,统一语言,最终找到领域模型。

如何确定领域关注的业务事件

在通用语言中存在“如果A发生,我们就需要做到B。”,这样的表述,那么A就可以定义成为一个领域事件。
领域事件的命名一般采用 “产生事件的对象名称+完成动作的过去形式” 的形式,这有点类似用户故事的描述。其实用户故事就可以看作是一个领域事件,只是用户故事转换成业务事件时,需要根据业务领域统一语言。

如何开展事件风暴

大部分的资料都是站在全局的高度去做事件风暴,将整个系统一起进行事件拆分,划分领域模型。
这样做当然没错,但是实际开发过程中,我们往往1:缺少领域专家;2:缺少足够是时间来做领域分析,事件风暴。这往往导致事件风暴成为理论,而缺少实践。
从我个人观点来看,架构是不断演进的,业务一直在变化,代码也一直在修改,那么,我们就可以从业务点出发,从一个需求出发,做事件风暴。

准备工作

必不可少的便利贴,开放的空间,大白板。
全员参与,包括业务,产品,开发,测试,UI。

核心概念

事件风暴将系统拆分为不同的元素,用不同颜色的便利贴表示。
image

统一语言

统一语言非常重要,是沟通的起点,如果一个业务内,包括业务方,产品,开发之间对于概念的表述不统一,会造成沟通不顺畅,甚至出现南辕北辙的现象。由于前期我们是从单个需求出发,可能统一语言定义出来的概念并不准确,或者在命名上有争议,不必介意,统一语言除了准确描述业务对象以外,更主要的功能是上下文的沟通和传递,只要上下文是统一的,业务就可以顺利开展,代码也可以准确编写。如果后续其他需求增加变更,发现之前定义的名称不准确,概念上修改过来就可以了。

事件风暴过程

image

识别领域事件

事件风暴以识别领域事件开始。书写领域事件的规则是使用被动语态,按照事件发展顺序贴在白板上。
遇到有争议的事件,不必过多纠结,先标记成热点事件,后续可以重点讨论。
事件一般由名次和动词组合而成,例如:订单已创建;地址已填写。

注意:用户的前端操作不是事件,例如:用户提交订单,用户提交表单;这些只是为事件提供数据。

识别参与者

事件一共有四种参与者:
image

- 角色:触发事件的人
- 策略:触发事件的规则
- 外部系统
- 事件:即当前事件的前置事件
注意:策略是规则,但规则不是策略。策略是规则+定时器的组合。策略会触发事件,但规则不会。

识别限界上下文

从两个方向识别限界上下文:

  • 纵向:识别事件流中的事件,倘若相邻两个事件的关系较弱,或者体现了两个非常明显的阶段,就可以对其进行分割。
  • 横向:梳理是有的事件,根据组成事件的名词和动词去发现事件之间的相关性(相同、相似的名词),然后去提炼一个整体的概念。

限界上下文包含场景,角色,活动,知识和能力,不包含UI部分。
限界上下文可以由不连续的事件组成。
限界上下文在命名的时候使用名词来定义。

识别限界上下文遵循的原则
  • 单一抽象层次原则

每个限界上下文从概念上应该尽量处于统一抽象层次,不能嵌套。
image

  • 正交原则

限界上下文之间不能互相影响,互相包含。
{% img /images/ddd/event4.jpg %}

识别上下文映射

通过事件风暴:

  • 首先识别跨界限界上下文之间相邻事件的关系。
  • 事件之间是否存在直接触发的关系(参与者为前置事件),需要确定这两个事件所述的限界上下文。
  • 判断这两个事件所属的限界上下文,谁是主要的。主要的上下文就是下游。通常,前置事件为下游,或是事件的发布者。
下游调用上游。
事件依赖关系为单向依赖.
避免下游使用上游的的领域模型(尊奉者模式),由上游来定义参数上和返回值,下游根据情况来决定是否需要定义防腐层。
一般来说,事件如果由自己的角色参与者(角色,策略,外部系统),就与前置事件脱离来关系。

领域分析建模

一个事件只能有一个写模型,如果出现多个写模型,要么就是这几个写模型存在包含关系,要么就是写模型遗漏了对应的事件。
对于读模型,要注意它属于那个限界上下文,如果不是当前上下文,则:

定义自己的读模型,通过防腐层进行转换,尽量不要迎合下游
使用ID值对象(用于建立关联)(基本类型偏执)
读模型和写模型就是领域模型对象

image

识别聚合
  • 针对领域分享模型,梳理模型对象之间的关系(继承,合成,聚合,依赖,无关系)
  • 确定领域模型对象是实体还是值对象
  • 将具有继承或合成关系的领域模型对象放在一个聚合边界内
  • 根据聚合的本质(概念完整性,概念独立性,不变量Invariant,事务一致性)梳理聚合

代码实现

角色构造型

image
DomainService来协调单个领域模型/值对象无法完成的业务功能,主要是数据持久化,外部接口调用获取数据等
AppService则负责业务编排
Factory负责封装复杂的创建逻辑,用于创建领域对象

查看原文

赞 0 收藏 0 评论 0

toby1024 发布了文章 · 2020-11-25

领域驱动实践-从需求到代码

业务需求

网约车出行项目mvp

  • 作为乘客我希望创建⼀个出⾏订单,以便于从A地前往B地
  • 作为司机我希望履⾏⼀个订单,以便于获取收⼊
  • 作为运营我希望能取消订单,以便于乘客联系不上司机时重新下单

传统mvc模式

传统mvc往往基于数据模型进行开发,通过需求分析,确定数据模型,然后在数据模型上做CRUD开发

image
server类中聚集类所有的业务代码

image

所有的操作都是在操作数据,当业务变得越来越复杂时,service中的代码越来越臃肿,然后根据业务进行模块拆分,但是由于业务纵横交错,后续修改业务代码时,可能会需要修改多个模块。

微服务开发

微服务的出现一部分原因就是希望将业务划分清楚,解决模块耦合的问题,借助领域驱动设计,我们可以通过一些方法论来进行业务建模和微服务划分。

统一语言

针对不同的角色,同一个事务可能有不同定义。

  • 对于乘客来说,出行订单应该是一个行程。乘客关心的是起点,终点,司机的实时位置,需要支出的费用。
  • 对于设计来说,出行订单是一笔生意。司机关心的是乘客的位置,目的地,该笔行程的收入、奖励。
  • 对于运营人员来说,出行订单是一个合约,合约的双方是乘客和司机,运营人员关注合约的履约情况,合约的抽成信息等。

针对不同的参与角色,我们定义不同的模型概念。
image

通过对业务进行限界上下文划分,很容易就可以进行代码的隔离,不同的上下文分开进行编码,上下文之间的业务调用通过api接口方式进行交互,这样后续的业务演进,系统部署升级以及扩容都相对独立。

但是一个userstor就划分一个微服务肯定是不现实的,我们需要根据业务的相关性来进行,从两个方向来进行组织限界上下文。

语义相关性

不同的用例存在语义相关性就可以考虑放在一个限界上线文内。例如创建行程,取消行程都跟行程有关,就适合放在一个限界上下文来处理。

功能相关性

有些用例虽然都是操作相同的对象,但是在功能上有相互的独立性,应该考虑分割成独立的上下文。例如支付行程,虽然也是在操作行程对象,但其实更侧重于支付动作,后续的业务扩展也多围绕在支付上,如增加支付渠道,增加租金统计等和行程关联不大。

DEMO

代码参考:ddd-demo
image

查看原文

赞 0 收藏 0 评论 0

toby1024 发布了文章 · 2020-11-24

面向对象编程

面向对象

image

多用组合少用继承

继承过多过深影响代码可读性,提高维护成本

设计原则与思想

image

单一职责原则 Single Responsibility Principle

如何理解单一职责

一个类(方法)只完成一件事

不要设计大而全的类(接口)

高内聚低耦合

如何判断是否职责单一

类或方法无法准确命名

依赖过多外部资源

代码行数过多,类的属性或者函数过多

类的方法都在集中操作某几个属性

拆分粒度是否越细越好?

考虑功能的相关性,避免低耦合过度失去高内聚,提高维护成本

开闭原则 Open/Closed Principle

对扩展开放,对修改关闭

合理设计,不要过度设计

职责单一,避免相互影响,保证稳定性

DRY

重复主要指:代码重复,语义重复,执行重复

代码重复指的是同样的代码出现在多个地方,这种情况可以通过抽取方法,工具类的方式解决

语义重复指的是相同的功能采用了不同的实现方法,出现在多个地方,这种情况可以通过抽取工具类的方法解决

执行重复指的业务上下文执行了同样的逻辑,一般是参数校验,格式化等,需要从业务角度梳理,去除不必要的重复执行

复用需要考虑扩展性,避免和业务耦合,保证代码的稳定性

规范与重构

image

重构是一个持续的过程,重构不光是代码级别的重构,也针对业务架构等

重构的前提是单元测试要覆盖到位

如何评价代码质量的高低

代码质量高低是通过综合各种因素得到的结论,常见的描述有可读性,可维护性,简洁性,灵活性等,对应的一般都是面向对象的几个特性。所以好的代码应该是面向对象的。

如何写出高质量的代码

需要掌握面向对象的思想,做好设计再写代码,在编码过程中灵活使用设计模式,遵循编码规范,不断的重构完善代码。
image

查看原文

赞 0 收藏 0 评论 0

toby1024 关注了专栏 · 2020-11-05

SegmentFault 行业快讯

第一时间为开发者提供行业相关的实时热点资讯

关注 54524

toby1024 关注了用户 · 2020-11-05

沉默王二 @itwanger

《Web全栈开发进阶之路》作者
公众号:沉默王二
微信:qing_geee
专注于有趣的Java技术,有益的程序人生

关注 6246

认证与成就

  • 获得 0 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-11-05
个人主页被 329 人浏览