├── business-demo ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── gtw │ │ │ └── business │ │ │ ├── application │ │ │ ├── mq │ │ │ │ └── mq消费入口.txt │ │ │ ├── report │ │ │ │ └── 报表查询.txt │ │ │ ├── scheduler │ │ │ │ └── 定时调度任务入口.txt │ │ │ ├── rpc │ │ │ │ └── 服务接口提供.txt │ │ │ └── controller │ │ │ │ ├── IncidentLogginController.java │ │ │ │ ├── TrackController.java │ │ │ │ ├── RoutingController.java │ │ │ │ └── CargoController.java │ │ │ ├── infrastructure │ │ │ ├── mq │ │ │ │ └── mq消息发送接口实现.txt │ │ │ ├── rpc │ │ │ │ ├── 服务接口调用.txt │ │ │ │ └── cargo │ │ │ │ │ ├── RemoteServiceTranslator.java │ │ │ │ │ ├── UserDO.java │ │ │ │ │ ├── SalersService.java │ │ │ │ │ └── RemoteServiceAdapter.java │ │ │ ├── db │ │ │ │ ├── converter │ │ │ │ │ ├── Converter.java │ │ │ │ │ ├── LocationConverter.java │ │ │ │ │ ├── CarrierMovementConverter.java │ │ │ │ │ ├── HandlingEventConverter.java │ │ │ │ │ └── CargoConverter.java │ │ │ │ ├── repository │ │ │ │ │ ├── ShopReponsitoryImpl.java │ │ │ │ │ ├── ProductRepositoryImpl.java │ │ │ │ │ ├── OrderRepositoryImpl.java │ │ │ │ │ ├── LocationRepositoryImpl.java │ │ │ │ │ ├── CarrierMovementRepositoryImpl.java │ │ │ │ │ ├── HandlingEventRepositoryImpl.java │ │ │ │ │ └── CargoRepositoryImpl.java │ │ │ │ ├── mapper │ │ │ │ │ ├── LocationMapper.java │ │ │ │ │ ├── CarrierMovementMapper.java │ │ │ │ │ ├── HandlingEventMapper.java │ │ │ │ │ └── CargoMapper.java │ │ │ │ ├── LocationDO.java │ │ │ │ ├── HandlingEventDO.java │ │ │ │ ├── CargoDO.java │ │ │ │ └── CarrierMovementDO.java │ │ │ ├── cache │ │ │ │ └── RedisCacheService.java │ │ │ └── event │ │ │ │ ├── publisher │ │ │ │ └── CargoBookEventPublisher.java │ │ │ │ └── listener │ │ │ │ └── CargoListener.java │ │ │ ├── common │ │ │ ├── model │ │ │ │ ├── DomainEvent.java │ │ │ │ └── Address.java │ │ │ ├── component │ │ │ │ ├── CacheService.java │ │ │ │ └── MQProducer.java │ │ │ └── utils │ │ │ │ └── UuidGenerator.java │ │ │ ├── domain │ │ │ ├── aggregate │ │ │ │ ├── shop │ │ │ │ │ ├── ShopRepository.java │ │ │ │ │ ├── OrderRepository.java │ │ │ │ │ ├── ProductRepository.java │ │ │ │ │ ├── Product.java │ │ │ │ │ ├── value │ │ │ │ │ │ ├── OrderIdGenerator.java │ │ │ │ │ │ └── OrderItem.java │ │ │ │ │ ├── factory │ │ │ │ │ │ ├── OrderFactory.java │ │ │ │ │ │ └── ShopFactory.java │ │ │ │ │ ├── Shop.java │ │ │ │ │ └── Order.java │ │ │ │ ├── location │ │ │ │ │ ├── LocationRepository.java │ │ │ │ │ └── Location.java │ │ │ │ ├── cargo │ │ │ │ │ ├── valueobject │ │ │ │ │ │ ├── EnterpriseSegment.java │ │ │ │ │ │ └── DeliverySpecification.java │ │ │ │ │ ├── exception │ │ │ │ │ │ └── CargoException.java │ │ │ │ │ ├── CargoBookEvent.java │ │ │ │ │ ├── CargoRepository.java │ │ │ │ │ └── Cargo.java │ │ │ │ ├── carriermovement │ │ │ │ │ ├── CarrierMovementRepository.java │ │ │ │ │ └── CarrierMovement.java │ │ │ │ └── handlingevent │ │ │ │ │ ├── HandlingEventRepository.java │ │ │ │ │ ├── EventTypeEnum.java │ │ │ │ │ └── HandlingEvent.java │ │ │ └── service │ │ │ │ └── CargoDomainService.java │ │ │ ├── service │ │ │ ├── command │ │ │ │ ├── OrderCommandService.java │ │ │ │ ├── IncidentLoggingCmdService.java │ │ │ │ ├── cmd │ │ │ │ │ ├── CargoDeleteCommand.java │ │ │ │ │ ├── CargoSenderUpdateCommand.java │ │ │ │ │ ├── CargoDeliveryUpdateCommand.java │ │ │ │ │ ├── CreateOrderCommand.java │ │ │ │ │ ├── HandlingEventAddCommand.java │ │ │ │ │ └── CargoBookCommand.java │ │ │ │ ├── CargoCmdService.java │ │ │ │ └── impl │ │ │ │ │ ├── IncidentLoggingCmdServiceImpl.java │ │ │ │ │ ├── OrderCommandServiceImpl.java │ │ │ │ │ └── CargoCmdServiceImpl.java │ │ │ └── query │ │ │ │ ├── TrackQueryService.java │ │ │ │ ├── qry │ │ │ │ ├── EventFindbyCargoQry.java │ │ │ │ └── CargoFindbyCustomerQry.java │ │ │ │ ├── OrderQueryService.java │ │ │ │ ├── RoutingQueryService.java │ │ │ │ ├── CargoQueryService.java │ │ │ │ ├── dto │ │ │ │ ├── CargoHandlingEventDTO.java │ │ │ │ ├── HandlingEventDTO.java │ │ │ │ ├── OrderDTO.java │ │ │ │ ├── CargoDTO.java │ │ │ │ └── CarrierMovementDTO.java │ │ │ │ ├── impl │ │ │ │ ├── OrderQueryServiceImpl.java │ │ │ │ ├── RoutingQueryServiceImpl.java │ │ │ │ ├── CargoQueryServiceImpl.java │ │ │ │ └── TrackQueryServiceImpl.java │ │ │ │ └── assembler │ │ │ │ ├── CargoDTOAssembler.java │ │ │ │ ├── CarrierMovementDTOAssembler.java │ │ │ │ └── HandlingEventDTOAssembler.java │ │ │ └── Application.java │ │ └── resources │ │ ├── application.properties │ │ ├── mapper │ │ ├── location.xml │ │ ├── carrier.xml │ │ ├── handlingevent.xml │ │ └── cargo.xml │ │ ├── static │ │ ├── js │ │ │ └── cargo.js │ │ ├── modifyphone.html │ │ ├── css │ │ │ └── cargo.css │ │ ├── index.html │ │ ├── modifydelivery.html │ │ ├── booking.html │ │ ├── event.html │ │ └── cargo.html │ │ └── mysql_init.sql └── pom.xml ├── images ├── CQRS.png ├── DIP分层.png ├── 六边形架构.png ├── 协作方式.png ├── 子域划分.png ├── 弹性边界.jpg ├── 洋葱模型.png ├── 事件风暴图例1.png ├── 事件风暴图例2.png ├── 划分业务服务.jpg ├── 服务接口能力.png ├── 领域建模图例.jpg ├── DDD+CQRS架构.png └── 限界上下文依赖关系.png ├── .gitignore ├── pom.xml └── README.md /business-demo/src/main/java/com/gtw/business/application/mq/mq消费入口.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/application/report/报表查询.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/mq/mq消息发送接口实现.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/rpc/服务接口调用.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/application/scheduler/定时调度任务入口.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/CQRS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/CQRS.png -------------------------------------------------------------------------------- /images/DIP分层.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/DIP分层.png -------------------------------------------------------------------------------- /images/六边形架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/六边形架构.png -------------------------------------------------------------------------------- /images/协作方式.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/协作方式.png -------------------------------------------------------------------------------- /images/子域划分.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/子域划分.png -------------------------------------------------------------------------------- /images/弹性边界.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/弹性边界.jpg -------------------------------------------------------------------------------- /images/洋葱模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/洋葱模型.png -------------------------------------------------------------------------------- /images/事件风暴图例1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/事件风暴图例1.png -------------------------------------------------------------------------------- /images/事件风暴图例2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/事件风暴图例2.png -------------------------------------------------------------------------------- /images/划分业务服务.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/划分业务服务.jpg -------------------------------------------------------------------------------- /images/服务接口能力.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/服务接口能力.png -------------------------------------------------------------------------------- /images/领域建模图例.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/领域建模图例.jpg -------------------------------------------------------------------------------- /images/DDD+CQRS架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/DDD+CQRS架构.png -------------------------------------------------------------------------------- /images/限界上下文依赖关系.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaotingwang/ddd-demo/HEAD/images/限界上下文依赖关系.png -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/application/rpc/服务接口提供.txt: -------------------------------------------------------------------------------- 1 | rpc对外接口放在这里,有待修改,rpc放在含有main模块,打包提供接口会有问题 -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/common/model/DomainEvent.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.common.model; 2 | 3 | public abstract class DomainEvent { 4 | } 5 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/common/component/CacheService.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.common.component; 2 | 3 | public interface CacheService { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/shop/ShopRepository.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.shop; 2 | 3 | public interface ShopRepository { 4 | } 5 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/common/component/MQProducer.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.common.component; 2 | 3 | public interface MQProducer { 4 | 5 | public void send(Object message); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/location/LocationRepository.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.location; 2 | 3 | public interface LocationRepository { 4 | Location find(String code); 5 | } 6 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/shop/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.shop; 2 | 3 | public interface OrderRepository { 4 | void save(Order order); 5 | Order byId(String id); 6 | } 7 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/cargo/valueobject/EnterpriseSegment.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.cargo.valueobject; 2 | 3 | public enum EnterpriseSegment { 4 | 5 | FRUIT, 6 | COMPUTER; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/converter/Converter.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.converter; 2 | 3 | public interface Converter { 4 | 5 | R serialize(T t); 6 | 7 | T deserialize(R r); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/carriermovement/CarrierMovementRepository.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.carriermovement; 2 | 3 | public interface CarrierMovementRepository { 4 | 5 | CarrierMovement find(String id); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/shop/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.shop; 2 | 3 | import java.util.List; 4 | 5 | public interface ProductRepository { 6 | 7 | List getShopProducts(String id); 8 | } 9 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/cache/RedisCacheService.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.cache; 2 | 3 | import com.gtw.business.common.component.CacheService; 4 | 5 | public class RedisCacheService implements CacheService { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/shop/Product.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.shop; 2 | 3 | public class Product { 4 | private String id; 5 | private String name; 6 | private String shopId; 7 | private int quantity; 8 | } 9 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://127.0.0.1:3306/ddd_demo?useUnicode=true&characterEncoding=UTF-8 2 | spring.datasource.username=root 3 | spring.datasource.password=root 4 | 5 | mybatis.mapper-locations=classpath:mapper/*.xml 6 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/cargo/exception/CargoException.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.cargo.exception; 2 | 3 | public class CargoException extends RuntimeException { 4 | public CargoException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/OrderCommandService.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command; 2 | 3 | import com.gtw.business.service.command.cmd.CreateOrderCommand; 4 | 5 | public interface OrderCommandService { 6 | String createOrder(CreateOrderCommand command); 7 | } 8 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/IncidentLoggingCmdService.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command; 2 | 3 | import com.gtw.business.service.command.cmd.HandlingEventAddCommand; 4 | 5 | public interface IncidentLoggingCmdService { 6 | 7 | void addHandlingEvent(HandlingEventAddCommand cmd); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/cargo/CargoBookEvent.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.cargo; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | public class CargoBookEvent extends ApplicationEvent { 6 | 7 | public CargoBookEvent(Cargo cargo) { 8 | super(cargo); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/repository/ShopReponsitoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.repository; 2 | 3 | import com.gtw.business.domain.aggregate.shop.ShopRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | @Repository 7 | public class ShopReponsitoryImpl implements ShopRepository { 8 | } 9 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/rpc/cargo/RemoteServiceTranslator.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.rpc.cargo; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class RemoteServiceTranslator { 7 | 8 | public UserDO toUserDO(Object obj) { 9 | return new UserDO(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/TrackQueryService.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query; 2 | 3 | import com.gtw.business.service.query.dto.CargoHandlingEventDTO; 4 | import com.gtw.business.service.query.qry.EventFindbyCargoQry; 5 | 6 | public interface TrackQueryService { 7 | 8 | CargoHandlingEventDTO queryHistory(EventFindbyCargoQry qry); 9 | 10 | 11 | } 12 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/qry/EventFindbyCargoQry.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.qry; 2 | 3 | public class EventFindbyCargoQry { 4 | 5 | private String cargoId; 6 | 7 | public String getCargoId() { 8 | return cargoId; 9 | } 10 | 11 | public void setCargoId(String cargoId) { 12 | this.cargoId = cargoId; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/Application.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | public static void main(String[] args) { 9 | SpringApplication.run(Application.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/cmd/CargoDeleteCommand.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command.cmd; 2 | 3 | public class CargoDeleteCommand { 4 | 5 | private String cargoId; 6 | 7 | public String getCargoId() { 8 | return cargoId; 9 | } 10 | 11 | public void setCargoId(String cargoId) { 12 | this.cargoId = cargoId; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/OrderQueryService.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query; 2 | 3 | import com.gtw.business.service.query.dto.OrderDTO; 4 | 5 | /** 6 | * CQRS 7 | */ 8 | public interface OrderQueryService { 9 | 10 | 11 | /** 12 | * 这里为了说明CQRS对查询的使用 13 | * @param id 14 | * @return 15 | */ 16 | OrderDTO byId(String id); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/shop/value/OrderIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.shop.value; 2 | 3 | import com.gtw.business.common.utils.UuidGenerator; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class OrderIdGenerator { 8 | 9 | public String generate() { 10 | return UuidGenerator.newUuid(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/handlingevent/HandlingEventRepository.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.handlingevent; 2 | 3 | import java.util.List; 4 | 5 | public interface HandlingEventRepository { 6 | 7 | List findByCargo(String cargoId); 8 | 9 | List findByScheduleId(String scheduleId); 10 | 11 | void save(HandlingEvent handlingEvent); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/RoutingQueryService.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.infrastructure.db.LocationDO; 6 | import com.gtw.business.service.query.dto.CarrierMovementDTO; 7 | 8 | public interface RoutingQueryService { 9 | 10 | List queryCarriers(); 11 | List queryLocations(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/qry/CargoFindbyCustomerQry.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.qry; 2 | 3 | public class CargoFindbyCustomerQry { 4 | 5 | private String customerPhone; 6 | 7 | public String getCustomerPhone() { 8 | return customerPhone; 9 | } 10 | 11 | public void setCustomerPhone(String customerPhone) { 12 | this.customerPhone = customerPhone; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/CargoQueryService.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.service.query.dto.CargoDTO; 6 | import com.gtw.business.service.query.qry.CargoFindbyCustomerQry; 7 | 8 | public interface CargoQueryService { 9 | 10 | CargoDTO getCargo(String cargoId); 11 | 12 | List queryCargos(); 13 | 14 | List queryCargos(CargoFindbyCustomerQry qry); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/mapper/LocationMapper.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.mapper; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.infrastructure.db.LocationDO; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Param; 8 | 9 | @Mapper 10 | public interface LocationMapper { 11 | 12 | LocationDO select(@Param("code") String code); 13 | 14 | List selectAll(); 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/cargo/CargoRepository.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.cargo; 2 | 3 | import com.gtw.business.domain.aggregate.cargo.valueobject.EnterpriseSegment; 4 | 5 | public interface CargoRepository { 6 | 7 | Cargo find(String id); 8 | 9 | int sizeByCustomer(String customerPhone); 10 | 11 | int sizeByEnterpriseSegment(EnterpriseSegment enterpriseSegment); 12 | 13 | void save(Cargo cargo); 14 | 15 | void remove(String id); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/mapper/CarrierMovementMapper.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.mapper; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.infrastructure.db.CarrierMovementDO; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Param; 8 | 9 | @Mapper 10 | public interface CarrierMovementMapper { 11 | 12 | CarrierMovementDO select(@Param("scheduleId") String scheduleId); 13 | 14 | List selectAll(); 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/LocationDO.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db; 2 | 3 | public class LocationDO { 4 | 5 | private String code; 6 | private String name; 7 | 8 | public String getCode() { 9 | return code; 10 | } 11 | 12 | public void setCode(String code) { 13 | this.code = code; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### Mac ### 16 | .DS_Store 17 | 18 | ### IntelliJ IDEA ### 19 | .idea 20 | *.iws 21 | *.iml 22 | *.ipr 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/location/Location.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.location; 2 | 3 | public class Location { 4 | 5 | private String code; 6 | private String name; 7 | 8 | public String getCode() { 9 | return code; 10 | } 11 | public void setCode(String code) { 12 | this.code = code; 13 | } 14 | public String getName() { 15 | return name; 16 | } 17 | public void setName(String name) { 18 | this.name = name; 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/repository/ProductRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.repository; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.domain.aggregate.shop.Product; 6 | import com.gtw.business.domain.aggregate.shop.ProductRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | @Repository 10 | public class ProductRepositoryImpl implements ProductRepository { 11 | @Override 12 | public List getShopProducts(String id) { 13 | return null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/repository/OrderRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.repository; 2 | 3 | import com.gtw.business.domain.aggregate.shop.Order; 4 | import com.gtw.business.domain.aggregate.shop.OrderRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public class OrderRepositoryImpl implements OrderRepository { 9 | @Override 10 | public void save(Order order) { 11 | 12 | } 13 | 14 | @Override 15 | public Order byId(String id) { 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/mapper/location.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/mapper/HandlingEventMapper.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.mapper; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.infrastructure.db.HandlingEventDO; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Param; 8 | 9 | @Mapper 10 | public interface HandlingEventMapper { 11 | 12 | List selectByCargo(@Param("cargoId") String cargoId); 13 | 14 | List selectByScheduleId(@Param("scheduleId") String scheduleId); 15 | 16 | void save(HandlingEventDO handlingEventDO); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/cmd/CargoSenderUpdateCommand.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command.cmd; 2 | 3 | public class CargoSenderUpdateCommand { 4 | 5 | private String cargoId; 6 | 7 | private String senderPhone; 8 | 9 | public String getCargoId() { 10 | return cargoId; 11 | } 12 | 13 | public void setCargoId(String cargoId) { 14 | this.cargoId = cargoId; 15 | } 16 | 17 | public String getSenderPhone() { 18 | return senderPhone; 19 | } 20 | 21 | public void setSenderPhone(String senderPhone) { 22 | this.senderPhone = senderPhone; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/event/publisher/CargoBookEventPublisher.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.event.publisher; 2 | 3 | import com.gtw.business.domain.aggregate.cargo.CargoBookEvent; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.ApplicationEventPublisher; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class CargoBookEventPublisher { 10 | @Autowired 11 | private ApplicationEventPublisher publisher; 12 | 13 | public void publish(CargoBookEvent event) { 14 | publisher.publishEvent(event); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/handlingevent/EventTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.handlingevent; 2 | 3 | public enum EventTypeEnum { 4 | RECIEVE(0), LOAD(1), UNLOAD(2), END(3); 5 | 6 | int index; 7 | 8 | private EventTypeEnum(int i) { 9 | this.index = i; 10 | } 11 | 12 | public static EventTypeEnum of(int i) { 13 | EventTypeEnum[] values = EventTypeEnum.values(); 14 | for (EventTypeEnum type : values) { 15 | if (type.index == i) return type; 16 | } 17 | return null; 18 | } 19 | 20 | public int index() { 21 | return index; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/dto/CargoHandlingEventDTO.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.dto; 2 | 3 | import java.util.List; 4 | 5 | public class CargoHandlingEventDTO { 6 | 7 | private CargoDTO cargo; 8 | 9 | private List events; 10 | 11 | public CargoDTO getCargo() { 12 | return cargo; 13 | } 14 | 15 | public void setCargo(CargoDTO cargo) { 16 | this.cargo = cargo; 17 | } 18 | 19 | public List getEvents() { 20 | return events; 21 | } 22 | 23 | public void setEvents(List events) { 24 | this.events = events; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/CargoCmdService.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command; 2 | 3 | import com.gtw.business.service.command.cmd.CargoBookCommand; 4 | import com.gtw.business.service.command.cmd.CargoDeleteCommand; 5 | import com.gtw.business.service.command.cmd.CargoDeliveryUpdateCommand; 6 | import com.gtw.business.service.command.cmd.CargoSenderUpdateCommand; 7 | 8 | public interface CargoCmdService { 9 | 10 | void bookCargo(CargoBookCommand cargoBookCommand); 11 | 12 | void updateCargoDelivery(CargoDeliveryUpdateCommand cmd); 13 | 14 | void deleteCargo(CargoDeleteCommand cmd); 15 | 16 | void updateCargoSender(CargoSenderUpdateCommand cmd); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/rpc/cargo/UserDO.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.rpc.cargo; 2 | 3 | public class UserDO { 4 | private String id; 5 | private String phone; 6 | private String name; 7 | 8 | public String getId() { 9 | return id; 10 | } 11 | 12 | public void setId(String id) { 13 | this.id = id; 14 | } 15 | 16 | public String getPhone() { 17 | return phone; 18 | } 19 | 20 | public void setPhone(String phone) { 21 | this.phone = phone; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setName(String name) { 29 | this.name = name; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/cmd/CargoDeliveryUpdateCommand.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command.cmd; 2 | 3 | public class CargoDeliveryUpdateCommand { 4 | 5 | private String cargoId; 6 | 7 | private String destinationLocationCode; 8 | 9 | public String getCargoId() { 10 | return cargoId; 11 | } 12 | 13 | public void setCargoId(String cargoId) { 14 | this.cargoId = cargoId; 15 | } 16 | 17 | 18 | public String getDestinationLocationCode() { 19 | return destinationLocationCode; 20 | } 21 | 22 | public void setDestinationLocationCode(String destinationLocationCode) { 23 | this.destinationLocationCode = destinationLocationCode; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/mapper/CargoMapper.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.mapper; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.infrastructure.db.CargoDO; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Param; 8 | 9 | @Mapper 10 | public interface CargoMapper { 11 | 12 | CargoDO select(@Param("id") String id); 13 | 14 | List selectAll(); 15 | 16 | List selectByCustomer(@Param("phone") String phone); 17 | 18 | void save(CargoDO cargoDO); 19 | 20 | void update(CargoDO cargoDO); 21 | 22 | void remove(@Param("id") String id); 23 | 24 | int countByCustomer(@Param("phone") String phone); 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/converter/LocationConverter.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.converter; 2 | 3 | import com.gtw.business.domain.aggregate.location.Location; 4 | import com.gtw.business.infrastructure.db.LocationDO; 5 | import org.springframework.beans.BeanUtils; 6 | 7 | public class LocationConverter { 8 | 9 | public static LocationDO serialize(Location location) { 10 | LocationDO target = new LocationDO(); 11 | BeanUtils.copyProperties(location, target); 12 | return target; 13 | } 14 | 15 | public static Location deserialize(LocationDO locationDO) { 16 | Location target = new Location(); 17 | BeanUtils.copyProperties(locationDO, target); 18 | return target; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/event/listener/CargoListener.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.event.listener; 2 | 3 | import com.gtw.business.domain.aggregate.cargo.Cargo; 4 | import com.gtw.business.domain.aggregate.cargo.CargoBookEvent; 5 | import org.springframework.context.event.EventListener; 6 | import org.springframework.core.annotation.Order; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class CargoListener { 11 | 12 | @EventListener 13 | @Order(1) 14 | public void processIndexEvent(final CargoBookEvent event) { 15 | execute(); 16 | System.out.println("货物预定成功:" + ((Cargo)event.getSource())); 17 | } 18 | 19 | private void execute() { 20 | System.out.println("CargoListener: 调用CargoListener进行索引"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/converter/CarrierMovementConverter.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.converter; 2 | 3 | import com.gtw.business.domain.aggregate.carriermovement.CarrierMovement; 4 | import com.gtw.business.infrastructure.db.CarrierMovementDO; 5 | import org.springframework.beans.BeanUtils; 6 | 7 | public class CarrierMovementConverter { 8 | 9 | public static CarrierMovementDO serialize(CarrierMovement location) { 10 | CarrierMovementDO target = new CarrierMovementDO(); 11 | BeanUtils.copyProperties(location, target); 12 | return target; 13 | } 14 | 15 | public static CarrierMovement deserialize(CarrierMovementDO locationDO) { 16 | CarrierMovement target = new CarrierMovement(); 17 | BeanUtils.copyProperties(locationDO, target); 18 | return target; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/shop/factory/OrderFactory.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.shop.factory; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.common.model.Address; 6 | import com.gtw.business.domain.aggregate.shop.Order; 7 | import com.gtw.business.domain.aggregate.shop.value.OrderIdGenerator; 8 | import com.gtw.business.domain.aggregate.shop.value.OrderItem; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class OrderFactory { 13 | private final OrderIdGenerator idGenerator; 14 | 15 | public OrderFactory(OrderIdGenerator idGenerator) { 16 | this.idGenerator = idGenerator; 17 | } 18 | 19 | public Order create(List items, Address address) { 20 | String orderId = idGenerator.generate(); 21 | return Order.create(orderId, items, address); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/common/utils/UuidGenerator.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.common.utils; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.Base64; 5 | import java.util.UUID; 6 | 7 | /** 8 | * Util class for generating unique IDs based on UUID. 9 | */ 10 | public final class UuidGenerator { 11 | 12 | private static final Base64.Encoder encoder = Base64.getUrlEncoder(); 13 | 14 | public static String newBase64Uuid() { 15 | UUID uuid = UUID.randomUUID(); 16 | byte[] src = ByteBuffer.wrap(new byte[16]) 17 | .putLong(uuid.getMostSignificantBits()) 18 | .putLong(uuid.getLeastSignificantBits()) 19 | .array(); 20 | return encoder.encodeToString(src).substring(0, 22); 21 | } 22 | 23 | public static String newUuid() { 24 | return UUID.randomUUID().toString().replace("-", ""); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /business-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | ddd-demo 7 | com.gtw 8 | 1.0.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | bussiness-demo 13 | 14 | 15 | org.springframework 16 | spring-tx 17 | 18 | 19 | javax.validation 20 | validation-api 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/mapper/carrier.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/dto/HandlingEventDTO.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.dto; 2 | 3 | public class HandlingEventDTO { 4 | 5 | private String eventType; 6 | private String datetime; 7 | 8 | private CarrierMovementDTO carrierMovement; 9 | 10 | public String getEventType() { 11 | return eventType; 12 | } 13 | 14 | public void setEventType(String eventType) { 15 | this.eventType = eventType; 16 | } 17 | 18 | public String getDatetime() { 19 | return datetime; 20 | } 21 | 22 | public void setDatetime(String datetime) { 23 | this.datetime = datetime; 24 | } 25 | 26 | public CarrierMovementDTO getCarrierMovement() { 27 | return carrierMovement; 28 | } 29 | 30 | public void setCarrierMovement(CarrierMovementDTO carrierMovement) { 31 | this.carrierMovement = carrierMovement; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/repository/LocationRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.repository; 2 | 3 | import com.gtw.business.domain.aggregate.location.Location; 4 | import com.gtw.business.domain.aggregate.location.LocationRepository; 5 | import com.gtw.business.infrastructure.db.LocationDO; 6 | import com.gtw.business.infrastructure.db.converter.LocationConverter; 7 | import com.gtw.business.infrastructure.db.mapper.LocationMapper; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class LocationRepositoryImpl implements LocationRepository { 13 | 14 | @Autowired 15 | private LocationMapper mapper; 16 | 17 | @Override 18 | public Location find(String code) { 19 | LocationDO locationDO = mapper.select(code); 20 | return LocationConverter.deserialize(locationDO); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/dto/OrderDTO.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.dto; 2 | 3 | import java.math.BigDecimal; 4 | import java.time.Instant; 5 | import java.util.List; 6 | 7 | import com.gtw.business.common.model.Address; 8 | import com.gtw.business.domain.aggregate.shop.Order; 9 | import com.gtw.business.domain.aggregate.shop.value.OrderItem; 10 | import lombok.Value; 11 | 12 | @Value 13 | public class OrderDTO { 14 | 15 | private String id; 16 | private List items; 17 | private BigDecimal totalPrice; 18 | private String status; 19 | private Address address; 20 | private Instant createdAt; 21 | 22 | public static OrderDTO trans(Order order) { 23 | return new OrderDTO(order.getId(), 24 | order.getItems(), 25 | order.getTotalPrice(), 26 | order.getStatus().name(), 27 | order.getAddress(), 28 | order.getCreatedAt()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/rpc/cargo/SalersService.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.rpc.cargo; 2 | 3 | import com.gtw.business.domain.aggregate.cargo.Cargo; 4 | import com.gtw.business.domain.aggregate.cargo.valueobject.EnterpriseSegment; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class SalersService { 10 | 11 | @Autowired 12 | private RemoteServiceAdapter adapter; 13 | 14 | public String getUserName(String phone) { 15 | UserDO user = this.adapter.getUser(phone); 16 | return null == user ? null : user.getName(); 17 | } 18 | 19 | public EnterpriseSegment deriveEnterpriseSegment(Cargo cargo) { 20 | return this.adapter.deriveEnterpriseSegment(cargo); 21 | } 22 | 23 | public boolean mayAccept(int cargoSize, Cargo cargo) { 24 | return this.adapter.mayAccept(cargoSize, cargo); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/cargo/valueobject/DeliverySpecification.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.cargo.valueobject; 2 | 3 | public class DeliverySpecification { 4 | 5 | private String originLocationCode; 6 | private String destinationLocationCode; 7 | 8 | public DeliverySpecification(String originLocationCode, String destinationLocationCode) { 9 | this.originLocationCode = originLocationCode; 10 | this.destinationLocationCode = destinationLocationCode; 11 | } 12 | 13 | public String getOriginLocationCode() { 14 | return originLocationCode; 15 | } 16 | 17 | public void setOriginLocationCode(String originLocationCode) { 18 | this.originLocationCode = originLocationCode; 19 | } 20 | 21 | public String getDestinationLocationCode() { 22 | return destinationLocationCode; 23 | } 24 | 25 | public void setDestinationLocationCode(String destinationLocationCode) { 26 | this.destinationLocationCode = destinationLocationCode; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/repository/CarrierMovementRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.repository; 2 | 3 | import com.gtw.business.domain.aggregate.carriermovement.CarrierMovement; 4 | import com.gtw.business.domain.aggregate.carriermovement.CarrierMovementRepository; 5 | import com.gtw.business.infrastructure.db.CarrierMovementDO; 6 | import com.gtw.business.infrastructure.db.converter.CarrierMovementConverter; 7 | import com.gtw.business.infrastructure.db.mapper.CarrierMovementMapper; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class CarrierMovementRepositoryImpl implements CarrierMovementRepository { 13 | 14 | @Autowired 15 | private CarrierMovementMapper mapper; 16 | 17 | @Override 18 | public CarrierMovement find(String id) { 19 | CarrierMovementDO carrierMovementDO = mapper.select(id); 20 | return CarrierMovementConverter.deserialize(carrierMovementDO); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/cmd/CreateOrderCommand.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command.cmd; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.List; 5 | 6 | import javax.validation.Valid; 7 | import javax.validation.constraints.Min; 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.NotEmpty; 10 | import javax.validation.constraints.NotNull; 11 | 12 | import com.gtw.business.common.model.Address; 13 | import lombok.Value; 14 | 15 | @Value 16 | public class CreateOrderCommand { 17 | @Valid 18 | @NotEmpty(message = "订单项不能为空") 19 | private List items; 20 | 21 | @NotNull(message = "订单地址不能为空") 22 | private Address address; 23 | 24 | @Value 25 | public class OrderItemCommand { 26 | @NotBlank(message = "产品ID不能为空") 27 | private String productId; 28 | 29 | @Min(value = 1, message = "产品数量必须大于0") 30 | private int count; 31 | 32 | @NotNull(message = "产品单价不能为空") 33 | private BigDecimal itemPrice; 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/shop/Shop.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.shop; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.common.model.Address; 6 | 7 | public class Shop { 8 | private String id; 9 | private String name; 10 | private Address address; 11 | //private List products; 12 | 13 | private ShopRepository shopRepository; 14 | private ProductRepository productRepository; 15 | 16 | public Shop(ShopRepository shopRepository, ProductRepository productRepository, String shopId) { 17 | this.shopRepository = shopRepository; 18 | this.productRepository = productRepository; 19 | this.id = shopId; 20 | } 21 | 22 | public Shop(String name, Address address) { 23 | this.name = name; 24 | this.address = address; 25 | } 26 | 27 | public List getProducts(ProductRepository productRepository) { 28 | return productRepository.getShopProducts(this.id); 29 | } 30 | 31 | public void purchase(List products) { 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/application/controller/IncidentLogginController.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.application.controller; 2 | 3 | import java.util.Date; 4 | 5 | import com.gtw.business.service.command.IncidentLoggingCmdService; 6 | import com.gtw.business.service.command.cmd.HandlingEventAddCommand; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | @RestController 14 | @RequestMapping("/event") 15 | public class IncidentLogginController { 16 | 17 | @Autowired 18 | private IncidentLoggingCmdService incidentLoggingCmdService; 19 | 20 | @RequestMapping(method = RequestMethod.POST) 21 | public void addHandlingEvent(@RequestBody HandlingEventAddCommand cmd) { 22 | cmd.setDatetime(new Date()); 23 | incidentLoggingCmdService.addHandlingEvent(cmd); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/rpc/cargo/RemoteServiceAdapter.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.rpc.cargo; 2 | 3 | import com.gtw.business.domain.aggregate.cargo.Cargo; 4 | import com.gtw.business.domain.aggregate.cargo.valueobject.EnterpriseSegment; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class RemoteServiceAdapter { 10 | 11 | @Autowired 12 | private RemoteServiceTranslator translator; 13 | 14 | // @Autowired 15 | // remoteService 16 | 17 | public UserDO getUser(String phone) { 18 | // User user = remoteService.getUser(phone); 19 | // return this.translator.toUserDO(user); 20 | return null; 21 | } 22 | 23 | public EnterpriseSegment deriveEnterpriseSegment(Cargo cargo) { 24 | // remote service 25 | // translator 26 | return EnterpriseSegment.FRUIT; 27 | } 28 | 29 | public boolean mayAccept(int cargoSize, Cargo cargo) { 30 | // remote service 31 | // translator 32 | return true; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/application/controller/TrackController.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.application.controller; 2 | 3 | import com.gtw.business.service.query.TrackQueryService; 4 | import com.gtw.business.service.query.dto.CargoHandlingEventDTO; 5 | import com.gtw.business.service.query.qry.EventFindbyCargoQry; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestMethod; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RestController 13 | @RequestMapping("/track") 14 | public class TrackController { 15 | 16 | @Autowired 17 | private TrackQueryService trackQueryService; 18 | 19 | @RequestMapping(value = "/{cargoId}", method = RequestMethod.GET) 20 | public CargoHandlingEventDTO query(@PathVariable String cargoId) { 21 | EventFindbyCargoQry qry = new EventFindbyCargoQry(); 22 | qry.setCargoId(cargoId); 23 | return trackQueryService.queryHistory(qry); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/shop/value/OrderItem.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.shop.value; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | @Getter 9 | @Builder 10 | public class OrderItem { 11 | private String productId; 12 | private int count; 13 | private BigDecimal itemPrice; 14 | 15 | public static OrderItem create(String productId, int count, BigDecimal itemPrice) { 16 | return OrderItem.builder() 17 | .productId(productId) 18 | .count(count) 19 | .itemPrice(itemPrice) 20 | .build(); 21 | } 22 | 23 | public BigDecimal totalPrice() { 24 | return itemPrice.multiply(BigDecimal.valueOf(count)); 25 | } 26 | 27 | public void updateCount(int count) { 28 | this.count = count; 29 | } 30 | 31 | public String getProductId() { 32 | return productId; 33 | } 34 | 35 | public int getCount() { 36 | return count; 37 | } 38 | 39 | public BigDecimal getItemPrice() { 40 | return itemPrice; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/impl/OrderQueryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.impl; 2 | 3 | import com.gtw.business.domain.aggregate.shop.Order; 4 | import com.gtw.business.domain.aggregate.shop.OrderRepository; 5 | import com.gtw.business.service.query.OrderQueryService; 6 | import com.gtw.business.service.query.dto.OrderDTO; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | /** 12 | * CQRS 13 | */ 14 | @Slf4j 15 | @Component 16 | public class OrderQueryServiceImpl implements OrderQueryService { 17 | 18 | private final OrderRepository orderRepository; 19 | 20 | public OrderQueryServiceImpl(OrderRepository orderRepository) { 21 | this.orderRepository = orderRepository; 22 | } 23 | 24 | /** 25 | * 这里为了说明CQRS对查询的使用 26 | * @param id 27 | * @return 28 | */ 29 | @Transactional(readOnly = true) 30 | public OrderDTO byId(String id) { 31 | Order order = orderRepository.byId(id); 32 | return OrderDTO.trans(order); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/mapper/handlingevent.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 19 | 23 | 24 | 25 | INSERT INTO `ddd_handling_event` (`id`, `cargo_id`, 26 | `eventtype`, 27 | `schedule_id`, `datetime`, `created_at`) 28 | VALUES 29 | (#{id}, 30 | #{cargoId}, #{eventType}, #{scheduleId}, #{datetime}, now()); 31 | 32 | 33 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/application/controller/RoutingController.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.application.controller; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.infrastructure.db.LocationDO; 6 | import com.gtw.business.service.query.RoutingQueryService; 7 | import com.gtw.business.service.query.dto.CarrierMovementDTO; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | @RestController 14 | @RequestMapping("/routing") 15 | public class RoutingController { 16 | 17 | @Autowired 18 | private RoutingQueryService routingQueryService; 19 | 20 | @RequestMapping(value = "/carrier", method = RequestMethod.GET) 21 | public List carriers() { 22 | return routingQueryService.queryCarriers(); 23 | } 24 | 25 | @RequestMapping(value = "/location", method = RequestMethod.GET) 26 | public List locations() { 27 | return routingQueryService.queryLocations(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/cmd/HandlingEventAddCommand.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command.cmd; 2 | 3 | import java.util.Date; 4 | 5 | public class HandlingEventAddCommand { 6 | 7 | private String cargoId; 8 | /** 9 | * 0:receive 1:load 2:unload 3:end 10 | */ 11 | private int eventType; 12 | 13 | private Date datetime; 14 | private String scheduleId; 15 | 16 | public String getCargoId() { 17 | return cargoId; 18 | } 19 | 20 | public void setCargoId(String cargoId) { 21 | this.cargoId = cargoId; 22 | } 23 | 24 | public int getEventType() { 25 | return eventType; 26 | } 27 | 28 | public void setEventType(int eventType) { 29 | this.eventType = eventType; 30 | } 31 | 32 | public String getScheduleId() { 33 | return scheduleId; 34 | } 35 | 36 | public void setScheduleId(String scheduleId) { 37 | this.scheduleId = scheduleId; 38 | } 39 | 40 | public Date getDatetime() { 41 | return datetime; 42 | } 43 | 44 | public void setDatetime(Date datetime) { 45 | this.datetime = datetime; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/shop/factory/ShopFactory.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.shop.factory; 2 | 3 | import com.gtw.business.common.model.Address; 4 | import com.gtw.business.domain.aggregate.shop.ProductRepository; 5 | import com.gtw.business.domain.aggregate.shop.ShopRepository; 6 | import com.gtw.business.domain.aggregate.shop.Shop; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class ShopFactory { 11 | private final ShopRepository shopRepository; 12 | private final ProductRepository productRepository; 13 | 14 | public ShopFactory(ShopRepository shopRepository, ProductRepository productRepository) { 15 | this.shopRepository = shopRepository; 16 | this.productRepository = productRepository; 17 | } 18 | 19 | public Shop load(String shopId) { 20 | // 为实体对象注入系统的singleton对象 21 | Shop shop = new Shop(shopRepository, productRepository, shopId); 22 | return shop; 23 | } 24 | 25 | public Shop create(String name, Address address) { 26 | // 为实体对象注入系统的singleton对象 27 | //Shop shop = new Shop(name, address); 28 | //shop. 29 | return null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/converter/HandlingEventConverter.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.converter; 2 | 3 | import com.gtw.business.domain.aggregate.handlingevent.EventTypeEnum; 4 | import com.gtw.business.domain.aggregate.handlingevent.HandlingEvent; 5 | import com.gtw.business.infrastructure.db.HandlingEventDO; 6 | 7 | public class HandlingEventConverter { 8 | 9 | public static HandlingEventDO serialize(HandlingEvent location) { 10 | HandlingEventDO target = new HandlingEventDO(); 11 | target.setId(location.id()); 12 | target.setCargoId(location.cargoId()); 13 | target.setDatetime(location.datetime()); 14 | target.setScheduleId(location.scheduleId()); 15 | target.setEventType(location.eventType().index()); 16 | return target; 17 | } 18 | 19 | public static HandlingEvent deserialize(HandlingEventDO locationDO) { 20 | HandlingEvent target = HandlingEvent.newHandlingEvent(locationDO.getId(), 21 | locationDO.getCargoId(), locationDO.getDatetime(), 22 | EventTypeEnum.of(locationDO.getEventType()), locationDO.getScheduleId()); 23 | return target; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/assembler/CargoDTOAssembler.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.assembler; 2 | 3 | import java.util.function.Function; 4 | 5 | import com.gtw.business.infrastructure.db.CargoDO; 6 | import com.gtw.business.infrastructure.db.LocationDO; 7 | import com.gtw.business.infrastructure.db.mapper.LocationMapper; 8 | import com.gtw.business.service.query.dto.CargoDTO; 9 | import org.springframework.beans.BeanUtils; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | public class CargoDTOAssembler implements Function { 15 | 16 | @Autowired 17 | private LocationMapper locationMapper; 18 | 19 | public CargoDTO apply(CargoDO t) { 20 | CargoDTO target = new CargoDTO(); 21 | BeanUtils.copyProperties(t, target); 22 | LocationDO select = locationMapper.select(t.getOriginLocationCode()); 23 | target.setOriginLocationName(select.getName()); 24 | select = locationMapper.select(t.getDestinationLocationCode()); 25 | target.setDestinationLocationName(select.getName()); 26 | return target; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/converter/CargoConverter.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.converter; 2 | 3 | import com.gtw.business.domain.aggregate.cargo.Cargo; 4 | import com.gtw.business.domain.aggregate.cargo.valueobject.DeliverySpecification; 5 | import com.gtw.business.infrastructure.db.CargoDO; 6 | 7 | public class CargoConverter { 8 | 9 | public static CargoDO serialize(Cargo cargo) { 10 | CargoDO target = new CargoDO(); 11 | target.setId(cargo.id()); 12 | target.setSenderPhone(cargo.sender()); 13 | target.setDescription(cargo.description()); 14 | DeliverySpecification delivery = cargo.delivery(); 15 | target.setDestinationLocationCode(delivery.getDestinationLocationCode()); 16 | target.setOriginLocationCode(delivery.getOriginLocationCode()); 17 | return target; 18 | } 19 | 20 | public static Cargo deserialize(CargoDO cargo) { 21 | Cargo target = Cargo.newCargo(cargo.getId(), cargo.getSenderPhone(), cargo.getDescription(), 22 | new DeliverySpecification(cargo.getOriginLocationCode(), 23 | cargo.getDestinationLocationCode())); 24 | return target; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/impl/IncidentLoggingCmdServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command.impl; 2 | 3 | import com.gtw.business.domain.aggregate.handlingevent.EventTypeEnum; 4 | import com.gtw.business.domain.aggregate.handlingevent.HandlingEvent; 5 | import com.gtw.business.domain.aggregate.handlingevent.HandlingEventRepository; 6 | import com.gtw.business.service.command.IncidentLoggingCmdService; 7 | import com.gtw.business.service.command.cmd.HandlingEventAddCommand; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | public class IncidentLoggingCmdServiceImpl implements IncidentLoggingCmdService { 13 | 14 | @Autowired 15 | private HandlingEventRepository handlingEventRepository; 16 | 17 | @Override 18 | public void addHandlingEvent(HandlingEventAddCommand cmd) { 19 | // validate 20 | 21 | // create 22 | HandlingEvent handlingEvent = HandlingEvent.newHandlingEvent(cmd.getCargoId(), 23 | cmd.getDatetime(), EventTypeEnum.of(cmd.getEventType()), cmd.getScheduleId()); 24 | 25 | // save 26 | handlingEventRepository.save(handlingEvent); 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/cmd/CargoBookCommand.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command.cmd; 2 | 3 | public class CargoBookCommand { 4 | 5 | private String senderPhone; 6 | private String description; 7 | private String originLocationCode; 8 | private String destinationLocationCode; 9 | 10 | public String getSenderPhone() { 11 | return senderPhone; 12 | } 13 | 14 | public void setSenderPhone(String senderPhone) { 15 | this.senderPhone = senderPhone; 16 | } 17 | 18 | public String getDescription() { 19 | return description; 20 | } 21 | 22 | public void setDescription(String description) { 23 | this.description = description; 24 | } 25 | 26 | public String getOriginLocationCode() { 27 | return originLocationCode; 28 | } 29 | 30 | public void setOriginLocationCode(String originLocationCode) { 31 | this.originLocationCode = originLocationCode; 32 | } 33 | 34 | public String getDestinationLocationCode() { 35 | return destinationLocationCode; 36 | } 37 | 38 | public void setDestinationLocationCode(String destinationLocationCode) { 39 | this.destinationLocationCode = destinationLocationCode; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/HandlingEventDO.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db; 2 | 3 | import java.util.Date; 4 | 5 | public class HandlingEventDO { 6 | 7 | private String id; 8 | 9 | private String cargoId; 10 | /** 11 | * 0:receiver 1:load 2:unload 3:end 12 | */ 13 | private int eventType; 14 | private Date datetime; 15 | private String scheduleId; 16 | 17 | public String getId() { 18 | return id; 19 | } 20 | 21 | public void setId(String id) { 22 | this.id = id; 23 | } 24 | 25 | public String getCargoId() { 26 | return cargoId; 27 | } 28 | 29 | public void setCargoId(String cargoId) { 30 | this.cargoId = cargoId; 31 | } 32 | 33 | public int getEventType() { 34 | return eventType; 35 | } 36 | 37 | public void setEventType(int eventType) { 38 | this.eventType = eventType; 39 | } 40 | 41 | public Date getDatetime() { 42 | return datetime; 43 | } 44 | 45 | public void setDatetime(Date datetime) { 46 | this.datetime = datetime; 47 | } 48 | 49 | public String getScheduleId() { 50 | return scheduleId; 51 | } 52 | 53 | public void setScheduleId(String scheduleId) { 54 | this.scheduleId = scheduleId; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/CargoDO.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db; 2 | 3 | public class CargoDO { 4 | 5 | private String id; 6 | private String senderPhone; 7 | private String description; 8 | private String originLocationCode; 9 | private String destinationLocationCode; 10 | 11 | public String getId() { 12 | return id; 13 | } 14 | 15 | public void setId(String id) { 16 | this.id = id; 17 | } 18 | 19 | public String getSenderPhone() { 20 | return senderPhone; 21 | } 22 | 23 | public void setSenderPhone(String senderPhone) { 24 | this.senderPhone = senderPhone; 25 | } 26 | 27 | public String getDescription() { 28 | return description; 29 | } 30 | 31 | public void setDescription(String description) { 32 | this.description = description; 33 | } 34 | 35 | public String getOriginLocationCode() { 36 | return originLocationCode; 37 | } 38 | 39 | public void setOriginLocationCode(String originLocationCode) { 40 | this.originLocationCode = originLocationCode; 41 | } 42 | 43 | public String getDestinationLocationCode() { 44 | return destinationLocationCode; 45 | } 46 | 47 | public void setDestinationLocationCode(String destinationLocationCode) { 48 | this.destinationLocationCode = destinationLocationCode; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/CarrierMovementDO.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db; 2 | 3 | import java.util.Date; 4 | 5 | public class CarrierMovementDO { 6 | 7 | private String scheduleId; 8 | private String fromLocationId; 9 | private String toLocationId; 10 | private Date startTime; 11 | private Date arriveTime; 12 | 13 | public CarrierMovementDO() {} 14 | 15 | public String getScheduleId() { 16 | return scheduleId; 17 | } 18 | 19 | public void setScheduleId(String scheduleId) { 20 | this.scheduleId = scheduleId; 21 | } 22 | 23 | public String getFromLocationId() { 24 | return fromLocationId; 25 | } 26 | 27 | public void setFromLocationId(String fromLocationId) { 28 | this.fromLocationId = fromLocationId; 29 | } 30 | 31 | public String getToLocationId() { 32 | return toLocationId; 33 | } 34 | 35 | public void setToLocationId(String toLocationId) { 36 | this.toLocationId = toLocationId; 37 | } 38 | 39 | public Date getStartTime() { 40 | return startTime; 41 | } 42 | 43 | public void setStartTime(Date startTime) { 44 | this.startTime = startTime; 45 | } 46 | 47 | public Date getArriveTime() { 48 | return arriveTime; 49 | } 50 | 51 | public void setArriveTime(Date arriveTime) { 52 | this.arriveTime = arriveTime; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/carriermovement/CarrierMovement.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.carriermovement; 2 | 3 | import java.util.Date; 4 | 5 | public class CarrierMovement { 6 | 7 | private String scheduleId; 8 | private String fromLocationId; 9 | private String toLocationId; 10 | private Date startTime; 11 | private Date arriveTime; 12 | 13 | public CarrierMovement() {} 14 | 15 | public String getScheduleId() { 16 | return scheduleId; 17 | } 18 | 19 | public void setScheduleId(String scheduleId) { 20 | this.scheduleId = scheduleId; 21 | } 22 | 23 | public String getFromLocationId() { 24 | return fromLocationId; 25 | } 26 | 27 | public void setFromLocationId(String fromLocationId) { 28 | this.fromLocationId = fromLocationId; 29 | } 30 | 31 | public String getToLocationId() { 32 | return toLocationId; 33 | } 34 | 35 | public void setToLocationId(String toLocationId) { 36 | this.toLocationId = toLocationId; 37 | } 38 | 39 | public Date getStartTime() { 40 | return startTime; 41 | } 42 | 43 | public void setStartTime(Date startTime) { 44 | this.startTime = startTime; 45 | } 46 | 47 | public Date getArriveTime() { 48 | return arriveTime; 49 | } 50 | 51 | public void setArriveTime(Date arriveTime) { 52 | this.arriveTime = arriveTime; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/assembler/CarrierMovementDTOAssembler.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.assembler; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.function.Function; 5 | 6 | import com.gtw.business.infrastructure.db.CarrierMovementDO; 7 | import com.gtw.business.infrastructure.db.mapper.LocationMapper; 8 | import com.gtw.business.service.query.dto.CarrierMovementDTO; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | public class CarrierMovementDTOAssembler 14 | implements Function { 15 | 16 | @Autowired 17 | private LocationMapper locationMapper; 18 | 19 | @Override 20 | public CarrierMovementDTO apply(CarrierMovementDO t) { 21 | CarrierMovementDTO dto = new CarrierMovementDTO(); 22 | dto.setStartTime(new SimpleDateFormat("yyyy-MM-dd HH:mm").format(t.getStartTime())); 23 | dto.setArriveTime(new SimpleDateFormat("yyyy-MM-dd HH:mm").format(t.getStartTime())); 24 | dto.setFromLocationId(t.getFromLocationId()); 25 | dto.setToLocationId(t.getToLocationId()); 26 | dto.setScheduleId(t.getScheduleId()); 27 | dto.setFromLocationName(locationMapper.select(t.getFromLocationId()).getName()); 28 | dto.setToLocationName(locationMapper.select(t.getToLocationId()).getName()); 29 | return dto; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/service/CargoDomainService.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.service; 2 | 3 | import java.util.Random; 4 | 5 | import com.gtw.business.domain.aggregate.cargo.Cargo; 6 | import com.gtw.business.domain.aggregate.handlingevent.HandlingEvent; 7 | import com.gtw.business.infrastructure.rpc.cargo.SalersService; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | public class CargoDomainService { 13 | 14 | public static final int MAX_CARGO_LIMIT = 10; 15 | public static final String PREFIX_ID = "CARGO-NO-"; 16 | 17 | @Autowired 18 | private SalersService salersService; 19 | 20 | /** 21 | * 货物物流id生成规则 22 | * 23 | * @return 24 | */ 25 | public static String nextCargoId() { 26 | return PREFIX_ID + (10000 + new Random().nextInt(9999)); 27 | } 28 | 29 | public void updateCargoSender(Cargo cargo, String senderPhone, HandlingEvent latestEvent) { 30 | 31 | if (null != latestEvent 32 | && !latestEvent.canModifyCargo()) { throw new IllegalArgumentException( 33 | "Sender cannot be changed after RECIEVER Status."); } 34 | 35 | cargo.changeSender(senderPhone); 36 | } 37 | 38 | public boolean mayAccept(int size, int cargoSize, Cargo cargo) { 39 | return size <= MAX_CARGO_LIMIT && salersService.mayAccept(cargoSize, cargo); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/impl/RoutingQueryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.impl; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import com.gtw.business.infrastructure.db.CarrierMovementDO; 7 | import com.gtw.business.infrastructure.db.LocationDO; 8 | import com.gtw.business.infrastructure.db.mapper.CarrierMovementMapper; 9 | import com.gtw.business.infrastructure.db.mapper.LocationMapper; 10 | import com.gtw.business.service.query.RoutingQueryService; 11 | import com.gtw.business.service.query.assembler.CarrierMovementDTOAssembler; 12 | import com.gtw.business.service.query.dto.CarrierMovementDTO; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Service; 15 | 16 | @Service 17 | public class RoutingQueryServiceImpl implements RoutingQueryService { 18 | 19 | @Autowired 20 | private CarrierMovementMapper carrierMovementMapper; 21 | @Autowired 22 | private LocationMapper locationMapper; 23 | @Autowired 24 | private CarrierMovementDTOAssembler converter; 25 | 26 | @Override 27 | public List queryCarriers() { 28 | List carrierMovementDOs = carrierMovementMapper.selectAll(); 29 | return carrierMovementDOs.stream().map(converter::apply).collect(Collectors.toList()); 30 | } 31 | 32 | @Override 33 | public List queryLocations() { 34 | return locationMapper.selectAll(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/shop/Order.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.shop; 2 | 3 | import java.math.BigDecimal; 4 | import java.time.Instant; 5 | import java.util.List; 6 | 7 | import com.gtw.business.common.model.Address; 8 | import com.gtw.business.domain.aggregate.shop.value.OrderItem; 9 | import lombok.Builder; 10 | import lombok.Getter; 11 | 12 | import static java.math.BigDecimal.ZERO; 13 | import static java.time.Instant.now; 14 | 15 | @Getter 16 | @Builder 17 | public class Order { 18 | private String id; 19 | private List items; 20 | private BigDecimal totalPrice; 21 | private OrderStatus status; 22 | private Address address; 23 | private Instant createdAt; 24 | 25 | public static Order create(String id, List items, Address address) { 26 | Order order = Order.builder() 27 | .id(id) 28 | .items(items) 29 | .totalPrice(calculateTotalPrice(items)) 30 | .status(OrderStatus.CREATED) 31 | .address(address) 32 | .createdAt(now()) 33 | .build(); 34 | // todo 发布事件 order.raiseCreatedEvent(id, items, address); 35 | return order; 36 | } 37 | 38 | private static BigDecimal calculateTotalPrice(List items) { 39 | return items.stream() 40 | .map(OrderItem::totalPrice) 41 | .reduce(ZERO, BigDecimal::add); 42 | } 43 | 44 | public enum OrderStatus { 45 | CREATED, 46 | PAID 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/static/js/cargo.js: -------------------------------------------------------------------------------- 1 | // global variable 2 | 3 | 4 | function getParameterByName(name, url) { 5 | if (!url) url = window.location.href; 6 | name = name.replace(/[\[\]]/g, '\\$&'); 7 | var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), 8 | results = regex.exec(url); 9 | if (!results) return null; 10 | if (!results[2]) return ''; 11 | return decodeURIComponent(results[2].replace(/\+/g, ' ')); 12 | } 13 | 14 | function getLocation(func) { 15 | var xmlhttp = new XMLHttpRequest(); 16 | var url = "/routing/location"; 17 | xmlhttp.open("GET", url, true); 18 | xmlhttp.send(); 19 | xmlhttp.onreadystatechange = function () { 20 | func(xmlhttp); 21 | } 22 | } 23 | 24 | function getCarrier(func) { 25 | var xmlhttp = new XMLHttpRequest(); 26 | var url = "/routing/carrier"; 27 | xmlhttp.open("GET", url, true); 28 | xmlhttp.send(); 29 | xmlhttp.onreadystatechange = function () { 30 | func(xmlhttp); 31 | } 32 | } 33 | 34 | function getCargo(cargoId, func) { 35 | var xmlhttp = new XMLHttpRequest(); 36 | var url = "/cargo/" + cargoId; 37 | xmlhttp.open("GET", url, true); 38 | xmlhttp.send(); 39 | xmlhttp.onreadystatechange = function () { 40 | func(xmlhttp); 41 | } 42 | } 43 | 44 | function getCargoEvent(cargoId, func) { 45 | var xmlhttp = new XMLHttpRequest(); 46 | var url = "/track/" + cargoId; 47 | xmlhttp.open("GET", url, true); 48 | xmlhttp.send(); 49 | xmlhttp.onreadystatechange = function () { 50 | func(xmlhttp); 51 | } 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/impl/CargoQueryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.impl; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import com.gtw.business.infrastructure.db.CargoDO; 7 | import com.gtw.business.infrastructure.db.mapper.CargoMapper; 8 | import com.gtw.business.service.query.CargoQueryService; 9 | import com.gtw.business.service.query.assembler.CargoDTOAssembler; 10 | import com.gtw.business.service.query.dto.CargoDTO; 11 | import com.gtw.business.service.query.qry.CargoFindbyCustomerQry; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Service; 14 | 15 | @Service 16 | public class CargoQueryServiceImpl implements CargoQueryService { 17 | 18 | @Autowired 19 | private CargoMapper cargoMapper; 20 | 21 | @Autowired 22 | private CargoDTOAssembler converter; 23 | 24 | @Override 25 | public List queryCargos() { 26 | List cargos = cargoMapper.selectAll(); 27 | return cargos.stream().map(converter::apply).collect(Collectors.toList()); 28 | } 29 | 30 | @Override 31 | public List queryCargos(CargoFindbyCustomerQry qry) { 32 | List cargos = cargoMapper.selectByCustomer(qry.getCustomerPhone()); 33 | return cargos.stream().map(converter::apply).collect(Collectors.toList()); 34 | } 35 | 36 | @Override 37 | public CargoDTO getCargo(String cargoId) { 38 | CargoDO select = cargoMapper.select(cargoId); 39 | return converter.apply(select); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/common/model/Address.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.common.model; 2 | 3 | import java.util.Objects; 4 | 5 | import lombok.AccessLevel; 6 | import lombok.NoArgsConstructor; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 9 | public class Address { 10 | private String province; 11 | private String city; 12 | private String detail; 13 | 14 | private Address(String province, String city, String detail) { 15 | this.province = province; 16 | this.city = city; 17 | this.detail = detail; 18 | } 19 | 20 | public static Address of(String province, String city, String detail) { 21 | return new Address(province, city, detail); 22 | } 23 | 24 | public Address changeDetailTo(String detail) { 25 | return new Address(this.province, this.city, detail); 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) { 31 | return true; 32 | } 33 | if (o == null || getClass() != o.getClass()) { 34 | return false; 35 | } 36 | Address address = (Address) o; 37 | return province.equals(address.province) && 38 | city.equals(address.city) && 39 | detail.equals(address.detail); 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return Objects.hash(province, city, detail); 45 | } 46 | 47 | public String getProvince() { 48 | return province; 49 | } 50 | 51 | public String getCity() { 52 | return city; 53 | } 54 | 55 | public String getDetail() { 56 | return detail; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/assembler/HandlingEventDTOAssembler.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.assembler; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.function.Function; 5 | 6 | import com.gtw.business.domain.aggregate.handlingevent.EventTypeEnum; 7 | import com.gtw.business.infrastructure.db.CarrierMovementDO; 8 | import com.gtw.business.infrastructure.db.HandlingEventDO; 9 | import com.gtw.business.infrastructure.db.mapper.CarrierMovementMapper; 10 | import com.gtw.business.service.query.dto.CarrierMovementDTO; 11 | import com.gtw.business.service.query.dto.HandlingEventDTO; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.util.StringUtils; 15 | 16 | @Component 17 | public class HandlingEventDTOAssembler implements Function { 18 | 19 | @Autowired 20 | private CarrierMovementMapper carrierMovementMapper; 21 | @Autowired 22 | private CarrierMovementDTOAssembler converter; 23 | 24 | public HandlingEventDTO apply(HandlingEventDO t) { 25 | HandlingEventDTO target = new HandlingEventDTO(); 26 | target.setEventType(EventTypeEnum.of(t.getEventType()).toString()); 27 | target.setDatetime(new SimpleDateFormat("yyyy-MM-dd HH:mm").format(t.getDatetime())); 28 | 29 | if (!StringUtils.isEmpty(t.getScheduleId())) { 30 | CarrierMovementDO carrierMovementDO = carrierMovementMapper.select(t.getScheduleId()); 31 | CarrierMovementDTO carrierMovement = converter.apply(carrierMovementDO); 32 | target.setCarrierMovement(carrierMovement); 33 | } 34 | return target; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/dto/CargoDTO.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.dto; 2 | 3 | public class CargoDTO { 4 | 5 | private String id; 6 | private String senderPhone; 7 | private String description; 8 | private String originLocationName; 9 | private String destinationLocationCode; 10 | private String destinationLocationName; 11 | 12 | public String getId() { 13 | return id; 14 | } 15 | 16 | public void setId(String id) { 17 | this.id = id; 18 | } 19 | 20 | public String getSenderPhone() { 21 | return senderPhone; 22 | } 23 | 24 | public void setSenderPhone(String senderPhone) { 25 | this.senderPhone = senderPhone; 26 | } 27 | 28 | public String getDescription() { 29 | return description; 30 | } 31 | 32 | public void setDescription(String description) { 33 | this.description = description; 34 | } 35 | 36 | public String getOriginLocationName() { 37 | return originLocationName; 38 | } 39 | 40 | public void setOriginLocationName(String originLocationName) { 41 | this.originLocationName = originLocationName; 42 | } 43 | 44 | public String getDestinationLocationName() { 45 | return destinationLocationName; 46 | } 47 | 48 | public void setDestinationLocationName(String destinationLocationName) { 49 | this.destinationLocationName = destinationLocationName; 50 | } 51 | 52 | public String getDestinationLocationCode() { 53 | return destinationLocationCode; 54 | } 55 | 56 | public void setDestinationLocationCode(String destinationLocationCode) { 57 | this.destinationLocationCode = destinationLocationCode; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/repository/HandlingEventRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.repository; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import com.gtw.business.domain.aggregate.handlingevent.HandlingEvent; 7 | import com.gtw.business.domain.aggregate.handlingevent.HandlingEventRepository; 8 | import com.gtw.business.infrastructure.db.HandlingEventDO; 9 | import com.gtw.business.infrastructure.db.converter.HandlingEventConverter; 10 | import com.gtw.business.infrastructure.db.mapper.HandlingEventMapper; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Component; 13 | 14 | @Component 15 | public class HandlingEventRepositoryImpl implements HandlingEventRepository { 16 | 17 | @Autowired 18 | private HandlingEventMapper mapper; 19 | 20 | @Override 21 | public List findByCargo(String cargoId) { 22 | List handlingEventDOs = mapper.selectByCargo(cargoId); 23 | 24 | return handlingEventDOs.stream().map(HandlingEventConverter::deserialize) 25 | .collect(Collectors.toList()); 26 | } 27 | 28 | @Override 29 | public List findByScheduleId(String scheduleId) { 30 | List handlingEventDOs = mapper.selectByScheduleId(scheduleId); 31 | 32 | return handlingEventDOs.stream().map(HandlingEventConverter::deserialize) 33 | .collect(Collectors.toList()); 34 | } 35 | 36 | @Override 37 | public void save(HandlingEvent handlingEvent) { 38 | HandlingEventDO handlingEventDO = HandlingEventConverter.serialize(handlingEvent); 39 | mapper.save(handlingEventDO); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/handlingevent/HandlingEvent.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.handlingevent; 2 | 3 | import java.util.Date; 4 | import java.util.UUID; 5 | 6 | public class HandlingEvent { 7 | 8 | private String id; 9 | private String cargoId; 10 | private Date datetime; 11 | private EventTypeEnum eventType; 12 | 13 | private String scheduleId; 14 | 15 | private HandlingEvent(String id, String cargoId, Date time, EventTypeEnum eventType) { 16 | this.id = id; 17 | this.cargoId = cargoId; 18 | this.eventType = eventType; 19 | this.datetime = time; 20 | } 21 | 22 | public static HandlingEvent newHandlingEvent(String cargoId, Date time, EventTypeEnum eventType, 23 | String scheduleId) { 24 | return newHandlingEvent(UUID.randomUUID().toString(), cargoId, time, eventType, scheduleId); 25 | } 26 | 27 | public static HandlingEvent newHandlingEvent(String id, String cargoId, Date time, 28 | EventTypeEnum eventType, String scheduleId) { 29 | HandlingEvent handlingEvent = new HandlingEvent(id, cargoId, time, eventType); 30 | handlingEvent.scheduleId = scheduleId; 31 | return handlingEvent; 32 | } 33 | 34 | public boolean canModifyCargo() { 35 | return eventType == EventTypeEnum.RECIEVE; 36 | } 37 | 38 | public Date datetime() { 39 | return datetime; 40 | } 41 | 42 | public String cargoId() { 43 | return cargoId; 44 | } 45 | 46 | public String id() { 47 | return id; 48 | } 49 | 50 | public EventTypeEnum eventType() { 51 | return eventType; 52 | } 53 | 54 | public String scheduleId() { 55 | return scheduleId; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/infrastructure/db/repository/CargoRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.infrastructure.db.repository; 2 | 3 | import com.gtw.business.domain.aggregate.cargo.Cargo; 4 | import com.gtw.business.domain.aggregate.cargo.CargoRepository; 5 | import com.gtw.business.domain.aggregate.cargo.valueobject.EnterpriseSegment; 6 | import com.gtw.business.infrastructure.db.CargoDO; 7 | import com.gtw.business.infrastructure.db.converter.CargoConverter; 8 | import com.gtw.business.infrastructure.db.mapper.CargoMapper; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | public class CargoRepositoryImpl implements CargoRepository { 14 | 15 | @Autowired 16 | private CargoMapper cargoMapper; 17 | 18 | @Override 19 | public Cargo find(String id) { 20 | CargoDO cargoDO = cargoMapper.select(id); 21 | Cargo cargo = CargoConverter.deserialize(cargoDO); 22 | return cargo; 23 | } 24 | 25 | @Override 26 | public void save(Cargo cargo) { 27 | CargoDO cargoDO = CargoConverter.serialize(cargo); 28 | CargoDO data = cargoMapper.select(cargoDO.getId()); 29 | if (null == data) { 30 | cargoMapper.save(cargoDO); 31 | } else { 32 | cargoMapper.update(cargoDO); 33 | } 34 | } 35 | 36 | @Override 37 | public void remove(String id) { 38 | cargoMapper.remove(id); 39 | } 40 | 41 | @Override 42 | public int sizeByCustomer(String customerPhone) { 43 | return cargoMapper.countByCustomer(customerPhone); 44 | } 45 | 46 | @Override 47 | public int sizeByEnterpriseSegment(EnterpriseSegment enterpriseSegment) { 48 | // cargoMapper 49 | return 20; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/impl/OrderCommandServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command.impl; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import com.gtw.business.domain.aggregate.shop.Order; 7 | import com.gtw.business.domain.aggregate.shop.OrderRepository; 8 | import com.gtw.business.domain.aggregate.shop.factory.OrderFactory; 9 | import com.gtw.business.domain.aggregate.shop.value.OrderItem; 10 | import com.gtw.business.service.command.OrderCommandService; 11 | import com.gtw.business.service.command.cmd.CreateOrderCommand; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | /** 18 | * 业务逻辑层,很薄的一层,实际业务功能是在domain中实现的 19 | */ 20 | @Service 21 | @Slf4j 22 | public class OrderCommandServiceImpl implements OrderCommandService { 23 | @Autowired(required = false) 24 | private OrderFactory orderFactory; 25 | @Autowired(required = false) 26 | private OrderRepository orderRepository; 27 | 28 | /** 29 | * 事务是在业务逻辑层进行控制的 30 | * @param command 31 | * @return 32 | */ 33 | @Transactional 34 | @Override 35 | public String createOrder(CreateOrderCommand command) { 36 | List items = command.getItems().stream() 37 | .map(item -> OrderItem.create(item.getProductId(), 38 | item.getCount(), 39 | item.getItemPrice())) 40 | .collect(Collectors.toList()); 41 | 42 | Order order = orderFactory.create(items, command.getAddress()); 43 | orderRepository.save(order); 44 | log.info("Created order[{}].", order.getId()); 45 | return order.getId(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/mapper/cargo.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 10 | 11 | 13 | 15 | 16 | 17 | 21 | 24 | 29 | 32 | 33 | INSERT INTO ddd_cargo (`id`, 34 | `sender_phone`, 35 | `description`, 36 | `originLocation_code`, 37 | `destinationLocation_code`, 38 | `created_at`) 39 | VALUES (#{id}, #{senderPhone}, 40 | #{description}, 41 | #{originLocationCode}, 42 | #{destinationLocationCode}, 43 | now()) 44 | 45 | 46 | UPDATE ddd_cargo 47 | set sender_phone = #{senderPhone}, 48 | description = #{description}, 49 | originLocation_code = #{originLocationCode}, 50 | destinationLocation_code = #{destinationLocationCode} 51 | WHERE id = #{id} 52 | 53 | 54 | DELETE FROM ddd_cargo WHERE id = 55 | #{id} 56 | 57 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/dto/CarrierMovementDTO.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.dto; 2 | 3 | public class CarrierMovementDTO { 4 | 5 | private String scheduleId; 6 | private String fromLocationId; 7 | private String fromLocationName; 8 | private String toLocationId; 9 | private String toLocationName; 10 | private String startTime; 11 | private String arriveTime; 12 | 13 | public String getScheduleId() { 14 | return scheduleId; 15 | } 16 | 17 | public void setScheduleId(String scheduleId) { 18 | this.scheduleId = scheduleId; 19 | } 20 | 21 | public String getFromLocationName() { 22 | return fromLocationName; 23 | } 24 | 25 | public void setFromLocationName(String fromLocationName) { 26 | this.fromLocationName = fromLocationName; 27 | } 28 | 29 | public String getToLocationName() { 30 | return toLocationName; 31 | } 32 | 33 | public void setToLocationName(String toLocationName) { 34 | this.toLocationName = toLocationName; 35 | } 36 | 37 | public String getStartTime() { 38 | return startTime; 39 | } 40 | 41 | public void setStartTime(String startTime) { 42 | this.startTime = startTime; 43 | } 44 | 45 | public String getArriveTime() { 46 | return arriveTime; 47 | } 48 | 49 | public void setArriveTime(String arriveTime) { 50 | this.arriveTime = arriveTime; 51 | } 52 | 53 | public String getFromLocationId() { 54 | return fromLocationId; 55 | } 56 | 57 | public void setFromLocationId(String fromLocationId) { 58 | this.fromLocationId = fromLocationId; 59 | } 60 | 61 | public String getToLocationId() { 62 | return toLocationId; 63 | } 64 | 65 | public void setToLocationId(String toLocationId) { 66 | this.toLocationId = toLocationId; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/domain/aggregate/cargo/Cargo.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.domain.aggregate.cargo; 2 | 3 | import com.gtw.business.domain.aggregate.cargo.valueobject.DeliverySpecification; 4 | 5 | /** 6 | * 货物 7 | */ 8 | public class Cargo { 9 | 10 | private String id; 11 | private String senderPhone; 12 | private String description; 13 | /** 14 | * 交付详情 15 | */ 16 | private DeliverySpecification delivery; 17 | 18 | public Cargo(String id) { 19 | this.id = id; 20 | } 21 | 22 | public Cargo() {} 23 | 24 | /** 25 | * Factory method:预订新的货物 26 | * 27 | * @param senderPhone 28 | * @param description 29 | * @param delivery 30 | * @return 31 | */ 32 | public static Cargo newCargo(String id, String senderPhone, String description, 33 | DeliverySpecification delivery) { 34 | Cargo cargo = new Cargo(id); 35 | cargo.senderPhone = senderPhone; 36 | cargo.description = description; 37 | cargo.delivery = delivery; 38 | return cargo; 39 | } 40 | 41 | public String id() { 42 | return id; 43 | } 44 | 45 | public String sender() { 46 | return senderPhone; 47 | } 48 | 49 | public String description() { 50 | return description; 51 | } 52 | 53 | public DeliverySpecification delivery() { 54 | return delivery; 55 | } 56 | 57 | public void setDelivery(DeliverySpecification delivery) { 58 | this.delivery = delivery; 59 | } 60 | 61 | public void changeDelivery(String destinationLocationCode) { 62 | if (this.delivery 63 | .getOriginLocationCode().equals(destinationLocationCode)) { throw new IllegalArgumentException( 64 | "destination and origin location cannot be the same."); } 65 | this.delivery.setDestinationLocationCode(destinationLocationCode); 66 | } 67 | 68 | public void changeSender(String senderPhone) { 69 | this.senderPhone = senderPhone; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/query/impl/TrackQueryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.query.impl; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import com.gtw.business.infrastructure.db.CargoDO; 7 | import com.gtw.business.infrastructure.db.HandlingEventDO; 8 | import com.gtw.business.infrastructure.db.mapper.CargoMapper; 9 | import com.gtw.business.infrastructure.db.mapper.HandlingEventMapper; 10 | import com.gtw.business.service.query.TrackQueryService; 11 | import com.gtw.business.service.query.assembler.CargoDTOAssembler; 12 | import com.gtw.business.service.query.assembler.HandlingEventDTOAssembler; 13 | import com.gtw.business.service.query.dto.CargoDTO; 14 | import com.gtw.business.service.query.dto.CargoHandlingEventDTO; 15 | import com.gtw.business.service.query.dto.HandlingEventDTO; 16 | import com.gtw.business.service.query.qry.EventFindbyCargoQry; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.stereotype.Service; 19 | 20 | @Service 21 | public class TrackQueryServiceImpl implements TrackQueryService { 22 | 23 | @Autowired 24 | private HandlingEventMapper handlingEventMapper; 25 | 26 | @Autowired 27 | private CargoMapper cargoMapper; 28 | 29 | @Autowired 30 | private CargoDTOAssembler converter; 31 | @Autowired 32 | private HandlingEventDTOAssembler handlingEventDTOAssembler; 33 | 34 | @Override 35 | public CargoHandlingEventDTO queryHistory(EventFindbyCargoQry qry) { 36 | 37 | CargoDO cargo = cargoMapper.select(qry.getCargoId()); 38 | List events = handlingEventMapper.selectByCargo(qry.getCargoId()); 39 | 40 | // convertor 41 | CargoDTO cargoDTO = converter.apply(cargo); 42 | List dtoEvents = events.stream().map(handlingEventDTOAssembler::apply) 43 | .collect(Collectors.toList()); 44 | 45 | CargoHandlingEventDTO cargoHandlingEventDTO = new CargoHandlingEventDTO(); 46 | cargoHandlingEventDTO.setCargo(cargoDTO); 47 | cargoHandlingEventDTO.setEvents(dtoEvents); 48 | 49 | return cargoHandlingEventDTO; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/application/controller/CargoController.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.application.controller; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.service.command.CargoCmdService; 6 | import com.gtw.business.service.command.cmd.CargoBookCommand; 7 | import com.gtw.business.service.command.cmd.CargoDeleteCommand; 8 | import com.gtw.business.service.command.cmd.CargoDeliveryUpdateCommand; 9 | import com.gtw.business.service.command.cmd.CargoSenderUpdateCommand; 10 | import com.gtw.business.service.query.CargoQueryService; 11 | import com.gtw.business.service.query.dto.CargoDTO; 12 | import com.gtw.business.service.query.qry.CargoFindbyCustomerQry; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.util.StringUtils; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RequestMethod; 19 | import org.springframework.web.bind.annotation.RequestParam; 20 | import org.springframework.web.bind.annotation.RestController; 21 | 22 | @RestController 23 | @RequestMapping("/cargo") 24 | public class CargoController { 25 | 26 | @Autowired 27 | CargoQueryService cargoQueryService; 28 | @Autowired 29 | CargoCmdService cargoCmdService; 30 | 31 | /** 32 | * 货物预定 33 | * @param cargoBookCommand 预定Command 34 | */ 35 | @RequestMapping(method = RequestMethod.POST) 36 | public void book(@RequestBody CargoBookCommand cargoBookCommand) { 37 | cargoCmdService.bookCargo(cargoBookCommand); 38 | } 39 | 40 | @RequestMapping(value = "/{cargoId}/delivery", method = RequestMethod.PUT) 41 | public void modifyDestinationLocationCode(@PathVariable String cargoId, 42 | @RequestBody CargoDeliveryUpdateCommand cmd) { 43 | cmd.setCargoId(cargoId); 44 | cargoCmdService.updateCargoDelivery(cmd); 45 | } 46 | @RequestMapping(value = "/{cargoId}/sender", method = RequestMethod.PUT) 47 | public void modifySender(@PathVariable String cargoId, 48 | @RequestBody CargoSenderUpdateCommand cmd) { 49 | cmd.setCargoId(cargoId); 50 | cargoCmdService.updateCargoSender(cmd); 51 | } 52 | 53 | @RequestMapping(value = "/{cargoId}", method = RequestMethod.DELETE) 54 | public void removeCargo(@PathVariable String cargoId) { 55 | CargoDeleteCommand cmd = new CargoDeleteCommand(); 56 | cmd.setCargoId(cargoId); 57 | cargoCmdService.deleteCargo(cmd); 58 | } 59 | 60 | @RequestMapping(value = "/{cargoId}", method = RequestMethod.GET) 61 | public CargoDTO cargo(@PathVariable String cargoId) { 62 | return cargoQueryService.getCargo(cargoId); 63 | } 64 | 65 | @RequestMapping(method = RequestMethod.GET) 66 | public List queryCargos( 67 | @RequestParam(value = "phone", required = false) String phone) { 68 | if (!StringUtils.isEmpty(phone)) { 69 | CargoFindbyCustomerQry qry = new CargoFindbyCustomerQry(); 70 | qry.setCustomerPhone(phone); 71 | return cargoQueryService.queryCargos(qry); 72 | } 73 | return cargoQueryService.queryCargos(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | business-demo 7 | 8 | 9 | 10 | org.springframework.boot 11 | spring-boot-starter-parent 12 | 2.3.4.RELEASE 13 | 14 | 15 | 16 | com.gtw 17 | ddd-demo 18 | 1.0.0-SNAPSHOT 19 | ddd-demo 20 | pom 21 | Demo project for DDD 22 | 23 | 24 | UTF-8 25 | UTF-8 26 | 1.8 27 | 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-web 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-configuration-processor 44 | true 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-devtools 50 | runtime 51 | true 52 | 53 | 54 | 55 | 56 | org.mybatis.spring.boot 57 | mybatis-spring-boot-starter 58 | 1.3.2 59 | 60 | 61 | mysql 62 | mysql-connector-java 63 | 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-starter-test 68 | test 69 | 70 | 71 | org.junit.vintage 72 | junit-vintage-engine 73 | 74 | 75 | 76 | 77 | 78 | org.projectlombok 79 | lombok 80 | true 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | org.springframework.boot 89 | spring-boot-maven-plugin 90 | 91 | 92 | true 93 | 94 | 95 | 96 | 97 | maven-compiler-plugin 98 | 99 | 1.8 100 | 1.8 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /business-demo/src/main/java/com/gtw/business/service/command/impl/CargoCmdServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gtw.business.service.command.impl; 2 | 3 | import java.util.List; 4 | 5 | import com.gtw.business.domain.aggregate.cargo.Cargo; 6 | import com.gtw.business.domain.aggregate.cargo.CargoRepository; 7 | import com.gtw.business.domain.aggregate.cargo.valueobject.DeliverySpecification; 8 | import com.gtw.business.domain.aggregate.cargo.valueobject.EnterpriseSegment; 9 | import com.gtw.business.domain.aggregate.handlingevent.HandlingEvent; 10 | import com.gtw.business.domain.aggregate.handlingevent.HandlingEventRepository; 11 | import com.gtw.business.domain.aggregate.cargo.CargoBookEvent; 12 | import com.gtw.business.infrastructure.event.publisher.CargoBookEventPublisher; 13 | import com.gtw.business.domain.service.CargoDomainService; 14 | import com.gtw.business.infrastructure.rpc.cargo.SalersService; 15 | import com.gtw.business.service.command.CargoCmdService; 16 | import com.gtw.business.service.command.cmd.CargoBookCommand; 17 | import com.gtw.business.service.command.cmd.CargoDeleteCommand; 18 | import com.gtw.business.service.command.cmd.CargoDeliveryUpdateCommand; 19 | import com.gtw.business.service.command.cmd.CargoSenderUpdateCommand; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.stereotype.Service; 22 | import org.springframework.util.CollectionUtils; 23 | 24 | @Service 25 | public class CargoCmdServiceImpl implements CargoCmdService { 26 | 27 | @Autowired 28 | private CargoRepository cargoRepository; 29 | @Autowired 30 | private HandlingEventRepository handlingEventRepository; 31 | @Autowired 32 | private CargoDomainService cargoDomainService; 33 | @Autowired 34 | private SalersService salersService; 35 | @Autowired 36 | CargoBookEventPublisher cargoBookEventPublisher; 37 | 38 | @Override 39 | public void bookCargo(CargoBookCommand cargoBookCommand) { 40 | // 参数校验 41 | 42 | // 协调领域模型 43 | DeliverySpecification delivery = new DeliverySpecification( 44 | cargoBookCommand.getOriginLocationCode(), 45 | cargoBookCommand.getDestinationLocationCode()); 46 | 47 | Cargo cargo = Cargo.newCargo(CargoDomainService.nextCargoId(), cargoBookCommand.getSenderPhone(), 48 | cargoBookCommand.getDescription(), delivery); 49 | 50 | // 流程编排 51 | int size = cargoRepository.sizeByCustomer(cargoBookCommand.getSenderPhone()); 52 | EnterpriseSegment enterpriseSegment = salersService.deriveEnterpriseSegment(cargo); 53 | int sizeCargo = cargoRepository.sizeByEnterpriseSegment(enterpriseSegment); 54 | 55 | if (!cargoDomainService.mayAccept(size, sizeCargo, 56 | cargo)) { throw new IllegalArgumentException( 57 | cargoBookCommand.getSenderPhone() + " cannot book cargo, exceed the limit: " 58 | + CargoDomainService.MAX_CARGO_LIMIT); } 59 | 60 | // 持久化 61 | cargoRepository.save(cargo); 62 | 63 | // 发布领域事件 64 | cargoBookEventPublisher.publish(new CargoBookEvent(cargo)); 65 | } 66 | 67 | @Override 68 | public void updateCargoDelivery(CargoDeliveryUpdateCommand cmd) { 69 | // validate 70 | 71 | // find 72 | Cargo cargo = cargoRepository.find(cmd.getCargoId()); 73 | 74 | // domain logic 75 | cargo.changeDelivery(cmd.getDestinationLocationCode()); 76 | 77 | // save 78 | cargoRepository.save(cargo); 79 | } 80 | 81 | @Override 82 | public void updateCargoSender(CargoSenderUpdateCommand cmd) { 83 | // find 84 | Cargo cargo = cargoRepository.find(cmd.getCargoId()); 85 | List events = handlingEventRepository.findByCargo(cmd.getCargoId()); 86 | 87 | // domain service 88 | cargoDomainService.updateCargoSender(cargo, cmd.getSenderPhone(), CollectionUtils.isEmpty(events) ? null : events.get(0)); 89 | 90 | // save 91 | cargoRepository.save(cargo); 92 | } 93 | 94 | @Override 95 | public void deleteCargo(CargoDeleteCommand cmd) { 96 | cargoRepository.remove(cmd.getCargoId()); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/static/modifyphone.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cargo Admin 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 | 25 | 28 | 29 | 30 | 修改发件人手机号 31 |
32 | 33 |
34 |
35 |
36 |
37 |
发件人:
38 |
39 |
40 | 41 |
42 |
43 |
46 |
47 | 48 |
49 |
50 |
51 | 52 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/static/css/cargo.css: -------------------------------------------------------------------------------- 1 | *{margin: 0;padding: 0;list-style: none;} 2 | /* 3 | KISSY CSS Reset 4 | 理念:1. reset 的目的不是清除浏览器的默认样式,这仅是部分工作。清除和重置是紧密不可分的。 5 | 2. reset 的目的不是让默认样式在所有浏览器下一致,而是减少默认样式有可能带来的问题。 6 | 3. reset 期望提供一套普适通用的基础样式。但没有银弹,推荐根据具体需求,裁剪和修改后再使用。 7 | 特色:1. 适应中文;2. 基于最新主流浏览器。 8 | 维护:玉伯, 正淳 9 | */ 10 | 11 | /** 清除内外边距 **/ 12 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */ 13 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */ 14 | pre, /* text formatting elements 文本格式元素 */ 15 | form, fieldset, legend, button, input, textarea, /* form elements 表单元素 */ 16 | th, td /* table elements 表格元素 */ { 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | /** 设置默认字体 **/ 22 | body, 23 | button, input, select, textarea /* for ie */ { 24 | font: 16px/1.5 sans-serif, tahoma, arial, \5b8b\4f53; 25 | font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; 26 | font-weight: normal; 27 | } 28 | h1, h2, h3, h4, h5, h6 { font-size: 100%; } 29 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */ 30 | code, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */ 31 | small { font-size: 14px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */ 32 | 33 | /** 重置列表元素 **/ 34 | ul, ol { list-style: none; } 35 | 36 | /** 重置文本格式元素 **/ 37 | a { text-decoration: none; } 38 | a:hover { text-decoration: underline; } 39 | 40 | 41 | /** 重置表单元素 **/ 42 | legend { color: #000; } /* for ie6 */ 43 | fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */ 44 | button, input, select, textarea { font-size: 100%; } /* 使得表单元素在 ie 下能继承字体大小 */ 45 | /* 注:optgroup 无法扶正 */ 46 | 47 | /** 重置表格元素 **/ 48 | table { border-collapse: collapse; border-spacing: 0; } 49 | 50 | /* 清除浮动 */ 51 | .ks-clear:after, .clear:after { 52 | content: '\20'; 53 | display: block; 54 | height: 0; 55 | clear: both; 56 | } 57 | .ks-clear, .clear { 58 | *zoom: 1; 59 | } 60 | 61 | 62 | header { 63 | background-color: #0B7CAF; 64 | height: 138px; 65 | line-height: 68px; 66 | padding: 0 20px; 67 | color: #fff; 68 | position: absolute; 69 | top: 0; 70 | left: 0; 71 | right: 0; 72 | z-index: -1; 73 | } 74 | 75 | h1, h2, h3, h4, h5, h6 { 76 | font-weight: 400; 77 | } 78 | 79 | header h1 { 80 | font-size: 28px; 81 | } 82 | 83 | h2 { 84 | font-size: 24px; 85 | } 86 | 87 | h3 { 88 | font-weight: 700; 89 | border: 1px solid #ddd; 90 | display: inline-block; 91 | border-radius: 2px; 92 | padding: 0px 4px; 93 | 94 | } 95 | 96 | .article { 97 | padding: 20px; 98 | max-width: 1140px; 99 | margin: 0px auto; 100 | box-shadow: 0px 0px 4px #ccc; 101 | background: #fff; 102 | margin-top: 80px; 103 | } 104 | 105 | .article>div:not(#toolbox) { 106 | padding: 10px 10px; 107 | } 108 | 109 | hr { 110 | color: #dfdfdf; 111 | } 112 | 113 | a:hover { 114 | text-decoration: none; 115 | } 116 | 117 | ol>li { 118 | margin: 24px; 119 | } 120 | 121 | ol>li>ul { 122 | background-color: #504949; 123 | color: #fff; 124 | padding: 8px 16px; 125 | } 126 | 127 | ul.change>li { 128 | margin: 10px 20px; 129 | } 130 | 131 | ul { 132 | margin: 10px 0; 133 | } 134 | 135 | ul>li { 136 | margin: 10px 0; 137 | } 138 | 139 | .comment { 140 | color: #d8fa3c; 141 | margin-left: 16px; 142 | font-size: 14px; 143 | } 144 | 145 | .missing>s, .missing>del { 146 | padding: 0 4px; 147 | } 148 | 149 | s, del, .missing, del.comment { 150 | color: #F44336; 151 | } 152 | 153 | .missing>.comment { 154 | color: #F44336; 155 | } 156 | 157 | 158 | .header { 159 | display: inline-block; 160 | } 161 | 162 | #toolbox { 163 | height:38px; 164 | border-bottom: 1px solid #ddd; 165 | } 166 | 167 | 168 | 169 | .tool { 170 | float:right; 171 | text-decoration: none; 172 | } 173 | 174 | #main { 175 | height:480px; 176 | margin:0 auto; 177 | padding-top:24px; 178 | } 179 | 180 | button { 181 | padding: 4px 8px; 182 | margin:0 4px; 183 | background-color: #0B7CAF; 184 | color: #fff; 185 | border: none; 186 | font-weight: bold; 187 | cursor:pointer; 188 | } 189 | .button_small { 190 | padding: 2px 4px; 191 | margin:0 4px; 192 | background-color: #eee; 193 | color: #333; 194 | font-weight: normal; 195 | border: none; 196 | font-size: 14px; 197 | cursor:pointer; 198 | } 199 | 200 | #cargo { 201 | margin: 24px 0; 202 | padding: 4px 0; 203 | border-bottom: solid 1px #ccc; 204 | } 205 | #cargo > div { 206 | display: inline-block; 207 | padding: 0 16px; 208 | } 209 | 210 | .form > div { 211 | padding: 8px 0px; 212 | } 213 | .form > div > div { 214 | display: inline-block; 215 | padding: 0 0px; 216 | } 217 | .form > div > div.label{ 218 | min-width: 160px; 219 | text-align: right; 220 | } 221 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cargo Admin 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |
20 |
21 | Cargo Management System 22 |
23 | 24 | 28 |
29 |
30 | 52 | 53 |
54 |
55 | 56 | 108 | 109 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/mysql_init.sql: -------------------------------------------------------------------------------- 1 | # Dump of table ddd_cargo 2 | # ------------------------------------------------------------ 3 | 4 | DROP TABLE IF EXISTS `ddd_cargo`; 5 | 6 | CREATE TABLE `ddd_cargo` ( 7 | `id` varchar(45) NOT NULL, 8 | `sender_phone` varchar(45) NOT NULL DEFAULT '', 9 | `description` varchar(500) DEFAULT NULL, 10 | `originLocation_code` varchar(20) NOT NULL DEFAULT '', 11 | `destinationLocation_code` varchar(20) NOT NULL DEFAULT '', 12 | `created_at` datetime DEFAULT NULL, 13 | `updated_at` datetime DEFAULT NULL, 14 | PRIMARY KEY (`id`), 15 | KEY `sender` (`sender_phone`) 16 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 17 | 18 | LOCK TABLES `ddd_cargo` WRITE; 19 | /*!40000 ALTER TABLE `ddd_cargo` DISABLE KEYS */; 20 | 21 | INSERT INTO `ddd_cargo` (`id`, `sender_phone`, `description`, `originLocation_code`, `destinationLocation_code`, `created_at`, `updated_at`) 22 | VALUES 23 | ('CARGO-NO-15218','0571001','电视机','HZ','NJ','2019-03-25 12:45:02',NULL), 24 | ('CARGO-NO-16193','0571002','书本','HZ','NJ','2019-03-19 19:39:05',NULL), 25 | ('CARGO-NO-16383','0571003','会员','HZ','BJ','2019-03-25 12:42:56',NULL), 26 | ('CARGO-NO-16759','0571004','香蕉、苹果和梨子','HZ','BJ','2019-03-21 20:15:55',NULL), 27 | ('CARGO-NO-19640','0571112','羽毛球','HZ','BJ','2019-03-25 12:44:32',NULL), 28 | ('CARGO-NO-34555','90010','字帖、文具用品','HZ','NJ','2019-03-13 19:50:58',NULL); 29 | 30 | /*!40000 ALTER TABLE `ddd_cargo` ENABLE KEYS */; 31 | UNLOCK TABLES; 32 | 33 | 34 | # Dump of table ddd_carrier_movement 35 | # ------------------------------------------------------------ 36 | 37 | DROP TABLE IF EXISTS `ddd_carrier_movement`; 38 | 39 | CREATE TABLE `ddd_carrier_movement` ( 40 | `schedule_id` varchar(45) NOT NULL, 41 | `fromLocation_code` varchar(45) NOT NULL, 42 | `toLocation_code` varchar(45) NOT NULL, 43 | `starttime` datetime DEFAULT NULL, 44 | `arrivetime` datetime DEFAULT NULL, 45 | `created_at` datetime DEFAULT NULL, 46 | `updated_at` datetime DEFAULT NULL, 47 | PRIMARY KEY (`schedule_id`) 48 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 49 | 50 | LOCK TABLES `ddd_carrier_movement` WRITE; 51 | /*!40000 ALTER TABLE `ddd_carrier_movement` DISABLE KEYS */; 52 | 53 | INSERT INTO `ddd_carrier_movement` (`schedule_id`, `fromLocation_code`, `toLocation_code`, `starttime`, `arrivetime`, `created_at`, `updated_at`) 54 | VALUES 55 | ('HN0001','HZ','NJ','2019-03-19 19:39:05','2019-03-26 19:39:05','2019-03-19 19:39:05',NULL), 56 | ('NB1114','NJ','BJ','2019-03-29 19:39:05','2019-03-30 19:39:05','2019-03-19 19:39:05',NULL); 57 | 58 | /*!40000 ALTER TABLE `ddd_carrier_movement` ENABLE KEYS */; 59 | UNLOCK TABLES; 60 | 61 | 62 | # Dump of table ddd_handling_event 63 | # ------------------------------------------------------------ 64 | 65 | DROP TABLE IF EXISTS `ddd_handling_event`; 66 | 67 | CREATE TABLE `ddd_handling_event` ( 68 | `id` varchar(45) NOT NULL, 69 | `cargo_id` varchar(45) NOT NULL, 70 | `eventtype` tinyint(2) NOT NULL, 71 | `schedule_id` varchar(45) DEFAULT '', 72 | `datetime` datetime DEFAULT NULL, 73 | `created_at` datetime DEFAULT NULL, 74 | `updated_at` datetime DEFAULT NULL, 75 | PRIMARY KEY (`id`), 76 | KEY `cargo` (`cargo_id`), 77 | KEY `carrie` (`schedule_id`) 78 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 79 | 80 | LOCK TABLES `ddd_handling_event` WRITE; 81 | /*!40000 ALTER TABLE `ddd_handling_event` DISABLE KEYS */; 82 | 83 | INSERT INTO `ddd_handling_event` (`id`, `cargo_id`, `eventtype`, `schedule_id`, `datetime`, `created_at`, `updated_at`) 84 | VALUES 85 | ('00001','CARGO-NO-16193',0,'','2019-03-19 19:39:05','2019-03-19 19:39:05',NULL), 86 | ('098005ec-2dda-42ee-9e13-aa3b49b8993b','CARGO-NO-15218',2,'HN0001','2019-03-30 08:00:00','2019-03-25 14:42:51',NULL), 87 | ('12dad393-f429-40f2-b365-58475658549e','CARGO-NO-16193',3,'','2019-03-30 18:00:00','2019-03-22 11:44:19',NULL), 88 | ('45646c85-861d-4935-8652-1fa2b2a9fde7','CARGO-NO-16193',1,'NB1114','2019-03-29 08:00:00','2019-03-22 12:35:47',NULL), 89 | ('5eae1a8a-cf09-40e4-9f81-7b61984bca12','CARGO-NO-15218',1,'HN0001','2019-03-30 08:00:00','2019-03-25 14:42:20',NULL), 90 | ('a11b1d5b-13b4-4c75-9a28-6648c33e6b3a','CARGO-NO-16193',2,'NB1114','2019-03-30 08:00:00','2019-03-22 12:35:53',NULL), 91 | ('d41baec9-d55e-4d73-b6ec-8c2504d50aed','CARGO-NO-16193',1,'HN0001','2019-03-20 08:00:00','2019-03-22 11:43:50',NULL), 92 | ('f1aa3ae7-07c5-4374-ada4-7dc97a07d823','CARGO-NO-16193',2,'HN0001','2019-03-26 08:00:00','2019-03-22 11:44:10',NULL); 93 | 94 | /*!40000 ALTER TABLE `ddd_handling_event` ENABLE KEYS */; 95 | UNLOCK TABLES; 96 | 97 | 98 | # Dump of table ddd_location 99 | # ------------------------------------------------------------ 100 | 101 | DROP TABLE IF EXISTS `ddd_location`; 102 | 103 | CREATE TABLE `ddd_location` ( 104 | `code` varchar(45) NOT NULL, 105 | `name` varchar(45) NOT NULL, 106 | PRIMARY KEY (`code`) 107 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 108 | 109 | LOCK TABLES `ddd_location` WRITE; 110 | /*!40000 ALTER TABLE `ddd_location` DISABLE KEYS */; 111 | 112 | INSERT INTO `ddd_location` (`code`, `name`) 113 | VALUES 114 | ('BJ','北京'), 115 | ('HZ','杭州'), 116 | ('NJ','南京'); 117 | 118 | /*!40000 ALTER TABLE `ddd_location` ENABLE KEYS */; 119 | UNLOCK TABLES; -------------------------------------------------------------------------------- /business-demo/src/main/resources/static/modifydelivery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cargo Admin 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 | 25 | 28 | 29 | 30 | 修改目的地 31 |
32 | 33 |
34 |
35 |
36 |
37 |
发送地:
38 |
杭州
39 |
40 |
41 |
目的地:
42 |
46 |
47 | 48 |
49 |
50 |
53 |
54 | 55 |
56 |
57 |
58 | 59 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/static/booking.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cargo Admin 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 | 25 | 28 | 29 | 30 | 预订货物 31 |
32 | 33 |
34 |
35 |
36 |
37 |
发件人:
38 |
39 |
40 |
41 |
货物描述:
42 |
43 |
44 |
45 |
发送地:
46 |
杭州
47 |
48 |
49 |
目的地:
50 |
54 |
55 |
56 |
57 |
60 |
61 | 62 |
63 |
64 |
65 | 66 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/static/event.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cargo Admin 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 | 25 | 28 | 29 | 30 | 添加单号处理事件 31 |
32 | 33 |
34 |
35 |
36 |
37 |
单号:
38 |
39 |
40 |
41 |
事件类型:
42 |
48 |
49 | 50 |
51 |
选择调度班次:
52 |
56 |
57 |
58 |
59 |
62 |
63 | 64 |
65 |
66 |
67 | 68 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /business-demo/src/main/resources/static/cargo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cargo Admin 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 | 25 | 28 | 29 | 30 | Cargo详情 31 |
32 |
33 |
34 |
35 |
单号:
36 |
发件人:
37 |
描述:
38 |
目的地:
39 |
40 |

单号追踪

41 |
42 |
    43 | 82 |
83 |
84 |
85 |
86 | 87 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 领域驱动设计 2 | 3 | 分段式协作分为三个阶段:战略设计、战术设计、战术实现 4 | 5 | ![协作方式](./images/协作方式.png) 6 | 7 | 8 | 9 | ## 战略设计 10 | 11 | 在整个设计过程中,务必忽略具体的技术实现细节,做到抽象与实现细节解耦,从而满足”领域驱动“而不是”技术实现驱动“。 12 | 13 | - 业务梳理和抽象 14 | 15 | - 识别**领域事件**:领域事件,在业务上真实发生的事,会对系统产生重要影响,因为过去所有发生的对业务有意义的信息都会以某种形式保存下来。事件以”XXX以YYY“的形式进行命名。 16 | 17 | **规则**:是对分支条件或复杂业务规则的抽象,目的是通过降低分支复杂度聚焦主要业务流程。以”XX规则“的名词进行命名。(使用场景:a. 开启了一条分支事件流;b. 简化复杂但对主流程影响不大的规则判断) 18 | 19 | 领域事件具有原子性,必须拆分到不可拆分为止 20 | 21 | 影响:对内产生改变某种数据,触发某种流程;对外发送了某些消息 22 | 23 | - 识别**决策命令**:是领域事件的触发动作,代表业务流程上的重要业务决策。技术实现上会通过领域类的方法来实现。 24 | 25 | **实施者**:是触发决策命令的原因(可以是:具体用户角色、外部系统、定时任务、前一个事件) 26 | 27 | - 识别**领域名词**:是在业务上下文中存在的领域概念,通常是:决策命令与领域事件中均出现的名词。 28 | 29 | 快速识别或抽象,在每一对决策命令和领域事件之间最相关的业务概念,以名词产出。此间通过重命名方式消除二义性,减少理解上的混乱 30 | 31 | | | | 32 | | -------------------------------------------- | -------------------------------------------- | 33 | | ![事件风暴图例1](./images/事件风暴图例1.png) | ![事件风暴图例2](./images/事件风暴图例2.png) | 34 | 35 | - 限界上下文、弹性边界识别 36 | 37 | - 限界上下文,是业务上下文的边界,在该边界内,交流某个概念,不会产生理解和认知的二义性,限界上下文是统一语言的重要保证。 38 | 39 | 限界上下文具备概念上的独立性,一个限界上下文内的子概念解释都不应该超出上下文的边界 40 | 41 | 将业务场景中识别出的全部领域名词和外部系统提取出,保证不重复。根据业务相关性,对领域名词进行归类,每个外部系统单独放置,然后用实线圈出每一组领域名词,每一个圈按照“XX上下文”方式进行抽象命名。 42 | 43 | 进行分类时,需要从以下维度进行考虑: 44 | 45 | 1. 语言二义性 46 | 2. 变化一致性 47 | 3. 业务相关性 48 | 4. 概念相关性 49 | 50 | - 弹性边界,直接决定是否需要将系统拆分成多个能够独立开发、部署和运行的服务,而业务则影响系统内的业务组件划分 51 | 52 | 在弹性边界内的应用,具备同样的伸缩原因和一致性边界: 53 | 54 | - 可以是功能性需求决定的,如客户对功能模块的自由性选择需求,提供不同组件 55 | - 也可以是非功能性需求,如按照伸缩性和容错性需求,停止服务就一同停止服务,还是只停止其中一个服务,其他服务不受影响 56 | 57 | - 便于操作理解,约定: 58 | 59 | - 一个弹性边界内可以包含多个限界上下文 60 | - 一个限界上下文不应跨越弹性边界 61 | 62 | ![弹性边界](./images/弹性边界.jpg) 63 | 64 | 通过分析依赖关系,提前识别依赖矛盾,减少低级设计错误。若出现以下依赖关系,需要考虑是否存在未澄清问题: 65 | 66 | 1. 双向依赖:上下文间缺少一层未被澄清,或者二者可以合二为一 67 | 2. 传染依赖:任何一个上下文发生变更,依赖链条上的上下文均需要变化 68 | 3. 过长依赖:自身依赖信息不能直接从依赖者获得,需要链接过长,依赖链条上任何一个上下文发生变化,其后链条上的上下文均可能需要改变 69 | 70 | ![限界上下文依赖关系](./images/限界上下文依赖关系.png) 71 | 72 | - 子域识别 73 | 74 | 是对问题域的澄清和划分,同时也是对于资源投入优先级的重要参考。每一个问题子域负责解决一个有独立业务价值的业务问题 75 | 76 | - 核心域:是整个业务的盈利来源和基石,如果核心域不存在,那么整个业务就不能运作 77 | - 支撑域:是支撑核心域运作的问题,具备强烈个性化需求 78 | - 通用域:该类问题业内常见,可能有现成方案,通过改造就可以使用 79 | 80 | 可以先识别核心域,再识别通用域,最后剩下的都是支撑域。 81 | 82 | 问题子域和限界上下文是完全不同的两个概念:问题子域解决的是问题澄清和优先级排序问题;限界上下文解决的是业务边界识别和统一语言问题。 83 | 84 | 便于操作理解,约定: 85 | 86 | - 一个子域可以包含多个限界上下文 87 | - 一个限界上下文不应跨越多个子域 88 | 89 | ![子域划分](./images/子域划分.png) 90 | 91 | 92 | 93 | ## 战术设计 94 | 95 | - 领域建模 96 | 97 | 领域模型是抽象模型,与具体的实现细节无关,不要过早的考虑面向对象或者数据表设计。 98 | 99 | - 典型的"自下(数据库)而上,而非自上(抽象)而下设计":子实体携带有父实体的ID,这是一种数据库外键关联和查询需求而非抽象模型。 100 | 101 | 领域模型(Domain Model ), 是对业务的高度抽象, 利用抽象模型作为业务子和系统实现的核心联系,领域模型封装和承载了全部的业务逻辑,并通过聚合的方式保持业务的"高内聚,低耦合"。 102 | 103 | 1. 选择并聚焦一个限界上下文,将**领域名词**与其所相关的所有**决策命令**和**领域事件**放在一起。 104 | 2. 基于聚合、聚合根、实体、值对象的定义和识别原则,将所有领域名词按照其所具备的业务概念联系起来,并识别为适合的类型。 105 | 3. 用实线联系聚合内的实体,用虚线联系对另一个聚合的引用。 106 | 4. 识别实体与实体、实体与值对象之间的数量对应关系,并按照图例所示的要求进行标识。 107 | 5. 在分析的过程中不断的检验、发现和补充缺失的重要业务概念,消除二义性,统一语言,迭代式的完善模型。 108 | 6. **将聚合相关的决策命令和领域事件放置到该聚合的聚合根上方**。 109 | 7. 在建模完成后,可以利用未来可能性较高的相关业务变更需求来检验模型的合l理性(业务响应力),看看是否只需要做少量修改即可匹配新的业务需求。 110 | 111 | 聚合(Aggregate ), **聚合负责封装业务逻辑, 通过一致性边界和统一语言,内聚决策命令和领域事件**,容纳并识别领域名词为以下不同的抽象模型: 112 | 113 | - 聚合根(Aggregate Root) :是一种实体, 是聚合的根节点。 114 | - 聚合根负责执行业务规则。 115 | - 聚合根有全局标识。边界内的实体只有局部标识,在聚合内唯一。 116 | - 聚合边界外的对象只能引用聚合根,不能持有聚合内对象的引用。 117 | - 边界内的对象可以持有对其他聚合根的引用。 118 | - 删除操作必须全部删除边界内的对象。 119 | - 聚合边界内任何对象发生改变,都不能违反聚合的整体业务规则。 120 | - 只有聚合根能直接从持久化系统查询得到,边界内对象只能从聚合根导航。 121 | - 实体(Entity ) :是聚合的主干, 具有唯一标识和生命周期。 122 | - 具有生命周期 123 | - 有唯一标识 124 | - 通过Id判断相等性 125 | - 增删改查/持久化 126 | - 可变 127 | - 比如Order/Car 128 | - 值对象(Value Object ) :是实体的附加业务概念, 用来描述实体所包包含的业务信息。 129 | - 起描述性作用 130 | - 无唯一标识 131 | - 通过属性判断相等性 132 | - 实现Equals) 方法 133 | - 即时创建/用完即扔 134 | - 不可变(Immutable) 135 | - 比如Address/Color 136 | 137 | 聚合边界内具有强一致性,所以: 138 | 139 | - 一个聚合不应跨越弹性边界,否则难以解决事务一致性问题。 140 | - 一个聚合不应跨越限界上下文,否则会造成概念上的混淆,增加人员沟通和维护成本。 141 | 142 | ![领域建模图例](./images/领域建模图例.jpg) 143 | 144 | - 业务服务识别 145 | 146 | 结合以下内容的分析结果,确定业务服务的划分(或合并): 147 | 148 | 1. 限界上下文分析结果(具有强二义性的两个上下文尽量不放在一个微服务中); 149 | 2. 弹性边界识别结果(决定性因素); 150 | 3. 问题子域划分结果(一个微服务尽量不跨越子域边界); 151 | 4. 领域模型的聚合设计结果(聚合绝不可拆分到不同微服务中); 152 | 153 | ![划分业务服务](./images/划分业务服务.jpg) 154 | 155 | - 业务服务API能力识别 156 | 1. 基于业务服务所包含的限界上下文,以及限界上下文内的聚合,**识别需要向外暴露的决策命令**,**决策命令的名称即可作为API的名称**。 157 | 2. 补充缺失的其他具有业务价值的API。 158 | 3. 按照"限界上下文、聚合、接口能力"的分类, 列出API清单。 159 | 160 | ![服务接口能力](./images/服务接口能力.png) 161 | 162 | 163 | 164 | ## 技术实现 165 | 166 | 技术实现环节涉及:UML设计、API详细设计、数据库设计、部署与运维... 167 | 168 | 对以下知识点一定要熟知: 169 | 170 | - 聚合根 171 | - 实体 172 | - 值对象 173 | - 领域服务 174 | - 领域事件 175 | - 资源库 176 | - 限界上下文 177 | - CQRS 178 | 179 | ### 分层架构 180 | 181 | 在落地领域驱动之前,首先明确的问题是选择何种架构去实现。 182 | 183 | #### DIP改进分层 184 | 185 | ![服务接口能力](./images/DIP分层.png) 186 | 187 | 所谓的依赖倒置原则指的是:高层模块不应该依赖于低层模块,两者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象。 188 | 189 | 正如架构图中看到的,**基础实施层位于其他所有层的上方,接口定义在其它层,基础实施实现这些接口**。或者可以这样来表述:领域层等其他层不应该依赖于基础实施层,两者都应该依赖于抽象。 190 | 191 | 这也就是意味着一个重要的落地指导原则: **所有依赖基础实施实现的功能,抽象和接口都应该定义在领域层或应用层中**。 192 | 193 | #### 六边形架构 194 | 195 | ![服务接口能力](./images/六边形架构.png) 196 | 197 | 在这种架构风格中,外部客户和内部系统的交互都会通过端口和适配器完成转换,这些外部客户之间是平等的,比如用户web界面和数据库持久化,当需要一个新的外部客户时,只需要增加相应的适配器,比如当我们依赖外部一个RPC的服务时,只需要编写对应的适配器即可。 198 | 199 | 这种设计角度下,没有前端web和数据库后端之分,统一为外部客户。 200 | 201 | 如果外部客户时HTTP请求,那么SpringMVC的注解和Controller构成了适配器;如果外部客户时MQ消息,那么适配器就是MQConsumer监听器;如果外部客户时数据库,那么适配器可能就是Mybatis的Mapper。 202 | 203 | #### 洋葱架构 204 | 205 | 洋葱模型 206 | 207 | 只允许外层依赖内层,不允许内层知道外层的细节。 208 | 209 | ### 驱动模式 210 | 211 | 在领域驱动架构中,通常会将查询和命令操作分开,我们称之为CQRS(命令查询的责任分离Command Query Responsibility Segregation)。 212 | 213 | ![服务接口能力](./images/CQRS.png) 214 | 215 | 图中读模块Query Model和写模块Command Model只是逻辑分离,物理层面还是使用了同一个数据库,也可以将数据库改成读库和写库做到物理分离。 216 | 217 | ### 架构落地 218 | 219 | 洋葱模型 220 | 221 | 架构中,平等地看待Web、RPC、DB、MQ等外部服务,基础实施依赖圆圈内部的抽象。 222 | 223 | 当一个命令Command请求过来时,会通过应用层的CommandService去协调领域层工作,而一个查询Query请求过来时,则直接通过基础实施的实现与数据库或者外部服务交互。再次强调,**所有的抽象都定义在圆圈内部,实现都在基础设施**。 224 | 225 | ### 代码落地 226 | 227 | #### 代码结构 228 | 229 | ```sql 230 | java 231 | └── com 232 | └── gtw 233 | └── business 234 | ├── application -- 用户接口层 235 | │   ├── controller -- HTTP 请求 236 | │   ├── mq -- mq 消费入口 237 | │   ├── report -- 报表类、查询入口 238 | │   ├── rpc -- rpc 服务提供入口(这里指rpc的实现,rpc接口提供单独放在可打包发布的module中) 239 | │   └── scheduler -- 定时任务调度入口 240 | ├── common -- 公共通用层 241 | │   ├── component -- 通用基础设施层的接口,如 mq,cache 242 | │   ├── model -- 公用的数据对象和抽象接口 243 | │   └── utils -- 工具类 244 | ├── domain -- 领域服务层 245 | │   ├── aggregate -- 领域模型 246 | │   └── service -- 领域服务 247 | ├── infrastructure -- 基础设施层 248 | │   ├── cache 249 | │   ├── db -- 对领域服务中的仓储实现 250 | │   │   └── reponsitory 251 | │   ├── event -- 对领域服务中的事件实现 252 | │   │   ├── listener 253 | │   │   └── publisher 254 | │   ├── mq -- mq 生产者 255 | │   └── rpc -- rpc 服务的调用 256 | └── service -- 应用服务层(CQRS) 257 | ├── command 258 | │   ├── cmd -- 命令的请求参数XXXCommand对象 259 | │   └── impl -- 命令请求服务的实现(请求接口直接定义在command包下) 260 | └── query 261 | ├── dto -- 查询结果DTO对象 262 | ├── impl -- 查询服务的实现(查询接口直接定义在command包下) 263 | └── qry -- 查询的条件参数XXXQry对象 264 | ``` 265 | 266 | 267 | 268 | #### 用户界面 `com.gtw.business.application.controller` 269 | 270 | `Controller`作为六边形架构中与HTTP端口的适配器,起到了适配请求,委托应用服务处理的任务。对称性架构的好处就在于,当增加新的用户的界面时可以创建一个新包去承载适配器(比如为`mq`消费,在`application`目录下新增`mq`包),然后调用应用层的服务。 271 | 272 | 这里可以有个默认规范:**所有查询的条件封装成XXXQry对象,所有命令的请求封装成XXXCommand对象**。 273 | 274 | 考虑校验逻辑应该放到哪一层的时候确定这一层代码可以有请求参数的基本校验,但是**应用服务的校验逻辑是必须存在的,校验和应用服务的耦合是紧密的**。 275 | 276 | #### 应用服务 `com.gtw.business.service` 277 | 278 | 应用服务的每个方法与用例是一一对应的,典型的处理流程是: 279 | 280 | 1. 校验 281 | 2. 协调领域模型或者领域服务 282 | 3. 持久化(事务控制) 283 | 4. 发布领域事件 284 | 285 | 发布领域事件的动作放在了应用层没有放在领域层,而领域事件的定义是在领域层,这是为什么呢?如果**不考虑持久化,发布领域事件的确应该在领域模型中,但是在代码落地时,考虑到持久化完成后才代表有了真实的事件,所以将触发事件的代码放到了资源库后面**。 286 | 287 | #### 领域模型 `com.gtw.business.domain.aggregate` 288 | 289 | 采用了`aggregate`而不是`model`,是为了将聚合根的概念显现出来,每个聚合根单独成一个子包,在单个聚合根中包含所需要的值对象、领域事件的定义、资源库的抽象接口等。 290 | 291 | **领域事件的定义、资源库的抽象接口**之所以放在相应聚合根的package中,是因为它更能体现这个领域模型,而且资源库的抽象和聚合根有着对应的关系(不大于聚合根的数量)。 292 | 293 | 关于聚合根对象的创建,特别提醒的是**聚合根对象的创建不应该被Spring容器管理**,也不应该在聚合根中注入其它对象。聚合根对象可以通过静态工厂方法来创建,下文还会介绍如何落地资源库进行聚合根的创建。 294 | 295 | #### 领域服务 `com.gtw.business.domain.service` 296 | 297 | 业务逻辑什么时候该放在领域模型中,什么时候放在领域服务中,可以从以下几点考虑: 298 | 299 | 1. 不是属于单个聚合根的业务或者需要多个聚合根配合的业务,放在领域服务中(注意:是业务,如果没有业务,**协调工作应该放到应用服务中**) 300 | 2. 静态方法放在领域服务中 301 | 3. 需要通过rpc等其它外部服务处理业务的,放在领域服务中 302 | 303 | #### 基础设施 `com.gtw.business.infrastructure` 304 | 305 | 基础设施可以对抽象的接口进行实现,资源库Repository的接口定义在领域层,那么在基础设施中就可以具体实现这个接口。 306 | 307 | 资源库Repository的实现就是将聚合根对象持久化,往往聚合根的定义和数据库中定义的结构并不一致,数据库的对象称为数据对象DO。 308 | 309 | 当持久化时就需要将聚合根序列化成数据库数据对象,通过资源库获取(构造)聚合根时,也需要反序列化数据库数据对象。可以基于反射或其它技术手段完成序列化和反序列化操作,这样可以避免聚合根中编写过多的getter和setter方法。 310 | 311 | #### 查询服务 `com.gtw.business.service.query` 312 | 313 | 应用服务包含了commond和query两个子包,查询服务是在query包中。 314 | 315 | 运用CQRS设计,查询服务不会调用应用服务,也不会调用领域模型和资源库Repository,它会直接查询数据库或者ES获取原始数据对象DO,然后组装成数据传输对象DTO给用户界面,这个组装的过程称为Assembler,由于与用户界面有一定的对应关系,所以Assembler放在查询服务中。 316 | 317 | 是否需要将每个对象都转化成DTO返回给用户界面这个要看情况,个人认为当DO能满足界面需求时是可以直接返回DO数据的。 318 | 319 | #### 落地MQ、Event、Cache 320 | 321 | 毫无疑问,MQ、Event、Cache的实现都应该在基础设施层,它们接口的定义放在哪里呢?一个方案是哪一层使用了抽象就在那一层定义接口,另一个方案是放到一个共有的抽象包下,基础设施和对应层依赖这个共有的抽象包。 322 | 323 | #### RPC和防腐层 324 | 325 | 前面提到过,当我们暴露一个RPC服务时和web层是平等对待的,比如暴露一个dubbo协议的服务就和暴露一个http的服务是平等的。这一小节我们将来探讨如何与第三方系统的RPC服务进行交互。 326 | 327 | 这里涉及到DDD中Bounded Context和Context Map的概念,在领域驱动设计中,限界上下文之间是不能直接交互的,它们需要通过Context Map进行交互,在微服务足够细致的年代,我们可以做到一个微服务就代表着一个限界上下文。 328 | 329 | 通用做法会再创建一个Translator实现上下文模型之间的翻译功能。其它限界上下文的模型在我们系统中并不是一个模型实体,而是一个值对象,很显然Adapter应该放在基础设施层中,那么这个值对象存放在哪里呢?我们可以将值对象和抽象接口定义在领域层,然后基础设施通过适配器和翻译器实现抽象接口,很明显这个做法是非常可取的。在具体落地时我们发现,这些值对象可能同时又被查询服务依赖,所以值对象和抽象接口定义在shared Data & Service中也是可取的,具体放在那里因看法而异。 330 | 331 | ### Example 332 | 333 | 需求-节选自《领域驱动设计第7章》:假设正在为一家货运公司开发新的软件,最初的需求包括三项基本功能: 334 | 335 | 1. 事先预约货物 336 | 2. 跟踪客户货物的主要处理流程 337 | 3. 当货物到达其处理过程中的某个位置时,自动向客户寄送发票 338 | 339 | 演示如何落地: 340 | 341 | 1. 修改`/business-demo/src/main/resources/application.properties`数据库连接 342 | 2. 初始化数据库脚本文件`/business-demo/resources/mysql_init.sql` 343 | 3. 运行`Application.java` 344 | 4. 浏览器访问`http://localhost:8080/index.html` 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | --------------------------------------------------------------------------------