前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《解构领域驱动设计》勘误

《解构领域驱动设计》勘误

作者头像
张逸
发布2023-03-23 18:16:54
6140
发布2023-03-23 18:16:54
举报
文章被收录于专栏:斑斓斑斓

本书从出版以来,已经先后印刷7次。感谢广大读者书友,友善地帮我找到了一些bug,并前后做了两次勘误。

在即将发行的印刷批次以及即将发布的电子书中,我又做了第三次勘误。本次勘误需要特别感谢周大福的庞剑锋。他认真阅读了本书的每词每句,承蒙他的慧眼,助我找出不少Bug,令我既感激又惭愧:感激锋哥的严谨与认真,帮助我提升了本书质量,惭愧则是因为这些错误委实不应该,还得怪我自己不够细心。

为了便于大家对比手上的书籍,找到错误并修订错误,我特别在这里将这三次勘误做了一次汇总,勘误顺序以本书页码为准,并在每个错误处标记勘误的批次。

勘误格式:

  • 红色为新增部分
  • 删除线表示删除
  • 新旧有较大区别,会给出勘误前后两个版本对比

第一篇 开篇

第2页(第二次勘误)

我们很难给复杂系统下一个举世公认的定义。专门从事复杂系统研究的Melanie Mitchell在接受Ubiquity杂志专访时,“勉为其难”地为复杂系统给出了一个相对通俗的定义:“由大量相互作用的部分组成的系统。与整个系统比起来,这些组成部分相对简单,没有中央控制,组成部分之间也没有全局性的通信,并且组成部分的相互作用导致了复杂行为。”

第5页(第一次勘误)

文本框下第三行,“就会是的”修改为“就会使得”。

第16页(第二次勘误)

聚合(aggregate)(参见第15章)是一种边界,它可以封装一到多个实体与值对象,并维持该边界范围之内的业务完整性。聚合至少包含一个实体,且只有实体才能作为聚合根(aggregate root)。工厂(factory)和资源库(repository)(参见第1715章)负责管理聚合的生命周期。

第18页(第二次勘误)

子领域的边界明确了问题空间中领域的优先级,限界上下文的边界则确保了领域建模的最大自由度。这也是战略设计在分治上起到的效用。当我们在战略层次从问题空间映射到解空间时,子领域也将映射到限界上下文,即可根据子领域的类型为限界上下文选择不同的建模方式。例如为处于核心子领域的限界上下文选择领域模型(domain model)模式[12]116,为处于支撑子领域(supporting sub domainsubdomain)的限界上下文选择事务脚本(transaction script)模式[12]110,这样就可以灵活地平衡开发成本与开发质量。

第36页(第一次勘误)

在图3-4下方第三段:

推动领域建模完成从问题空间到解空间战术求解的核心驱动力是“领域”,在领域驱动设计统一过程中,就是通过业务服务表达领域知识,成为领域分析建模、领域设计建模和领域分析实现建模的驱动力。

第二篇 全局分析

第57页(第二次勘误)

第二段:

价值需求又好像一杆秤,每当我们挖掘到一条业务需求,就拿到这杆秤上称一称,然后根据它的质量(俗称重量)重量来确定优先级。

第75页(第二次勘误)

文本框文字的第三段:

业务服务的规格说明规约则为领域建模提供了建模依据,帮助分解任务和明确职责分配,并在通过测试驱动开发进行领域实现建模时,作为识别和编写测试用例的主要参考。

第三篇 架构映射

第151页(第三次勘误)

代码有误,修改前:

代码语言:javascript
复制
public class OrderAppService {
   @Service
   private PlacingOrderService placingOrderService;

   // 事务管理为横切关注点
   @Transactional(propagation=Propagation.REQUIRED) 
   public void placeOrder(Order order) { 
      try {
         orderService.execute(order);
      } catch (InvalidOrderException ex | Exception ex) {
         // 日志记录为横切关注点
         logger.error(ex.getMessage());
         // ApplicationException派生自RuntimeException,事务会在抛出该异常时回滚
         throw new ApplicationException("failed to place order", ex);
      }
   }
}

修改第9行代码:

代码语言:javascript
复制
public class OrderAppService {
   @Service
   private PlacingOrderService placingOrderService;

   // 事务管理为横切关注点
   @Transactional(propagation=Propagation.REQUIRED) 
   public void placeOrder(Order order) { 
      try {
         placingOrderService.execute(order);
      } catch (InvalidOrderException ex | Exception ex) {
         // 日志记录为横切关注点
         logger.error(ex.getMessage());
         // ApplicationException派生自RuntimeException,事务会在抛出该异常时回滚
         throw new ApplicationException("failed to place order", ex);
      }
   }
}

