DDD架构简介及理解
1.cola架构图解(Clean Object-oriented & Layered Architecture)
1.1适配层(Adapter Layer):
负责对前端展示(web,wireless,wap)的路由和适配,对于传统B/S系统而言,adapter就相称于MVC中的controller;
1.2应用层(Application Layer):
主要负责获取输入,组装上下文,参数校验,调用领域层做业务处理,假如需要的话,发送消息通知等。条理是开放的,应用层也可以绕过领域层,直接访问基础实施层;
1.3领域层(Domain Layer):
主要是封装了焦点业务逻辑,并通过领域服务(Domain Service)和领域对象(Domain Entity)的方法对App层提供业务实体和业务逻辑盘算。领域是应用的焦点,不依靠任何其他条理;
1.4基础实施层(Infrastructure Layer):
主要负责技能细节问题的处理,好比数据库的CRUD、搜索引擎、文件系统、分布式服务的RPC等。此外,领域防腐的重任也落在这里,外部依靠需要通过gateway的转义处理,才气被上面的App层和Domain层使用。
1.5客户端层(Client Layer):
统一对外提供服务接口,微服务调用等
2.领域模型(Domain model)
领域反映到代码里就是模型,模型是对领域某个方面的抽象,并且可以用来解决相关域的问题,
Domain Model 的基础单元,分为实体和值对象两种。实体和值对象,二者是领域模型中非常紧张的基础领域对象(Domain Object,DO)。
2.1实体对象 (Entities):
有唯一标志的焦点领域对象(有ID,通过ID识别是否为同一个对象),且这个标志在整个软件生命周期中都不会发生变化。可类比和数据库打交道的Entity实体,差异的是DDD中这些实体会包含与该实体相关的业务逻辑,它是操作行为的载体。也就是说DO包含了业务字段和业务方法,要求强内聚且低耦合,实体的充血模型不包含持久化逻辑
2.2值对象( Value Object):
依附于实体存在,通过对象属性来识别的对象,它将一些相关的实体属性打包在一起处理,形成一个新的对象。不关心唯一性(无ID,通过全字段的equals方法识别是否为同一对象,不提供set方法,若更新直接替换整体对象),具有校验逻辑、等值判断逻辑,只关心值的类,强调内聚
2.3聚合( aggregate):
组织复杂的业务逻辑,多个实体和值对象一起协同工作,这个协同的组织就是聚合。聚合是数据修改和持久化的基本单元,同一个聚合内要包管变乱的划一性,所以在计划的时候要包管聚合的计划拆分到最小化以包管效率和性能。每个聚合内有一个聚合根,多个实体、值对象和领域服务等领域对象。聚合是领域对象的显式分组,我们把一些关联性极强、生命周期划一的实体、值对象放到一个聚合里。
聚合有两个焦点要素:
这个界限 根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的界限是松耦合的。根实体对象有组成聚合所有对象的引用,但是外部对象只能引用根对象实体。只有聚合根才气使用仓储库直接查询,其它的只能通过相关的聚合访问。假如根实体被删除,聚合内部的其它对象也将被删除。聚合支持了领域模型的行为和不变性,同时充当划一性和变乱性界限。聚合在领域模型里是一个逻辑界限,它自己没有业务逻辑实现相关的代码。聚合的业务逻辑是由聚合内的聚合根、实体、值对象和领域服务等来实现的。跨多个实体的领域逻辑通过领域服务来实现。好比,有的业务场景需要同一个聚合的A和B两个实体来共同完成,我们就可以将这段业务逻辑用领域服务组合A和B两个实体来完成。聚合根的作用是包管内部的实体的划一性,对外只需要对聚合根进行操作。
聚合表达了对象的关联关系,比方一个网购订单Order至少包含了客户信息和一个或多个订单项,那么这个聚合就可以进行如下建模:
- //Order为聚合根
- public class Order {
- private String orderId;
- //OrderItem为 实体 订单项
- private List<OrderItem> items;
- private Customer customer;
- public Order(String orderId, Customer customer) {
- this.orderId = orderId;
- this.customer = customer;
- this.items = new ArrayList<>();
- }
- public void addItem(OrderItem item) {
- if (item == null) {
- throw new IllegalArgumentException("Order item cannot be null");
- }
- this.items.add(item);
- }
- public void removeItem(OrderItem item) {
- this.items.remove(item);
- }
- public double getTotalAmount() {
- return items.stream().mapToDouble(OrderItem::getAmount).sum();
- }
- // Getters and setters
- }
复制代码 3.服务(Service)
服务提供的操作是它提供给使用它的客户端,并突出领域对象的关系。(服务的目标是向上层提供接口)
所有的service只负责和谐并委派业务逻辑给领域对象进行处理,其自己并未真正实现业务逻辑,绝大部门的业务逻辑都由领域对象承载和实现了。细分为领域服务和应用服务。
3.1领域服务(Domain Service):
领域中的一些概念,假如是*名词,适合建模为对象的一般归类到实体对象或值对象。假如是动词*,好比一些操作、一些动作,代表的是一种行为,假如是和实体或值对象密切相关的,也可以合并到某个实体或者值对象中。但是,有些操作不属于实体或者值对象自己,或会涉及到多个领域对象,并且需要和谐这些领域对象共同完成这个操作或动作,这时就需要创建领域服务来提供这些操作。简单理解:就是跨多个领域对象的业务方法
当一些逻辑不属于某个实体时,可以把这些逻辑单独拿出来放到领域服务中。可以使用领域服务的情况:
- 执行一个显著的业务操作,包含了Repository层操作(数据库)
- 对领域对象进行转换
- 以多个领域对象作为输入参数进行盘算,结果产生一个值对象
3.2应用服务(Application Service):
应用层通过应用服务接口来暴露系统的全部功能。在应用服务的实现中,它负责编排和转发,它将要实现的功能委托给一个或多个领域对象来实现,它自己只负责处理业务用例的执行顺序以及结果的拼装。应用层相对来说是较“薄”的一层,除了定义应用服务之外,在该层我们可以进行安全认证,权限校验,持久化变乱控制,或者向其他系统发生基于变乱的消息通知,别的还可以用于创建邮件以发送给客户等。
领域服务和应用服务的差异:
- 领域服务和应用服务是差异的,领域服务是领域模型的一部门,用来处理业务逻辑,而应用服务不是。
- 应用服务是领域服务的直接客户,负责处理变乱、安全等操作,它将领域模型变成对外界可用的软件系统。
- 跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。
跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务流程通过应用服务来实现。
- 好比有的业务场景需要同一个聚合的 A 和 B 两个实体来共同完成,我们就可以将这段业务逻辑用领域服务来实现;
- 而有的业务场景需要聚合 C 和聚合 D 中的两个服务共同完成,这时你就可以用应用服务来组合这两个服务。
要点:Application Service 是业务流程的封装,不处理业务逻辑,怎样判断一段代码到底是业务流程照旧逻辑:
(1)不要有if/else分支逻辑
通常情况下,假如有分支逻辑的,都代表一些业务判断,那么,应该将逻辑封装到DomainService或者Entity里。但并非绝对。类似停止条件判断则不属于次
- boolean withholdSuccess = inventoryService.withhold(cmd.getItemId(), cmd.getQuantity());
- if (!withholdSuccess) {
- throw new IllegalArgumentException("Inventory not enough");
- }
复制代码 (2)不要有任何盘算
将所有与业务字段相关的加减乘除等盘算逻辑封装到实体里
(3)一些数据的转化可以交给其他对象来做
好比DTO Assembler,将对象间转化的逻辑抽取和剥离在单独的类中,降低ApplicationService的复杂度。使用mapstruct框架接口
一般ApplicationService的常见职能如下:
- 准备数据:包括从外部服务或持久化源取出相对应的Entity、VO以及外部服务返回的DTO。
- 执行操作:包括新对象的创建、赋值,以及调用领域对象的方法对其进行操作。需要留意的是这个时候通常都是纯内存操作,非持久化。
- 持久化:将操作结果持久化,或操作外部系统产生相应的影响,包括发消息等异步操作。
4.服务仓储(Repository)
依靠倒置原则:Repository的接口是在Domain层,但是实现类是在Infrastructure层,Infrastructure层作为南向网关向上实现领域接口,向下对接基础办法功能,剥离领域依靠耦合,领域防腐层。
防腐层(Anti-Corruption),简单说,就是应用不要直接依靠外域的信息,要把外域的信息转换成自己领域上下文(Context)的实体再去使用,从而实现本域和外部依靠的解耦。
在该架构中,我们把AC这个概念进行了泛化,将数据库、搜索引擎等数据存储都列为外部依靠的范畴。利用依靠倒置,统一使用gateway来实现业务领域和外部依靠的解耦。
5.领域变乱(Domain events)
领域变乱属于领域层的领域模型对象,由限界上下文中的聚合发布,感兴趣的聚合(同一限界上下文/差异限界上下文)可以进行消费。而当一个变乱由应用层发布,则该变乱为应用变乱。
领域变乱的引入主要是为了更有效地追踪实体状态的改变,并且在状态改变时,通过变乱消息的转达来实现领域模型对象之间的协同工作。变乱定名有一定的规范:**名称 + 动词已往式 + event(ContextRefreshedEvent)**每个领域变乱都有一个时间戳,表现变乱发生的时间,领域变乱可以选择持久化到数据库中。通过变乱机制,差异的服务或模块之间可以实现低耦合的通讯,促进系统的可扩展性和维护性保持系统灵活性。
6.方法定名
对于简单的crud方法,在每个分层中有统一的规范定名。
方法名称,对应条理adapter层app层repo接口和infr层mapper层查询方法getxxxsearchxxxfindxxxselectxxx删除方法removexxxerasexxxpurgexxxdeletexxx新增方法addxxxcreatexxxsavexxxinsertxxx更新方法editxxxmodifyxxxchangexxxupdatexxx 此中由于domain层承担的是业务焦点逻辑,而非普通crud,所以不存在改束缚。特别的一点是,对于所有的分页获取数据的方法,统一定名为pageListXxx。示例controller基础接口如下:
- public interface BaseController<T> {
- /**
- * 分页获取数据
- *
- * @param t
- * @return
- */
- XquantResponse<PageDTO<T>> pageList(T t);
- /**
- * 单条查询
- * 查询方法命名 adapter层 getxxx, app层 searchxxx,(domain层 query)domain层承担的是业务核心逻辑,而非普通crud,repository接口和infrastructure层 findxxx,mapper层 selectxxx
- *
- * @param id 主键值
- * @return
- */
- XquantResponse<T> getById(Long id);
- /**
- * 新增或修改单条数据(一般情况下可以将add和edit方法合并为saveOrUpdate方法)
- *
- * @param t 参数
- * @return
- * @see #addOne(T)
- * @see #editOne(T)
- */
- XquantResponse<Boolean> saveOrUpdateOne(T t);
- /**
- * 单条删除
- * 删除方法命名 adapter层 remove, app层 erase, repository接口和infrastructure层 purge, mapper层 delete
- *
- * @param id 主键值
- * @return
- */
- XquantResponse<Boolean> removeOne(Long id);
- /**
- * 新增单条数据
- * 新增方法命名 adapter层 add, app层 create, repository接口和infrastructure层 save, mapper层 insert
- *
- * @param t 参数
- * @return
- * @see #saveOrUpdateOne(T)
- */
- default XquantResponse<Boolean> addOne(T t) {
- return null;
- }
- /**
- * 编辑更新数据
- * 更新方法命名 adapter层 edit, app层 modify, repository接口和infrastructure层 change, mapper层 update
- *
- * @param t 参数
- * @return
- * @see #saveOrUpdateOne(T)
- */
- default XquantResponse<Boolean> editOne(T t) {
- return null;
- }
- /**
- * 列表查询 不分页
- *
- * @param t 查询参数
- * @return
- */
- default XquantResponse<List<T>> getList(T t) {
- return null;
- }
- }
复制代码 7.DDD实现示例
比方要实现一个银行转账业务功能,可按照如下步骤进行构建
7.1提取业务焦点域
7.1.1提取关键词
提取焦点域,我们需要做的第一件事就是提取关键词。分析该业务,转账的焦点功能就是把A账户的钱转到B账户名下,此中涉及到了2个关键词钱和账户,然后对关键词进行抽象拓展,即形成领域模型。
7.1.2模型抽象
模型抽象需要做的事是将钱和账户变得通用化,以应对可预见的业务变化,比方钱在生存中大部门情况下我们都直接等同为金额,10元 100元如许。那么在代码中钱这个概念可能就直接计划为BigDecimal类型。这里存在一个隐蔽的缺陷是,金额实际上只是钱的一个属性,钱实际上至少还包含一个明显的属性是 币种类型,是人民币照旧港币。而我们的领域是充血模型的,要求其具备高内聚的特性,所以关于钱的一些校验方法,以及与钱相关的方法我们都内聚在一个Money对象中。经过一轮抽象风暴,钱这个关键词比起生存中的钱的概念变得更具有抽象性,而反应在代码中,其变的更具象化,我们为钱的初步建模如下所示:
- @Data
- @AllArgsConstructor(onConstructor = @__(@JsonCreator))
- @NoArgsConstructor
- public class Money {
-
- /**
- * 金额
- */
- private long cent;
- /**
- * 币种
- */
- private Currency currency;
-
- /**
- * 金额相减
- *
- * @param money
- * @return
- */
- public Money subtract(Money money) {
- return new Money(this.cent - money.cent, this.currency);
- }
- /**
- * 金额相加
- *
- * @param money
- * @return
- */
- public Money add(Money money) {
- return new Money(this.cent + money.cent, this.currency);
- }
- /**
- * 金额利率
- *
- * @param money
- * @return
- */
- public BigDecimal multiply(BigDecimal money) {
- return BigDecimal.valueOf(this.cent).multiply(money);
- }
复制代码 此中包含了与money相关的各个属性和方法,而且money在这里作为被计划为值对象的时候,此中的方法都只操作自己具备的属性,也就是说方法的入参和出参不会有除Money外的其他领域模型对象,如许在高内聚的同时,与其他领域极大降低了耦合度。假如需要涉及多个领域模型的业务操作,在领域服务中处理。并且领域中只做内存盘算,不会有存储层的方法调用。
money对象在这里并不关心唯一性,在计划账户领域模型的时候,他将作为值对象依附于账户存在。我们对账户建模如下:
- @Data
- public class Account {
- private Long id;
- /**
- * 可用余额
- */
- private Money available;
- /**
- * 每日限额
- */
- private Money dailyLimit;
- public Currency getCurrency() {
- return this.available.getCurrency();
- }
- /**
- * 转入
- *
- * @param money
- */
- public void deposit(Money money) {
- if (!this.getCurrency().equals(money.getCurrency())) {
- throw new XquantBaseException("金额异常");
- }
- this.available = this.available.add(money);
- }
- /**
- * 转出
- *
- * @param money
- */
- public void withdraw(Money money) {
- if (this.available.compareTo(money) < 0) {
- throw new XquantBaseException("金额异常");
- }
- if (this.dailyLimit.compareTo(money) < 0) {
- throw new XquantBaseException("金额异常");
- }
- this.available = this.available.subtract(money);
- }
- }
复制代码 如许,Money和Account都内聚了自己强相关的业务逻辑,包括与之相关的基本校验
7.2组织领域服务
当一些业务逻辑涉及到多个领域对象时,使用领域服务来完成。领域服务的包(domainservice)处于domain层。比方转帐的领域服务:
- @Service
- public class AccountTransferServiceImpl implements AccountTransferService {
- @Autowired
- private ExchangeRateService exchangeRateService;
- @Override
- public void transfer(Account sourceAccount, Account targetAccount, Money targetMoney, ExchangeRate exchangeRate) {
- //ExchangeRate exchangeRate1 = exchangeRateService.getExchangeRate(sourceAccount.getCurrency(), targetAccount.getCurrency());
- //省略部分代码
- Money sourceMoney = exchangeRate.exchange(targetMoney);
- //转入
- sourceAccount.deposit(sourceMoney);
- //转出
- targetAccount.withdraw(targetMoney);
- }
- }
复制代码 此中Account也是领域对象,ExchangeRate是值对象。
7.3组织应用服务
当我们要对外暴露接口服务功能,对领域对象或者服务进行编排和串联的时候,就需要组织应用服务了,应用服务位于app层中,同时推荐将持久化操作和变乱操作都放置在这一条理。应用层可以注入基础办法层的很多服务,比方持久化,消息中心件等服务。简单的crud方法也在这一层调用存储层并对外提供接口:
- @Service
- public class TransferServiceImpl implements TransferService {
- @Autowired
- private AccountRepository accountRepository;
- @Autowired
- private AuditMessageProducer auditMessageProducer;
- @Autowired
- private ExchangeRateService exchangeRateService;
- @Autowired
- private AccountTransferService accountTransferService;
- @Transactional
- @Override
- public XquantResponse<Boolean> transfer(AccountDTO accountDTO) {
- String targetAccountNumber = accountDTO.getTargetAccountNumber();
- BigDecimal targetAmount = accountDTO.getAmount();
- // 参数校验
- Money targetMoney = new Money(targetAmount.longValue(), new Currency("CNY"));
- // 读数据
- Account sourceAccount = accountRepository.findById(accountDTO.getId());
- Account targetAccount = accountRepository.findById(Long.valueOf(targetAccountNumber));
- ExchangeRate exchangeRate = exchangeRateService.getExchangeRate(sourceAccount.getCurrency(), targetMoney.getCurrency());
- // 业务逻辑
- accountTransferService.transfer(sourceAccount, targetAccount, targetMoney, exchangeRate);
- // 保存数据 todo 纯粹的业务逻辑和数据分离,聚合数据库操作到一个事务方法
- accountRepository.saveAccount(sourceAccount);
- accountRepository.saveAccount(targetAccount);
- // 发送审计消息
- AuditMessage message = new AuditMessage(1L, sourceAccount, targetAccount, targetMoney, new Date());
- auditMessageProducer.send(message);
- return XquantResponse.success(true);
- }
- @Override
- public XquantResponse<Boolean> saveOrUpdateAccount(AccountDTO accountDTO) {
- Account account = AccountApBuilder.INSTANCE.toDomain(accountDTO);
- Boolean aBoolean = accountRepository.saveOrUpdateAccount(account);
- return XquantResponse.success(aBoolean);
- }
- @Override
- public XquantResponse<PageDTO<AccountDTO>> pageListAccount(AccountDTO accountDTO) {
- Account account = AccountApBuilder.INSTANCE.toDomain(accountDTO);
- PageDTO<Account> accountPageDTO = accountRepository.pageListAccount(account, accountDTO.getCurrPage(), accountDTO.getPageSize());
- PageDTO<AccountDTO> result = AccountApBuilder.INSTANCE.toPageList(accountPageDTO);
- return XquantResponse.success(result);
- }
- @Override
- public XquantResponse<AccountDTO> searchAccountById(Long id) {
- Account byId = accountRepository.findById(id);
- return XquantResponse.success(AccountApBuilder.INSTANCE.doToDTO(byId));
- }
- @Override
- public XquantResponse<Boolean> eraseAccountById(Long id) {
- Boolean aBoolean = accountRepository.purgeAccountById(id);
- return XquantResponse.success(aBoolean);
- }
- @Override
- public XquantResponse<Boolean> createAccount(AccountDTO accountDTO) {
- Account account = AccountApBuilder.INSTANCE.toDomain(accountDTO);
- Boolean aBoolean = accountRepository.changeAccount(account);
- return XquantResponse.success(aBoolean);
- }
- @Override
- public XquantResponse<Boolean> modifyAccount(AccountDTO accountDTO) {
- Account account = AccountApBuilder.INSTANCE.toDomain(accountDTO);
- Boolean aBoolean = accountRepository.changeAccount(account);
- return XquantResponse.success(aBoolean);
- }
- }
复制代码 7.4基础设层防腐
基础办法层承担了领域防腐ACL的重任,将外部的三方办法与领域模型分离,使用依靠倒置原则让业务细节和技能细节解耦。比方我们在domain层定义仓储服务接口:
- /**
- * @Author yongliang.xiong
- * @Date 2024/11/8 14:40
- * @Description 数据存储的依赖反转,在南向网关中,我们只定义接口,解耦业务代码和存储代码。实现和ACL交由inf层
- */
- public interface AccountRepository {
- /**
- * 根据id获取账户
- *
- * @param id
- * @return
- */
- Account findById(Long id);
- /**
- * 保存账户信息
- *
- * @param account
- * @return
- */
- Account saveAccount(Account account);
- /**
- * 保存或更新账户信息
- *
- * @param account
- * @return
- */
- Boolean saveOrUpdateAccount(Account account);
- /**
- * 分页查询账户列表
- *
- * @param account
- * @param currPage 当前页码
- * @param pageSize 每页数据量
- * @return
- */
- PageDTO<Account> pageListAccount(Account account, long currPage, long pageSize);
- /**
- * 根据id查询账户
- *
- * @param id
- * @return
- */
- Account searchAccountById(Long id);
- /**
- * 根据id删除账户
- *
- * @param id
- * @return
- */
- Boolean purgeAccountById(Long id);
- /**
- * 修改账户
- *
- * @param account
- * @return
- */
- Boolean changeAccount(Account account);
- }
复制代码 然后在infrastructure层实现对应接口:
- @Service
- public class AccountRepositoryImpl extends ServiceImpl<AccountMapper, AccountPO> implements AccountRepository {
- @Resource
- private AccountMapper accountMapper;
- @Resource
- private AccountBuilder accountBuilder;
- @Override
- public Account findById(Long id) {
- AccountPO accountPO = accountMapper.selectById(id);
- return AccountBuilder.INSTANCE.toDomain(accountPO);
- //AccountPO byId = this.getById(id);
- }
- @Override
- public Account saveAccount(Account account) {
- AccountPO accountPO = AccountBuilder.INSTANCE.toPO(account);
- this.save(accountPO);
- return account;
- }
- @Override
- public Boolean saveOrUpdateAccount(Account account) {
- AccountPO accountPO = AccountBuilder.INSTANCE.toPO(account);
- return this.saveOrUpdate(accountPO);
- }
- @Override
- public PageDTO<Account> pageListAccount(Account account, long currPage, long pageSize) {
- Page<AccountPO> dataPage = new Page<>(currPage, pageSize);
- AccountPO accountPO = AccountBuilder.INSTANCE.toPO(account);
- List<AccountPO> accountPOIPage = accountMapper.selectPageAccount(dataPage, accountPO);
- dataPage.setRecords(accountPOIPage);
- return AccountBuilder.INSTANCE.toPageList(dataPage);
- //List<Account> records = AccountBuilder.INSTANCE.toDomainList(accountPOIPage);
- // return new PageDTO(records, dataPage.getTotal(), dataPage.getSize(), dataPage.getCurrent();
- }
- @Override
- public Account searchAccountById(Long id) {
- AccountPO byId = this.getById(id);
- return AccountBuilder.INSTANCE.toDomain(byId);
- }
- @Override
- public Boolean purgeAccountById(Long id) {
- return this.removeById(id);
- }
- @Override
- public Boolean changeAccount(Account account) {
- AccountPO accountPO = AccountBuilder.INSTANCE.toPO(account);
- return this.updateById(accountPO);
- }
- }
复制代码 防腐层除了依靠反转之外,还要留意DO,DTO和PO(infrastructure层的持久化对象)对象的转换,要将外界的变动信息隔离在领域层之外,就需要在infr层进行对象转化,这是一个繁琐但是也是须要的处理,幸亏我们可以通过mapstruct来处理:
- @Mapper(componentModel = "spring")
- public interface AccountBuilder {
- AccountBuilder INSTANCE = Mappers.getMapper(AccountBuilder.class);
- /**
- * 将领域对象转换为PO
- *
- * @param account
- * @return
- */
- @Mappings({
- @Mapping(source = "available.cent", target = "availableCent"),
- @Mapping(source = "available.currency.currencyCode", target = "currency"),
- @Mapping(source = "available.cent", target = "dailyLimit")
- })
- AccountPO toPO(Account account);
- /**
- * 将PO转换为领域对象
- *
- * @param accountPO
- * @return
- */
- @Mappings({
- @Mapping(target = "available.cent", source = "availableCent"),
- @Mapping(target = "available.currency.currencyCode", source = "currency"),
- @Mapping(target = "dailyLimit.cent", source = "dailyLimit")
- })
- Account toDomain(AccountPO accountPO);
- /**
- * 将分页对象转换为分页列表
- *
- * @param dataPage
- * @return
- */
- default PageDTO<Account> toPageList(Page<AccountPO> dataPage) {
- List<AccountPO> content = dataPage.getRecords();
- List<Account> mappedContent = content.stream()
- .map(this::toDomain).collect(Collectors.toList());
- return new PageDTO(mappedContent, dataPage.getTotal(), dataPage.getSize(), dataPage.getCurrent());
- }
- }
复制代码 类似依靠的消息中心件,第三方接口调用等操作都是云云,需要在基础办法层做处理,隔离易变性。
至此围绕钱和账户,我们构建了对应的领域模型,并提取了领域服务和应用服务,在基础办法层实现了领域防腐。至此服务对adapter和client层已经处于可用状态。但是需要意识到的一点是:业务和需求是会持续变化的,精良的程序也是渐进式演化的,领域驱动计划也不例外,精良的框架计划和架构并不追求固定不变。我们总是以开闭原则为焦点,保持可拓展性和灵活性。因此,领域模型的计划也会随着业务的变化而改进,就如同money模型,我们现在这个建模是传统意义上的钱,也就是纸币,现在除了纸币尚有数字货币,虚拟货币等,假如业务升级到需要囊括这些新型货币,不可避免要重修模型。
8.代码处理规范
8.1非常处理
外抛的非常不能过于宽泛,RuntimeException就过于宽泛。应该使用XquantBaseException或则其子类,自定义非常直接继承XquantBaseException。比方:
- public void deposit(Money money) {
- if (!this.getCurrency().equals(money.getCurrency())) {
- //外抛的异常不能过于宽泛,例如 throws RuntimeException
- throw new XquantBaseException("金额异常");
- }
- this.available = this.available.add(money);
- }
复制代码 同理捕获非常时也应只管避免过于宽泛的捕获处理。在catch语句中不应该使用printStackTrace打印非常,应该使用日志组件来记载error:
- public String test() {
- try {
- return "s.getName()";
- } catch (Exception e) {
- //错误使用
- e.printStackTrace();
-
- //应该使用日志记录
- log.error("异常日志!", e);
- }
- return "";
- }
复制代码 8.2线程安全类的使用
包括但不限于 Vector、Stack、Hashtable 和 StringBuffer这类线程相对安全的工具类。当明白不会出现线程安全问题时,使用未作同步处理的工具类List、Deque、Map、StringBuilder代替以获取更高性能。比方StringBuffer,假如对象未发生线程逃逸,那么就使用StringBuilder代替。比方以下方法是未逃逸不需要使用StringBuffer的:
- public void buildString() {
- StringBuilder sb = new StringBuilder();
- sb.append("Hello");
- sb.append(" ");
- sb.append("World");
- String result = sb.toString();
- System.out.println(result);
- }
复制代码 8.3禁用逾期的api
被标注@Deprecated的方法和类,都是不稳定的,将会被移除或替换,标注的注解会提供{@link #newMethod()}来提供替代方案,应该使用该替代方案来替换。同时在提交的代码中和发布包中不能出现System.out.println()语句来打印日志。在catch语句中不能出现Throwable::printStackTrace()。
8.4非空检查
对于各种用于获取结果的方法如getXxx(常见如Map::get),在获取其结果后,使用该结果前应该使用Optional: fNullable进行非空检查以避免NPE非常:
- public void processUser(Long id) {
- User user = getUserById(id);
- Optional<User> optionalUser = Optional.ofNullable(user);
- optionalUser.ifPresent(u -> {
- //存在时逻辑处理
- });
- //或者是中断条件
- if (optional.isPresent()) {
- //todo
- }
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |