专栏首页斑斓事件风暴过程全体验-下篇

事件风暴过程全体验-下篇

ThoughtWorks 钱平

作为TW技术咨询师,为多家企业进行架构和 Fintech 创新相关技术咨询,如架构设计、遗留系统上云迁移及规划、各种技术赋能、企业技术相关平台的生态规划及落地建设,创新实验室的技术部分筹建等等。此前,10多年的投行研发经验,包括外汇交易等核心系统,涉猎从架构、开发、Scrum Master、运维等多个角色。个人创业经验经历涉及互联网、金融和教育行业,并在广州多所重点大中院校担任创客导师。

在上一篇文章中,以英雄联盟自动预警系统为例子,展开了事件风暴获得了上下文,这里就继续延伸,看看事件风暴的产物还可以怎么进一步落到开发代码上面。

这里我们聚焦案件记录上下文,假设它独立为一个服务存在。然后我们可以把它对应的决策/命令搬过来一起看:

Step1. 设计API

结合命令风暴的结果我们发现有一部分的决策是外部触发的(框起来的部分),那这部分我们就应该为它们设计对应的API

在api/rest/CaseController.java:

@RestController
@RequestMapping("/case")
public class CaseController {
    @PostMapping
    public void createCase(@RequestBody String caseBrief) {}

    @PostMapping("/{caseId}/close")
    public void closeCase(@PathVariable int caseId) {}

    @PostMapping("/{caseId}/esculate")
    public void esculateCase(@PathVariable int caseId) {}

    @PostMapping("/{caseId}/thief")
    public void updateThiefInfo(@PathVariable int caseId,
                                @RequestBody String thiefInfo) {}

    @PostMapping("/{caseId}/fight-plan")
    public void addFlightPlan(@PathVariable int caseId,
                              @RequestBody FightPlanDTO fightPlanDTO) {}

    @PostMapping("/{caseId}/fight-plan/{planId}/success")
    public void markSuccessFightPlan(@PathVariable int caseId,
                                     @PathVariable int planId) {}

    @PostMapping("/{caseId}/fight-plan/{planId}/fail")
    public void markFailFightPlan(@PathVariable int caseId,
                                  @PathVariable int planId) {}
}

这里大体上遵循了REST的规则,充分使用URL指定所需的资源。当然,比如像“关闭”案件记录“应该用POST还是DELETE这些细节地方也是可以进一步斟酌的。

Step2. 构建领域对象

把聚合根对应的实体和值对象,以及后面补充的一些关键属性补充起来,暂时它还是一个贫血的POJO:

public class CaseReport {
    int id;
    Timestamp startTime;
    Timestamp closeTime;
    String caseBrief;
    String thief;
    List<FightPlan> fightPlanList;
}

public class FightPlan {
    int id;
    String status;
    String planDetail;
    int meetingId;
}

Step3. 构建应用服务和领域服务

这里以系统收到升级预警通知的步骤为例子:

场景与步骤:收到升级预警通知 -> 触发升级预警

  • 校验所升级预警的案件存在
  • 创建线上会议
  • 把会议登陆URL发送通知给其他英雄

如果按我自己原来的写法,我会把这个逻辑写在应用服务层ApplService里面。但是刚巧在DDD跟张逸老师协作了他的一个场景驱动工作坊,所以这里尝试用他的方式去规划一下。

场景放在App Service:

@Service
public class CaseReportService {
    @Autowired
    CaseReportService caseReportService;

    @Transactional
    public void esculate(int caseId) {
        caseReportService.esculate(caseId);
    }
}

步骤放在DomainService:

@Service
public class CaseReportService {
    @Autowired
    CaseReportRepository caseReportRepository;

    @Autowired
    FightPlanRepository fightPlanRepository;

    @Autowired
    MeetingGateway meetingGateway;

    @Autowired
    NotificationGateway notificationGateway;

     public void esculate(int caseId) {
        final CaseReport caseReport = caseReportRepository.getCaseReportById(caseId);

        if (caseReport != null) {
            final MeetingInfo meeting = meetingGateway.createMeeting(caseReport);
            notificationGateway.sendEsculationMsg(meeting);
        } else {
            throw new BizException(INVALID_CASEREPORT_ID);
        }
    }
}