第153页(第一、二次勘误)

代码有误,修改前:

代码语言:javascript
复制
public class OrderAppService {
   @Service
   private PlacingOrderService placingOrderService;

   // 此时将NotificationService视为基础设施服务
   @Service
   private NotificationService notificationService;

   // 事务为横切关注点
   @Transactional(propagation=Propagation.REQUIRED) 
   public void placeOrder(Order order) { 
      try {
         orderService.execute(order);
         notificationService.send(notificationComposer.compose(order));
      } catch (InvalidOrderException ex | Exception ex) {
         // 日志为横切关注点
         logger.error(ex.getMessage());
         // ApplicationException派生自RuntimeException,事务会在抛出该异常时回滚
         throw new ApplicationException("failed to place order", ex);
      }
   }
}

第11行的placeOrder()方法都做了修订:

代码语言:javascript
复制
public class OrderAppService {
   @Service
   private PlacingOrderService placingOrderService;

   // 此时将NotificationService视为基础设施服务
   @Service
   private NotificationService notificationService;

   // 事务管理为横切关注点
   @Transactional(propagation=Propagation.REQUIRED) 
   public void placeOrder(PlacingOrderRequest request) { 
      try {
         Order order=request.to();
         orderService.placeOrder(order);
         Notification notification = notificationComposer.compose(order);
         notificationService.send(notification);
      } catch (InvalidOrderException Exception ex) {
         // 日志记录为横切关注点
         logger.error(ex.getMessage());
         // ApplicationException派生自RuntimeException,事务会在抛出该异常时回滚
         throw new ApplicationException("failed to place order", ex);
      }
   }
}

第159页(第三次勘误)

代码有误,修改前:

代码语言:javascript
复制
   private void onPaymentCompleted(PaymentCompleted paymentEvent) {
      if (paymentEvent.OperationResult == OperationResult.SUCCESS) {
         updatingService.execute(OrderStatus.PAID);        
         ApplicationEvent orderPaid = composeOrderPaidEvent(paymentEvent.orderId());
         eventPublisher.publishEvent("payment", orderPaid);
      } else {...}
   }

修改第3行代码:

代码语言:javascript
复制
   private void onPaymentCompleted(PaymentCompleted paymentEvent) {
      if (paymentEvent.OperationResult == OperationResult.SUCCESS) {
         updatingService.execute(paymentEvent.orderId(), OrderStatus.PAID);        
         ApplicationEvent orderPaid = composeOrderPaidEvent(paymentEvent.orderId());
         eventPublisher.publishEvent("payment", orderPaid);
      } else {...}
   }

第181页(第三次勘误)

图12-13下的代码模型:

修改后(调整了pl/message的位置):

第192页(第三次勘误)

代码有误,修改前:

代码语言:javascript
复制
public interface InventoryClient {
   InventoryReview check(Order order);
}

增加一个lock()方法,修改后:

代码语言:javascript
复制
public interface InventoryClient {
   InventoryReview check(Order order);
   void lock(Order order);
}

第195页(第一次勘误)

代码有误,修改前:

代码语言:javascript
复制
   public void placeOrder(Order order) {
      if (!order.isValid()) {
         throw new InvalidOrderException();
      }

      InventoryReview inventoryReview = inventoryClient.check(order);
      if (!inventoryReview.isAvailable()) {
         throw new NotEnoughInventoryException();
      }


      orderRepository.add(order);
          ShoppingCartService.removeItems(order.customerId(),
              Order.purchasedProducts()); 
      inventoryClient.lock(LockingInventoryRequest.from(order));
   }

第15行代码有误,应去掉对order的转换:

代码语言:javascript
复制
   public void placeOrder(Order order) {
      if (!order.isValid()) {
         throw new InvalidOrderException();
      }

      InventoryReview inventoryReview = inventoryClient.check(order);
      if (!inventoryReview.isAvailable()) {
         throw new NotEnoughInventoryException();
      }


      orderRepository.add(order);
          ShoppingCartService.removeItems(order.customerId(),
              Order.purchasedProducts()); 
      inventoryClient.lock(order);
   }

第四篇 领域建模

第226页(第二次勘误)

我的职责就是管理世界与世界的相互关系,就是理顺事物的顺序,就是让结果出现在原因之后,就是不使含义与含义相混淆,就是让过去出现在现在之前,就是让未来出现在现在之后。——村上春树,《海边的卡夫卡》

