本文首先介紹一下 DDD 代碼模型的分層策略洼畅,然后介紹一下各層的職責(zé)不见,最后基于 DDD 開發(fā)一個(gè)訂單中心柑营,來實(shí)踐 DDD.
DDD 分層策略
在 DDD 中蒋纬,共有四層(領(lǐng)域?qū)佑┎省?yīng)用層粪滤、用戶接口層、基礎(chǔ)設(shè)施層)雀扶,其層級實(shí)際上是環(huán)狀架構(gòu)杖小。如上圖所示。根據(jù)整潔架構(gòu)思想愚墓,在上述環(huán)狀架構(gòu)中予权,越往內(nèi)層,代碼越穩(wěn)定浪册,其代碼不應(yīng)該受外界技術(shù)實(shí)現(xiàn)的變動(dòng)而變動(dòng)扫腺,所以依賴關(guān)系是:外層依賴內(nèi)層
。按照這個(gè)依賴原則村象,DDD 代碼模塊依賴關(guān)系如下:
- 領(lǐng)域?qū)樱╠omain):位于最內(nèi)層笆环,不依賴其他任何層;
- 應(yīng)用層(application):僅依賴領(lǐng)域?qū)樱?/li>
- 用戶接口層(interfaces):依賴應(yīng)用層和領(lǐng)域?qū)樱?/li>
- 基礎(chǔ)設(shè)施層(infrastructure):依賴應(yīng)用層和領(lǐng)域?qū)樱?/li>
- 啟動(dòng)模塊(starter):依賴用戶接口層和基礎(chǔ)設(shè)施層煞肾,對整個(gè)項(xiàng)目進(jìn)行啟動(dòng)咧织。
注意:interfaces 和 infrastructure 位于同一個(gè)換上,二者沒有依賴關(guān)系籍救。
DDD 各層職責(zé)
領(lǐng)域模型層 domain
包括實(shí)體习绢、值對象、領(lǐng)域工廠蝙昙、領(lǐng)域服務(wù)(處理本聚合內(nèi)跨實(shí)體操作)闪萄、資源庫接口、自定義異常等
應(yīng)用服務(wù)層 application
跨聚合的服務(wù)編排奇颠,僅編排聚合根败去。包括:應(yīng)用服務(wù)等
用戶接口層 interfaces
本應(yīng)用的所有流量入口。包括三部分:
- web 入口的實(shí)現(xiàn):包括 controller烈拒、DTO 定義圆裕、DTO 轉(zhuǎn)化類
- 消息監(jiān)聽者(消費(fèi)者):包括 XxxListener
- RPC 接口的實(shí)現(xiàn):比如在使用 Dubbo 時(shí)广鳍,我們的服務(wù)需要開放 Dubbo 服務(wù)給第三方,此時(shí)需要?jiǎng)?chuàng)建單獨(dú)的模塊包吓妆,例如 client 模塊赊时,包含 Dubbo 接口和 DTO,在用戶接口層中行拢,去做 client 中接口的實(shí)現(xiàn)以及 DTO 轉(zhuǎn)化類
基礎(chǔ)設(shè)施層 infrastructure
本應(yīng)用的所有流量出口祖秒。包括:
- 資源庫接口的實(shí)現(xiàn)
- 數(shù)據(jù)庫操作接口、數(shù)據(jù)庫實(shí)現(xiàn)(如果使用mybatis舟奠,則包含 resource/*.xml)竭缝、數(shù)據(jù)庫對象 DO、DO 轉(zhuǎn)化類
- 中間件的實(shí)現(xiàn)沼瘫、文件系統(tǒng)實(shí)現(xiàn)抬纸、緩存實(shí)現(xiàn)、消息實(shí)現(xiàn) 等
- 第三方服務(wù)接口的實(shí)現(xiàn)
基于 DDD 開發(fā)訂單中心
需求:基于 DDD 開發(fā)一個(gè)訂單中心耿戚,實(shí)現(xiàn)下訂單松却、查詢訂單等功能
代碼:https://github.com/zhaojigang/ordercenter
ordercenter 根模塊
├── order-application 應(yīng)用模塊
├── order-domain 領(lǐng)域模塊
├── order-infrastructure 基礎(chǔ)設(shè)施模塊
├── order-interfaces 用戶接口模塊
├── order-starter 啟動(dòng)模塊
└── pom.xml 根模塊
領(lǐng)域?qū)哟a模型
包依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
引入 spring-boot-autoconfigure:2.4.2,在領(lǐng)域工廠中需要用到 Spring 注解
DDD 標(biāo)識注解 common.ddd.AggregateRoot
/**
* 標(biāo)注一個(gè)實(shí)體是聚合根
*/
@Documented
@Retention(SOURCE)
@Target(TYPE)
public @interface AggregateRoot {
}
自定義異常 common.exception.OrderException
/**
* 自定義異常
*/
@Data
public class OrderException extends RuntimeException {
private Integer code;
private String message;
public OrderException(Integer code, String message) {
this.code = code;
this.message = message;
}
}
將自定義異常放在領(lǐng)域?qū)咏埃驗(yàn)?DDD 推薦使用充血模型晓锻,在領(lǐng)域?qū)嶓w、值對象或者領(lǐng)域服務(wù)中飞几,也會(huì)做一些業(yè)務(wù)邏輯砚哆,在業(yè)務(wù)邏輯中,可以根據(jù)需要拋出自定義異常
資源庫接口 io.study.order.repository.OrderRepository
/**
* 訂單資源庫接口
*/
public interface OrderRepository {
/**
* 保存訂單
*
* @param order 訂單
*/
void add(Order order);
/**
* 根據(jù)訂單ID獲取訂單
* @param orderId
*/
Order orderOfId(OrderId orderId);
}
- 資源庫接口放置在領(lǐng)域?qū)有寄瑢?shí)現(xiàn)領(lǐng)域?qū)ο笞猿志没晁瑫r(shí)實(shí)現(xiàn)依賴反轉(zhuǎn)。
- 依賴反轉(zhuǎn):將依賴關(guān)系進(jìn)行反轉(zhuǎn)卵史,假設(shè) Order 要做自持久化战转,那么需要拿到資源庫的實(shí)現(xiàn) OrderRepositoryImpl 才行,那么 domain 包就要依賴 infrastructure 包以躯,但是這不符合
外層依賴內(nèi)層
的原則槐秧,所以需要進(jìn)行依賴反轉(zhuǎn),由 infrastructure 包依賴 domain 包忧设。實(shí)現(xiàn)依賴反轉(zhuǎn)的方式就是在被依賴方中添加接口(例如刁标,在 domain 包中添加 OrderRepository 接口),依賴包對接口進(jìn)行實(shí)現(xiàn)(infrastructure 包中對 OrderRepository 進(jìn)行實(shí)現(xiàn))址晕,這樣的好處是膀懈,domain 可以完全僅關(guān)注業(yè)務(wù)邏輯,不要關(guān)心具體技術(shù)細(xì)節(jié)谨垃,不用去關(guān)心启搂,到底是存儲(chǔ)到 mysql硼控,還是 oracle,使用的數(shù)據(jù)庫框架是 mybatis 還是 hibernate胳赌,技術(shù)細(xì)節(jié)的實(shí)現(xiàn)由 infrastructure 來完成淀歇,真正實(shí)現(xiàn)了業(yè)務(wù)邏輯和技術(shù)細(xì)節(jié)的分離- 資源庫的命名推薦:對于資源庫,推薦面向集合進(jìn)行設(shè)計(jì)匈织,即資源庫的方法名采用與集合相似的方法名,例如牡直,保存和更新是 add缀匕、addAll,刪除時(shí) remove碰逸、removeAll乡小,查詢是 xxxOfccc,例如 orderOfId饵史,ordersOfCondition满钟,復(fù)數(shù)使用 xxxs 的格式,而不是 xxxList 這樣的格式
- 一個(gè)聚合具有一個(gè)資源庫:比如訂單聚合中胳喷,Order 主訂單是聚合根湃番,OrderItem 子訂單是訂單聚合中的一個(gè)普通實(shí)體,那么在訂單聚合中只能存在 OrderRepository吭露,不能存在 OrderItemRepository吠撮,OrderItem 的 CRUD 都要通過 OrderRepository 先獲得 Order,再從 Order 中獲取 List<OrderItem>讲竿,再做邏輯泥兰。這樣的好處,
保證了聚合根值整個(gè)聚合的入口题禀,對聚合內(nèi)的其他實(shí)體和值對象的方訪問鞋诗,只能通過聚合根,保證了聚合的封裝性
領(lǐng)域工廠 io.study.order.factory.OrderFactory
/**
* 訂單工廠
*/
@Component
public class OrderFactory {
private static OrderRepository orderRepository;
@Autowired
public OrderFactory(OrderRepository repository) {
orderRepository = repository;
}
public static Order createOrder() {
return new Order(orderRepository);
}
}
工廠的作用:創(chuàng)建聚合迈嘹。
工廠的好處:
- 創(chuàng)建復(fù)雜的聚合削彬,簡化客戶端的使用。例如 Order 的創(chuàng)建需要注入資源庫秀仲,訂單創(chuàng)建后吃警,可以直接發(fā)布訂單創(chuàng)建事件。
- 可讀性好(更加符合通用語言)啄育,比如 對于創(chuàng)建訂單酌心,createOrder 就比 new Order 的語義更加明確
- 更好的保證一致性,防止出錯(cuò)挑豌,假設(shè)創(chuàng)建兩個(gè)主訂單 Order安券,兩個(gè)主訂單下分別還要?jiǎng)?chuàng)建多個(gè)子訂單 OrderItem墩崩,每個(gè)子訂單中需要存儲(chǔ)主訂單的ID,如果由客戶端來設(shè)置 OrderItem 中的主訂單ID侯勉,可能會(huì)將A主訂單的ID設(shè)置給B主訂單下的子訂單鹦筹,可能出現(xiàn)數(shù)據(jù)不一致的問題,具體的示例見 《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)》P183址貌。
實(shí)體唯一標(biāo)識 io.study.order.domain.OrderId
import lombok.Value;
/**
* 訂單ID
*/
@Value
public class OrderId {
private Long id;
public static OrderId of(Long id) {
return new OrderId(id);
}
public void validId(){
if (id == null || id <= 0) {
throw new OrderException(400, "id 為空");
}
}
}
- 推薦使用強(qiáng)類型的對象作為實(shí)體的唯一標(biāo)識铐拐,好處有兩個(gè):
a. 用來避免傳參混亂,同時(shí)提升接口的可讀性练对,例如 xxx(Long orderId, Long goodsId)遍蟋,假設(shè)上述接口第一個(gè)參數(shù)傳了 goodsId,第二個(gè)傳了 orderId螟凭,那么編譯期是無法發(fā)現(xiàn)的虚青,改為 xxx(OrderId orderId, GoodsId, goodsId) 即可避免,同時(shí)可讀性也較高螺男。
b. 唯一標(biāo)識中會(huì)有一些其他行為方法棒厘,如果唯一標(biāo)識使用弱類型,那么這些行為方法將會(huì)泄露在實(shí)體中- 唯一標(biāo)識類是一個(gè)值對象下隧,推薦值對象設(shè)置為不可變對象奢人,使用 @lombok.Value 標(biāo)注值對象,既可標(biāo)識該對象為值對象淆院,也可以是該類變?yōu)椴豢勺冾惔锎@纾硎竞蟮?OrderId 沒有 setXxx 方法迫筑。
- 值對象的行為函數(shù)都是無副作用函數(shù)(即不能影響值對象本身的狀態(tài)宪赶,例如 OrderId 對象被創(chuàng)建后,不能再使用 setXxx 修改其屬性值)脯燃,如果確實(shí)有屬性需要變動(dòng)搂妻,值對象需要整個(gè)換掉(例如,重新創(chuàng)建一個(gè) OrderId 對象)
聚合根 io.study.order.domain.Order
/**
* 訂單聚合根
*/
@Setter
@Getter
@AggregateRoot
public class Order {
/**
* 訂單 ID
*/
private OrderId id;
/**
* 訂單名稱
*/
private String name;
/**
* 訂單資源庫
*/
private OrderRepository orderRepository;
protected Order(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
/**
* 創(chuàng)建訂單
*
* @param order
*/
public void saveOrder(Order order) {
orderRepository.add(order);
}
public void setName(String name) {
if (name == null) {
throw new OrderException(400, "name 不能為空");
}
this.name = name;
}
public void setGoodsId(Long goodsId) {
if (goodsId == null) {
throw new OrderException(400, "goodsId 不能為空");
}
this.goodsId = goodsId;
}
public void setBuyQuality(Integer buyQuality) {
if (buyQuality == null) {
throw new OrderException(400, "buyQuality 不能為空");
}
this.buyQuality = buyQuality;
}
}
- 聚合根是一個(gè)特殊的實(shí)體辕棚,是整個(gè)聚合對外的使者欲主,其他聚合與改聚合溝通的方式只能是通過聚合根
- 由于使用工廠來創(chuàng)建 Order,那么 Order 的構(gòu)造器需要設(shè)置為 protected逝嚎,防止外界直接使用進(jìn)行創(chuàng)建
- 實(shí)體單個(gè)屬性的校驗(yàn)需要在 setXxx 中完成自校驗(yàn)
- 實(shí)體是可變的扁瓢、具有唯一標(biāo)識,其唯一標(biāo)識通常需要設(shè)計(jì)成強(qiáng)類型
- 聚合中的 XxxRepository 可以通過上述的工廠進(jìn)行注入补君,也可以使用“雙委派”機(jī)制引几,即提供類似方法:
createOrder(Order order, XxxRepository repository)
,然后應(yīng)用層在調(diào)用該方法時(shí)挽铁,傳入注入好的 repository 實(shí)例即可伟桅。但是這樣的方式敞掘,提高了客戶端使用的復(fù)雜性。
應(yīng)用層代碼模型
包依賴
<dependencies>
<dependency>
<groupId>io.study</groupId>
<artifactId>order-domain</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
應(yīng)用服務(wù) io.study.order.app.service.OrderAppService
/**
* 訂單應(yīng)用服務(wù)
*/
@Service
public class OrderAppService {
/**
* 創(chuàng)建一個(gè)訂單
*
* @param order
*/
public void createOrder(Order order) {
/**
* 存儲(chǔ)訂單
*/
order.saveOrder(order);
/**
* 扣減庫存
*/
}
}
應(yīng)用服務(wù)用于服務(wù)編排楣铁,如上述先存儲(chǔ)訂單玖雁,然后再調(diào)用庫存服務(wù)減庫存。(庫存服務(wù)屬于第三方服務(wù)盖腕,第三方服務(wù)的集成見下一小節(jié))
基礎(chǔ)設(shè)施層代碼模型
包依賴
<dependencies>
<!-- 領(lǐng)域模塊 -->
<dependency>
<groupId>io.study</groupId>
<artifactId>order-domain</artifactId>
<version>${project.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- mapstruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<scope>provided</scope>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
- mapstruct 用于實(shí)現(xiàn)模型映射器赫冬,關(guān)于其使用見 http://www.reibang.com/p/53aac78e7d60
- 數(shù)據(jù)存儲(chǔ)采用 mysql,數(shù)據(jù)庫操作框架使用 mybatis溃列,可以看到劲厌,領(lǐng)域?qū)訉唧w的技術(shù)實(shí)現(xiàn)并不關(guān)注,僅關(guān)注業(yè)務(wù)哭廉,通過 DDD 實(shí)現(xiàn)了技術(shù)細(xì)節(jié)與業(yè)務(wù)邏輯的解耦。
資源庫實(shí)現(xiàn) io.study.order.repository.impl.OrderRepositoryImpl
/**
* 訂單資源庫實(shí)現(xiàn)類
*/
@Repository
public class OrderRepositoryImpl implements OrderRepository {
@Resource
private OrderDAO orderDAO;
@Override
public void add(Order order) {
orderDAO.insertSelective(OrderDOConverter.INSTANCE.toDO(order));
}
@Override
public Order orderOfId(OrderId orderId) {
OrderDO orderDO = orderDAO.selectByPrimaryKey(orderId.getId());
return OrderDOConverter.INSTANCE.fromDO(orderDO);
}
}
數(shù)據(jù)庫操作接口 io.study.order.data.OrderDAO
/**
* 訂單 DAO
* 使用 mybatis-generator 自動(dòng)生成
*/
@org.apache.ibatis.annotations.Mapper
public interface OrderDAO {
int insertSelective(OrderDO record);
OrderDO selectByPrimaryKey(Long id);
}
數(shù)據(jù)庫實(shí)現(xiàn)類 resources/mapper/OrderDAO.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="io.study.order.data.OrderDAO">
<resultMap id="BaseResultMap" type="io.study.order.data.OrderDO">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
</resultMap>
<sql id="Base_Column_List">
id, name
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long">
select
<include refid="Base_Column_List"/>
from `order`
where id = #{id,jdbcType=BIGINT}
</select>
<insert id="insertSelective" parameterType="io.study.order.data.OrderDO">
insert into `order`
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="name != null">
name,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=BIGINT},
</if>
<if test="name != null">
#{name,jdbcType=VARCHAR},
</if>
</trim>
</insert>
</mapper>
數(shù)據(jù)對象
/**
* 訂單數(shù)據(jù)庫對象
*/
@Data
public class OrderDO {
/**
* 訂單 ID
*/
private Long id;
/**
* 訂單名稱
*/
private String name;
}
數(shù)據(jù)對象轉(zhuǎn)換器 io.study.order.data.OrderDOConverter
/**
* OrderDO 轉(zhuǎn)換器
*/
@org.mapstruct.Mapper
public interface OrderDOConverter {
OrderDOConverter INSTANCE = Mappers.getMapper(OrderDOConverter.class);
@Mapping(source = "id.id", target = "id")
OrderDO toDO(Order order);
@Mapping(target = "id", expression = "java(OrderId.of(orderDO.getId()))")
void update(OrderDO orderDO, @MappingTarget Order order);
default Order fromDO(OrderDO orderDO) {
Order order = OrderFactory.createOrder();
INSTANCE.update(orderDO, order);
return order;
}
}
在創(chuàng)建實(shí)體對象時(shí)相叁,需要使用工廠進(jìn)行創(chuàng)建遵绰,這樣才能為實(shí)體注入資源庫實(shí)現(xiàn)。
用戶接口層代碼模型
包依賴
<dependencies>
<!-- 領(lǐng)域模塊 -->
<dependency>
<groupId>io.study</groupId>
<artifactId>order-domain</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 應(yīng)用模塊 -->
<dependency>
<groupId>io.study</groupId>
<artifactId>order-application</artifactId>
<version>${project.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- mapstruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<scope>provided</scope>
</dependency>
<!-- springboot-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springfox -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>
</dependencies>
引入 springfox-boot-starter:3.0.0 來實(shí)現(xiàn)自動(dòng)化可測試的 Rest 接口文檔
Controller io.study.order.web.OrderController
/**
* Order 控制器
*/
@Api("訂單控制器")
@RestController
@RequestMapping("order")
public class OrderController {
@Resource
private OrderAppService orderAppService;
@Resource
private OrderRepository orderRepository;
/**
* 創(chuàng)建一個(gè)訂單
*
* @param orderDto
*/
@ApiOperation("創(chuàng)建訂單")
@PostMapping("/create")
public void createOrder(@RequestBody OrderDto orderDto) {
orderAppService.createOrder(OrderDtoAssembler.INSTANCE.fromDTO(orderDto));
}
/**
* 查詢一個(gè)訂單
*
* @param id 訂單ID
* @return
*/
@ApiOperation("根據(jù)訂單ID獲取訂單")
@GetMapping("/find/{id}")
public OrderDto findOrder(@PathVariable Long id) {
Order order = orderRepository.orderOfId(OrderId.of(id));
return OrderDtoAssembler.INSTANCE.toDTO(order);
}
}
數(shù)據(jù)傳輸對象 io.study.order.web.dto.OrderDto
/**
* 訂單數(shù)據(jù)傳輸對象
*/
@ApiModel("訂單")
@Data
public class OrderDto {
/**
* 訂單 ID
*/
@ApiModelProperty("訂單ID")
private Long id;
/**
* 訂單名稱
*/
@ApiModelProperty("訂單名稱")
private String name;
}
DTO 轉(zhuǎn)換類 io.study.order.web.assembler.OrderDtoAssembler
/**
* OrderDTO<=>Order 轉(zhuǎn)換器
*/
@Mapper
public interface OrderDtoAssembler {
OrderDtoAssembler INSTANCE = Mappers.getMapper(OrderDtoAssembler.class);
/**
* DTO 轉(zhuǎn) Entity
* @param dto
* @return
*/
default Order fromDTO(OrderDto dto) {
Order order = OrderFactory.createOrder();
INSTANCE.update(dto, order);
return order;
}
/**
* Entity 轉(zhuǎn) DTO
* @param order
* @return
*/
@Mapping(source = "id.id", target = "id")
OrderDto toDTO(Order order);
@Mapping(target = "id", expression = "java(OrderId.of(orderDto.getId()))")
void update(OrderDto orderDto, @MappingTarget Order order);
}
轉(zhuǎn)換器應(yīng)該寫在外層還是內(nèi)層增淹,比如 OrderDtoAssembler 是應(yīng)該寫在 interfaces 層椿访,還是寫在 application 層,從依賴關(guān)系來考慮:假設(shè)寫在 application 層虑润,由于 DTO 是定義在 interfaces 層成玫,那么 application 需要依賴 interfaces,與
外層依賴內(nèi)層
的原則不符拳喻,那么 DTO 是否可以寫在 application 層哭当,假設(shè)現(xiàn)在有個(gè)需要對外提供的 Dubbo 接口,該接口中存在的 DTO 是需要打包給第三方的冗澈,所以并不適合寫在 application 層钦勘。
啟動(dòng)模塊代碼模型
包依賴
<dependencies>
<!-- 基礎(chǔ)設(shè)施層 -->
<dependency>
<groupId>io.study</groupId>
<artifactId>order-infrastructure</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 用戶接口層 -->
<dependency>
<groupId>io.study</groupId>
<artifactId>order-interfaces</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
啟動(dòng)器 io.study.order.OrderApplication
/**
* 應(yīng)用啟動(dòng)器
*/
@EnableOpenApi
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
springfox3.x 通過注解 @EnableOpenApi 來啟動(dòng)自動(dòng)配置
配置文件 resource/application.properties
mybatis.mapper-locations=/mapper/*.xml
spring.datasource.username: root
spring.datasource.password: xxx
spring.datasource.url: jdbc:mysql://localhost:3306/my-test?useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver