DDD 實(shí)戰(zhàn)1 - 基礎(chǔ)代碼模型

本文首先介紹一下 DDD 代碼模型的分層策略洼畅,然后介紹一下各層的職責(zé)不见,最后基于 DDD 開發(fā)一個(gè)訂單中心柑营,來實(shí)踐 DDD.

DDD 分層策略

image.png

在 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)系如下:

image.png
  • 領(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)用的所有流量入口。包括三部分:

  1. web 入口的實(shí)現(xiàn):包括 controller烈拒、DTO 定義圆裕、DTO 轉(zhuǎn)化類
  2. 消息監(jiān)聽者(消費(fèi)者):包括 XxxListener
  3. 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)用的所有流量出口祖秒。包括:

  1. 資源庫接口的實(shí)現(xiàn)
  2. 數(shù)據(jù)庫操作接口、數(shù)據(jù)庫實(shí)現(xiàn)(如果使用mybatis舟奠,則包含 resource/*.xml)竭缝、數(shù)據(jù)庫對象 DO、DO 轉(zhuǎn)化類
  3. 中間件的實(shí)現(xiàn)沼瘫、文件系統(tǒng)實(shí)現(xiàn)抬纸、緩存實(shí)現(xiàn)、消息實(shí)現(xiàn) 等
  4. 第三方服務(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模型

image.png

包依賴

    <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);
}
  1. 資源庫接口放置在領(lǐng)域?qū)有寄瑢?shí)現(xiàn)領(lǐng)域?qū)ο笞猿志没晁瑫r(shí)實(shí)現(xiàn)依賴反轉(zhuǎn)。
  2. 依賴反轉(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é)的分離
  3. 資源庫的命名推薦:對于資源庫,推薦面向集合進(jìn)行設(shè)計(jì)匈织,即資源庫的方法名采用與集合相似的方法名,例如牡直,保存和更新是 add缀匕、addAll,刪除時(shí) remove碰逸、removeAll乡小,查詢是 xxxOfccc,例如 orderOfId饵史,ordersOfCondition满钟,復(fù)數(shù)使用 xxxs 的格式,而不是 xxxList 這樣的格式
  4. 一個(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)建聚合迈嘹。
工廠的好處:

  1. 創(chuàng)建復(fù)雜的聚合削彬,簡化客戶端的使用。例如 Order 的創(chuàng)建需要注入資源庫秀仲,訂單創(chuàng)建后吃警,可以直接發(fā)布訂單創(chuàng)建事件。
  2. 可讀性好(更加符合通用語言)啄育,比如 對于創(chuàng)建訂單酌心,createOrder 就比 new Order 的語義更加明確
  3. 更好的保證一致性,防止出錯(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 為空");
        }
    }
}
  1. 推薦使用強(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í)體中
  2. 唯一標(biāo)識類是一個(gè)值對象下隧,推薦值對象設(shè)置為不可變對象奢人,使用 @lombok.Value 標(biāo)注值對象,既可標(biāo)識該對象為值對象淆院,也可以是該類變?yōu)椴豢勺冾惔锎@纾硎竞蟮?OrderId 沒有 setXxx 方法迫筑。
  3. 值對象的行為函數(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;
    }
}
  1. 聚合根是一個(gè)特殊的實(shí)體辕棚,是整個(gè)聚合對外的使者欲主,其他聚合與改聚合溝通的方式只能是通過聚合根
  2. 由于使用工廠來創(chuàng)建 Order,那么 Order 的構(gòu)造器需要設(shè)置為 protected逝嚎,防止外界直接使用進(jìn)行創(chuàng)建
  3. 實(shí)體單個(gè)屬性的校驗(yàn)需要在 setXxx 中完成自校驗(yàn)
  4. 實(shí)體是可變的扁瓢、具有唯一標(biāo)識,其唯一標(biāo)識通常需要設(shè)計(jì)成強(qiáng)類型
  5. 聚合中的 XxxRepository 可以通過上述的工廠進(jìn)行注入补君,也可以使用“雙委派”機(jī)制引几,即提供類似方法:createOrder(Order order, XxxRepository repository),然后應(yīng)用層在調(diào)用該方法時(shí)挽铁,傳入注入好的 repository 實(shí)例即可伟桅。但是這樣的方式敞掘,提高了客戶端使用的復(fù)雜性。

應(yīng)用層代碼模型

image.png

包依賴

    <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è)施層代碼模型

image.png

包依賴

    <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>
  1. mapstruct 用于實(shí)現(xiàn)模型映射器赫冬,關(guān)于其使用見 http://www.reibang.com/p/53aac78e7d60
  2. 數(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)。

用戶接口層代碼模型

image.png

包依賴

    <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)模塊代碼模型

image.png

包依賴

    <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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市亚亲,隨后出現(xiàn)的幾起案子彻采,更是在濱河造成了極大的恐慌,老刑警劉巖捌归,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肛响,死亡現(xiàn)場離奇詭異,居然都是意外死亡惜索,警方通過查閱死者的電腦和手機(jī)特笋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巾兆,“玉大人雹有,你說我怎么就攤上這事偿渡。” “怎么了霸奕?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵溜宽,是天一觀的道長。 經(jīng)常有香客問我质帅,道長适揉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任煤惩,我火速辦了婚禮嫉嘀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘魄揉。我一直安慰自己剪侮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布洛退。 她就那樣靜靜地躺著瓣俯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兵怯。 梳的紋絲不亂的頭發(fā)上彩匕,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機(jī)與錄音媒区,去河邊找鬼驼仪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛袜漩,可吹牛的內(nèi)容都是我干的绪爸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宙攻,長吁一口氣:“原來是場噩夢啊……” “哼毡泻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粘优,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤仇味,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后雹顺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丹墨,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年嬉愧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贩挣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖王财,靈堂內(nèi)的尸體忽然破棺而出卵迂,到底是詐尸還是另有隱情,我是刑警寧澤绒净,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布见咒,位于F島的核電站,受9級特大地震影響挂疆,放射性物質(zhì)發(fā)生泄漏改览。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一缤言、第九天 我趴在偏房一處隱蔽的房頂上張望宝当。 院中可真熱鬧,春花似錦胆萧、人聲如沸庆揩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽订晌。三九已至,卻和暖如春瞻离,著一層夾襖步出監(jiān)牢的瞬間腾仅,已是汗流浹背乒裆。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工套利, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鹤耍。 一個(gè)月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓肉迫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親稿黄。 傳聞我的和親對象是個(gè)殘疾皇子喊衫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容