第231页(第三次勘误)

以下代码中的extends修改为implements:

代码语言:javascript
复制
package com.dddexplained.eas.core.domain;

public interface Identity<T> implements Serializable {
  T value();
}

第245页(第三次勘误)

代码有误,修改前:

代码语言:javascript
复制
public enum LengthUnit {
   MM(1), CM(10), DM(100), M(1000);

   private int ratio;
   Unit(int ratio) {
      this.ratio = ratio;
   }

   int convert(Unit target, int value) {
      return value * ratio / target.ratio;
   }
}

LengthUnit的构造函数有误,应修改为:

代码语言:javascript
复制
public enum LengthUnit {
   MM(1), CM(10), DM(100), M(1000);

   private int ratio;
   LengthUnit(int ratio) {
      this.ratio = ratio;
   }

   int convert(Unit target, int value) {
      return value * ratio / target.ratio;
   }
}

第261页(第一次勘误)

代码中的注释“//”改为“//client”

第261页(第三次勘误)

代码有误,AggregateRoot<T>是一个接口,应改为implements:

代码语言:javascript
复制
public class Customer implements AggregateRoot<Customer> {
   private List<Order> orders;

   public List<Order> getOrders() {
      return this.orders;
   }
}

第265页(第二次勘误)

15.5节的第5段:“从对象的角度看,生命周期代表了一个实例从创建到回收的过程,就像从出生到死亡的生命过程。而数据记录呢?生命周期的起点是指插入一条新记录,该记录被删除就是生命周期的终点。”

第269页(第一次勘误)

原代码为:

public static createFlight(String flightId, String ioFlag, ...)

缺少了返回值,应修改为:

public static Flight createFlight(String flightId, String ioFlag, ...)

第271页(第三次勘误)

本页下方到272页的代码有误,修改前:

代码语言:javascript
复制
public class Flight extends Entity<FlightId> implements AggregateRoot<Flight> {
   private String flightNo;
   private Carrier carrier;
   private Airport departureAirport;
   private Airport arrivalAirport;
   private Gate boardingGate;
   private LocalDate flightDate;

   private Flight(String flightNo) {
      this.flightNo = flightNo;
   }

   public static class Builder {
      // required fields
      private final String flightNo;

      // optional fields
      private Carrier carrier;
      private Airport departureAirport;
      private Airport arrivalAirport;
      private Gate boardingGate;
      private LocalDate flightDate;

      public Builder(String flightNo) {
         this.flightNo = flightNo;
      }
      public Builder beCarriedBy(String airlineCode) {
         carrier = new Carrier(airlineCode);
         return this;
      }
      public Builder departFrom(String airportCode) {
         departureAirport = new Airport(airportCode);
         return this;
      }
      public Builder arriveAt(String airportCode) {
         arrivalAirport = new Airport(airportCode);
         return this;
      }
      public Builder boardingOn(String gateNo) {
         boardingGate = new Gate(gateNo);
         return this;
      }
      public Builder flyingIn(LocalDate flyingInDate) {
         flightDate = flyingInDate;
         return this;
      }
      public Flight build() {
         return new Flight(this);
      }
   }
   private Flight(Builder builder) {
      flightNo = builder.flightNo;
      carrier = builder.carrier;
      departureAirport = builder.departureAirport;
      arrivalAirport = builder.arrivalAirport;
      boardingGate = builder.boardingGate;
      flightDate = builder.flightDate;
   }
}

主要的修改是增加了第9行代码所示的prepareBuilder()方法。完整的代码修改如下所示:

代码语言:javascript
复制
public class Flight extends Entity<FlightId> implements AggregateRoot<Flight> {
   private String flightNo;
   private Carrier carrier;
   private Airport departureAirport;
   private Airport arrivalAirport;
   private Gate boardingGate;
   private LocalDate flightDate;

   public static Builder prepareBuilder(String flightNo) {
      return new Builder(flightNo);
   }

   public static class Builder {
      // required fields
      private final String flightNo;

      // optional fields
      private Carrier carrier;
      private Airport departureAirport;
      private Airport arrivalAirport;
      private Gate boardingGate;
      private LocalDate flightDate;
      private Builder(String flightNo) {
              this.flightNo = flightNo;
      }