碰巧这里三个步骤都是调用底层服务,所以都是调用了gateway/repository去进行,如果当中有一些是不依赖与底层的动作,则应该充血地放到DomainObject(CaseReport)里面去。

至此,一个分层架构大概出来了,并且已经完成了业务domain的开发以及API的入口:

分层架构

最后,聚合细化的信息其实也是可以对应给DB设计指引的:

  • 1-1的场景下,可以合在一个表,可以两个表
  • 1-n的场景下,通常两个表,也可以三个表
  • n-n的场景下,通常三个表

对应我们的例子,可以这样来设计表结构:

CREATE TABLE case_report_tbl (
  id int NOT NULL AUTO_INCREMENT,
  case_brief varchar(255) NOT NULL,
  start_time TIMESTAMP NOT NULL,
  close_time TIMESTAMP NOT NULL,
  thief varchar(255),
  PRIMARY KEY (ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE fight_plan_tbl (
  id int NOT NULL AUTO_INCREMENT,
  status varchar(10) NOT NULL,
  plan_detail varchar(255) DEFAULT NULL,
  meeting_id int NOT NULL,
  case_id int NOT NULL,
  PRIMARY KEY (ID),
  FOREIGN KEY (case_id) REFERENCES case_report_tbl(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

当然,现在也有很多是推荐不用FOREIGN KEY的设计手法,开放式无约束地建表,并把校验的逻辑都回归到代码里面并使用自动化测试去保证这个必须的逻辑校验,所以这里也只是一个指引,大家可以按需谋划。

到此,事件风暴对应下来,唯一还未补的缺应该就剩下底层所需的技术支撑了。比如Rest调用三方服务,或者是DB相关的SQL。这个应该是easy job就不继续写了哈 ^_^

本文分享自微信公众号 - 逸言(YiYan_OneWord),作者:钱平

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-12-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 基于Scala Trait的设计模式

    在《作为Scala语法糖的设计模式》中,我重点介绍了那些已经融入Scala语法的设计模式。今天要介绍的两个设计模式,则主要与Scala的trait有关。 Dec...

    张逸
  • 可视化与领域驱动设计

    从DDD的角度,领域逻辑的分析可以运用战略方法Bounded Context。可是,一个问题是:如何获得Bounded Context ? 我查看了许多关于Bo...

    张逸
  • Martin Odersky访谈录所思

    ThoughtWorks的「TW洞见」在4月发布了对Scala之父Martin Odersky的访谈。Odersky的回答显得言简意赅,仔细分析,仍然能从中收获...

    张逸
  • Android自定义GLSurfaceView

    当我们需要把同一个场景渲染到不同的Surface上时,此时系统GLSurfaceView 就不能满足需求了,所以我们需要自己创建EGL环境来实现渲染操作。 注意...

    曾大稳
  • Android 深入(一)- 自定义View之BottomTitleImageView

    1)从图中我们View的整个生命周期,主要由三部分操作完成分别是measure()、layout()、draw(),作用如下:

    热心的程序员
  • .Net多线程编程—同步机制

    1.简介 新的轻量级同步原语:Barrier,CountdownEvent,ManualResetEventSlim,SemaphoreSlim,SpinLoc...

    甜橙很酸
  • 优雅地实现RecyclerView的上拉加载

    这篇博客是承接上一篇博客--探索Android架构的DataLayer层(DataManager方式)具体实现,其实是上篇博客的一个使用比较普遍的例子,当然如果...

    1025645
  • 2017了,回家前 "年末" 分享:下雨,飘雪,红包雨,碰撞球,自定义View

    (本博客为原创:https://cloud.tencent.com/developer/user/1148436/activities) 目录:   效果展示 ...

    林冠宏-指尖下的幽灵
  • Java设计模式(七)代理模式

    学代理模式(静态代理,下面所属都指的是静态代理)的时候,敲着敲着代码,觉得怎么感觉像在写装饰器模式一样。所以在叙述代理模式的时候,我会用自己的理解去区分两种模式...

    每天学Java
  • 用C++跟你聊聊“代理模式”

    弄这个模式的时候啊,这心里多多少少还是有点蓝瘦。以前不懂事儿,看上哪个姑娘,自己还不敢说,总是让身边的朋友来“代理”。 反正最后也没成。

    看、未来

扫码关注云+社区

领取腾讯云代金券