      public Builder beCarriedBy(String airlineCode) {
         carrier = new Carrier(airlineCode);
         return this;
      }
      public Builder departFrom(String airportCode) {
         departureAirport = new Airport(airportCode);
         return this;
      }
      public Builder arriveAt(String airportCode) {
         arrivalAirport = new Airport(airportCode);
         return this;
      }
      public Builder boardingOn(String gateNo) {
         boardingGate = new Gate(gateNo);
         return this;
      }
      public Builder flyingIn(LocalDate flyingInDate) {
         flightDate = flyingInDate;
         return this;
      }
      public Flight build() {
         return new Flight(this);
      }
   }
   private Flight(Builder builder) {
      flightNo = builder.flightNo;
      carrier = builder.carrier;
      departureAirport = builder.departureAirport;
      arrivalAirport = builder.arrivalAirport;
      boardingGate = builder.boardingGate;
      flightDate = builder.flightDate;
   }
}

第273页(第三次勘误)

需要将本页代码中的AirportCode修改为Airport,修改后的代码为:

代码语言:javascript
复制
public class Flight extends Entity<FlightId> implements AggregateRoot<Flight> {
   private String flightNo;
   private Carrier carrier;
   private Airport departureAirport;
   private Airport arrivalAirport;
   private Gate boardingGate;
   private LocalDate flightDate;

   // 聚合必备的字段要在构造函数的参数中给出
   private Flight(String flightNo) {
      this.flightNo = flightNo;
   }

   public static Flight withFlightNo(String flightNo) {
      return new Flight(flightNo);
   }

   public Flight beCarriedBy(String airlineCode) {
      this.carrier = new Carrier(airlineCode);
      return this;
   }

   public Flight departFrom(String airportCode) {
      this.departureAirport = new Airport(airportCode);
      return this;
   }

   public Flight arriveAt(String airportCode) {
      this.arrivalAirport = new Airport(airportCode);
      return this;
   }

   public Flight boardingOn(String gate) {
      this.boardingGate = new Gate(gate);
      return this;
   }

   public Flight flyingIn(LocalDate flightDate) {
      this.flightDate = flightDate;
      return this;
   }
}

第283页(第二次勘误)

第二段出现单词拼写错误:

“例如,付款记录聚合OrdserSettlement与支付约定聚合PayAggreementPayAgreement都在支付上下文中,在计算OrderSettlement实体的支付金额时,需要PayAggreementPayAgreement实体计算获得的支付利率。因此,可在OrdserSettlement根实体的payAmountFor()方法中,传入PayAggreementPayAgreement对象:”

代码也应对应修改为:

代码语言:javascript
复制
public class OrderSettlement {
   public BigDecimal payAmountFor(PayAgreement agreement) {
      return orderAmount.multiply(agreement.actualPayRate());
   }
}

public class PayAgreement {
   public BigDecimal actualPayRate() {
      return new BigDecimal(payRate * 0.01);
   }
}

第286页

代码中TransferingService类的transfer()方法中,对Account的transferTo()方法的调用时,缺少了amount的参数,故而代码应修改为:

代码语言:javascript
复制
public class TransferingService {
   private AccountRepository accountRepo;
   private TransactionRepository transactionRepo;

   public void transfer(AccountId sourceAccountId, AccountId targetAccountId, Money 
amount) {
      SourceAccount sourceAccount = accountRepo.accountOf(sourceAccountId);
      TargetAccount targetAccount = accountRepo.accountOf(targetAccountId);
      // 账户余额是否大于amount值,由Account聚合负责
      Transaction transaction = sourceAccount.transferTo(targetAccount, amount);

      accountRepo.save(sourceAccount);
      accountRepo.save(targetAccount);
      transactionRepo.save(transaction);
   }
}

第292页代码与第293页的代码的transfer()方法也需要做对应的调整。

第293页(第三次勘误)

代码下的第一段内容:“通知服务也采用类似方式实现TransferingEventSubscriber接口。”

原来的TransferEventSubscriber统一修改为TransferingEventSubscriber。因此,第292页代码中也应做对应修改。第一段代码修改为:

代码语言:javascript
复制
   private void publish(TransferSucceeded succeededEvent) {
      for (TransferingEventSubscriber subscriber : subscribers) {
         subscriber.handle(succeededEvent);
      }
   }

   private void publish(TransferFailed failedEvent) {
      for (TransferingEventSubscriber subscriber : subscribers) {
         subscriber.handle(failedEvent);
      }
   }

代码段中的第2行与第8行,都做了修改。

第292页的第二段代码也该如此修改,如下代码中的第一行,修改为TransferingEventSubscriber:

代码语言:javascript
复制
public class TransactionService implements TransferingEventSubsriber {
   private TransactionRepository transactionRepo;

第297页(第二次勘误)

图16-3中的“事件发布者”改为“发布者”。

第298页(第二次勘误)

原图16-5为:

由于应用服务也可以和端口交互,故而图中增加了端口,如下所示:

第299页(第二次勘误)

增加了一行内容(标记为红色):

  • 远程服务与应用服务:体现了最小知识法则,保证远程服务的单一职责。
  • 应用服务与领域服务:由领域服务封装领域逻辑,以避免其泄漏到应用层。
  • 应用服务与端口:应用服务可以与端口协作,用于访问外部资源。
  • 应用服务与工厂:只限于消息契约对象或装配器担任聚合工厂的场景。
  • 应用服务与聚合:应用服务在调用领域服务时,需要获得聚合,为了避免领域知识的泄漏,不建议应用服务直接调用聚合实体和值对象的领域行为,对外,也必须将聚合转换为消息契约对象。
  • 领域服务与工厂、端口和聚合:确保了领域逻辑的职责分配,避免领域服务成为事务脚本。
  • 聚合:聚合只能与聚合协作,不知道其他角色构造型,保证了聚合的稳定性和纯粹性。

第321页(第二次勘误):

该页脚注的内容:

ZenUML项目(参见ZenUML官网)的开发者是肖鹏。他曾经担任ThoughtWorks中国区持续交付Practice Lead,也是我在ThoughtWorks任职时的Buddy与Sponsor,目前在墨尔本一家咨询公司任架构师,业余时间负责ZenUML的开发。ZenUML除了提供Web版本,还提供了Chrome、ConfulenceConfluence和IntelliJ IDEA的插件。

第326页(第二次勘误)

该页中间给出的测试方法名进行了修改:

should_transfer_from_src_account_to_desttarget_account_given_correct_transfer_amount()

第327页(第三次勘误)

代码本身没有错误,但为了行文简单,没有给出setup的内容,会影响读者的理解,源代码为:

代码语言:javascript
复制
public class AccountTest {   
   @Test
   public void should_transfer_from_src_account_to_dest_account_given_correct_transfer_amount() {
      // given
      Money balanceOfSrc = new Money(100_000L, Currency.RMB);
      SourceAccount src = new Account(srcAccountId, balanceOfSrc);

      Money balanceOfDes = new Money(0L, Currency.RMB);
      TargetAccount target = new Account(targetAccountId, balanceOfDes);

      Money trasferAmount = new Money(10_000L, Currency.RMB);

      // when
      src.transferTo(target, transferAmount);

      // then
      assertThat(src.getBalance()).isEqualTo(Money.of(90_000L, Currency.RMB));
      assertThat(target.getBalance()).isEqualTo(Money.of(10_000L, Currency.RMB));
   }
}

增加了setup和字段定义的内容:

代码语言:javascript
复制
public class AccountTest {
   private AccountId srcAccountId;
   private AccountId targetAccountId;

   @Before
   void setup() {
       srcAccountId = AccountId.of("123456");  //用于演示
       targetAccountId = AccountId.of("654321");  //用于演示
   }   
   @Test
   void should_transfer_from_src_account_to_target_account_given_correct_transfer_
amount() {
      // given
      Money balanceOfSrc = new Money(100_000L, Currency.RMB);
      SourceAccount src = new Account(srcAccountId, balanceOfSrc);

      Money balanceOfDes = new Money(0L, Currency.RMB);
      TargetAccount target = new Account(targetAccountId, balanceOfDes);

      Money trasferAmount = new Money(10_000L, Currency.RMB);

      // when
      src.transferTo(target, transferAmount);

      // then
      assertThat(src.getBalance()).isEqualTo(Money.of(90_000L, Currency.RMB));
      assertThat(target.getBalance()).isEqualTo(Money.of(10_000L, Currency.RMB));
   }
}

第343页(第二次勘误)

在代码下的第一段末尾,出现单词拼写错误:

在“确定是否为月末工作日”与“确定是否为间隔一星期的星期五”任务这一级,业务目标为“确定是否为正确的工作日”,故而命名为WordDayServiceWorkdayService。

第五篇 融合

第371页(第三次勘误)

该页第二段代码中的方法名有误,应修改为:

代码语言:javascript
复制
package com.dddexplained.ecommerce.ordercontext.southbound.port.client;

@FeignClient("inventory-service")
public interface InventoryClient {
   @RequestMapping(value = "/inventories/order", method = RequestMethod.POST)
   InventoryResponse isAvailable(@RequestBody CheckingInventoryRequest inventoryRequest);
}

第384页(第一次勘误)

18.4.1节的第二段:

由于角色构造型规定应用服务应体现业务服务的服务价值,即它作为业务服务的内外协调接口。同时,应用服务还应承担调用横切关注点的职责,事务作为一种横切关注点,将其放在应用服务才是合情合理的。

第408页(第一次勘误)

代码中的构造函数弄错了,修改前:

代码语言:javascript
复制
@Embeddable
public class Absence {
   private LocalDate leaveDate;

   @Enumerated(EnumType.STRING)
   private LeaveReason leaveReason;

   public Absence() {
   }

   public Address(String country, String province, String city, String street, String zip) {
      this.country = country;
      this.province = province;
      this.city = city;
      this.street = street;
      this.zip = zip;
   }
}

修改后:

代码语言:javascript
复制
@Embeddable
public class Absence {
   private LocalDate leaveDate;

   @Enumerated(EnumType.STRING)
   private LeaveReason leaveReason;

   public Absence() {
   }

   public Absence(LocalDate leaveDate, LeaveReason leaveReason) {
      this.leaveDate = leaveDate;
      this.leaveReason = leaveReason;
   }
}

第482页(第三次勘误)

针对该页的第二段代码,需要做适度修正。严格说来,这并非代码错误,因为本身代码使用了static import(该import并未在书中给出),从而省略了枚举的类型,但对于直接读书的读者来说,这样不太友好,故而增加了类型TicketStatus:

代码语言:javascript
复制
public class TicketServiceTest {
   @Test
   public void should_throw_TicketException_if_available_ticket_not_found() {
      TicketId ticketId = TicketId.next();
      TicketRepository mockTickRepo = mock(TicketRepository.class);
      when(mockTickRepo.ticketOf(ticketId, TicketStatus.Available)).thenReturn(Optional.empty());

      TicketService ticketService = new TicketService();
      ticketService.setTicketRepository(mockTickRepo);

      String trainingId = "111011111111";
      Candidate candidate = new Candidate("200901010110", "Tom", "tom@eas.com", trainingId);
      Nominator nominator = new Nominator("200901010007", "admin", "admin@eas.com", 
TrainingRole.Coordinator);

      assertThatThrownBy(() -> ticketService.nominate(ticketId, candidate, nominator))
             .isInstanceOf(TicketException.class)
             .hasMessageContaining(String.format("available ticket by id {%s} is not 
found", ticketId.id()));
      verify(mockTickRepo).ticketOf(ticketId, TicketStatus.Available);
   }
}

同理,在第487页,也增加了枚举类型StateTransit:

代码语言:javascript
复制
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/spring-mybatis.xml")
public class TicketHistoryRepositoryIT {
   @Autowired
   private TicketHistoryRepository ticketHistoryRepository;
   private final TicketId ticketId = TicketId.from("18e38931-822e-4012-a16e-ac65dfc56f8a");

   @Before
   public void setup() {
      ticketHistoryRepository.deleteBy(ticketId);

      StateTransit availableToWaitForConfirm = StateTransit.from(Available).to(WaitForConfirm);
      LocalDateTime oldTime = LocalDateTime.of(2020, 1, 1, 12, 0, 0);
      TicketHistory oldHistory = createTicketHistory(availableToWaitForConfirm, oldTime);
      ticketHistoryRepository.add(oldHistory);

      StateTransit toConfirm = StateTransit.from(WaitForConfirm).to(Confirm);
      LocalDateTime newTime = LocalDateTime.of(2020, 1, 1, 13, 0, 0);
      TicketHistory newHistory = createTicketHistory(toConfirm, newTime);
      ticketHistoryRepository.add(newHistory);
   }

   @Test
   public void should_return_latest_one() {
      Optional<TicketHistory> latest = ticketHistoryRepository.latest(ticketId);

      assertThat(latest.isPresent()).isTrue();
      assertThat(latest.get().getStateTransit()).isEqualTo(from(WaitForConfirm).to(Confirm));
   }
}

第475和第476页(第三次勘误)

这两页的图20-57与20-58绘制的序列图有误。图20-57错误的部分为下图红色部分:

图20-58错误的部分为下图红色部分:

这两个图的红色部分,均修改为:

以上就是《解构领域驱动设计》的所有勘误。真可以说小错误不断,一边总结,一边脸红,真是惭愧惭愧。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-02-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 逸言 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档