在 DDD 實戰(zhàn)1 - 基礎(chǔ)代碼模型 介紹了 DDD 的基礎(chǔ)代碼模型锰蓬,本文來實現(xiàn)與第三方上下文的調(diào)用热康。首先介紹 ordercenter 做為消費者引用第三方接口的方式(第三方接口分別提供 Rest 和 Dubbo 兩種形式)悍赢,然后介紹 ordercenter 做為服務(wù)提供者為第三方提供服務(wù)接口的方式情龄。
引用第三方接口
設(shè)計原則:
- 第三方服務(wù)的接入需要使用防腐層進行包裝谆焊,進行防腐設(shè)計
- 第三方服務(wù)由應(yīng)用服務(wù)層進行編排
- 第三方服務(wù)的實現(xiàn)由基礎(chǔ)設(shè)施層進行實現(xiàn)
- 根據(jù)
外層依賴內(nèi)層
的原則,需要將第三方服務(wù)的防腐接口和防腐模型放置在應(yīng)用服務(wù)層弟灼,其實現(xiàn)放置在基礎(chǔ)設(shè)施層爬凑;應(yīng)用層只關(guān)心業(yè)務(wù)邏輯徙缴,不關(guān)心具體的技術(shù)實現(xiàn)(不關(guān)心是 Rest 服務(wù)還是 Dubbo 服務(wù)),基礎(chǔ)設(shè)施層來關(guān)心技術(shù)細節(jié)贰谣。
應(yīng)用服務(wù)層
應(yīng)用服務(wù) io.study.order.app.service.OrderAppService
/**
* 訂單應(yīng)用服務(wù)
*/
@Service
public class OrderAppService {
@Resource(name = "restInventoryAdaptor")
private InventoryAdaptor inventoryAdaptor;
/**
* 創(chuàng)建一個訂單
*
* @param order
*/
public void createOrder(Order order) {
/**
* 獲取商品庫存信息娜搂,進行校驗
*/
InventoryDTO inventoryDTO = inventoryAdaptor.getRemainQuality(order.getGoodsId());
if (inventoryDTO.getRemainQuantity() - order.getBuyQuality() < 0) {
throw new OrderException(400, "商品庫存不足");
}
/**
* 扣減庫存
*/
inventoryAdaptor.reduceRemainQuality(order.getGoodsId(), order.getBuyQuality());
/**
* 存儲訂單
*/
order.saveOrder(order);
}
}
第三方服務(wù)防腐接口 io.study.order.rpc.inventory.InventoryAdaptor
/**
* 庫存第三方服務(wù)接口
*/
public interface InventoryAdaptor {
/**
* 獲取商品剩余庫存信息
* @param goodsId
* @return
*/
InventoryDTO getRemainQuality(Long goodsId);
/**
* 扣減庫存
* @param goodsId
* @param reduceQuality 減少的庫存數(shù)
* @return
*/
Boolean reduceRemainQuality(Long goodsId, Integer reduceQuality);
}
第三方服務(wù)防腐模型 io.study.order.rpc.inventory.InventoryDTO
/**
* 庫存 DTO
*/
@Data
public class InventoryDTO {
/**
* 商品 ID
*/
private Long goodsId;
/**
* 剩余庫存
*/
private Integer remainQuantity;
}
基礎(chǔ)設(shè)施層
在基礎(chǔ)設(shè)施層,使用了 Rest 和 Dubbo 兩種方式來實現(xiàn)了庫存服務(wù)接口吱抚。分別來看下實現(xiàn)。
第三方服務(wù)實現(xiàn) io.study.order.rpc.impl.RestInventoryAdaptorImpl
/**
* 庫存服務(wù)(Rest 實現(xiàn))
*/
@Component("restInventoryAdaptor")
public class RestInventoryAdaptorImpl implements InventoryAdaptor {
private static final CloseableHttpClient HTTP_CLIENT = HttpClientBuilder.create().build();
@Override
public InventoryDTO getRemainQuality(Long goodsId) {
HttpGet httpGet = new HttpGet("http://localhost:8082/inventory/getInventoryInfo?goodsId=" + goodsId);
try {
CloseableHttpResponse response = HTTP_CLIENT.execute(httpGet);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
return RestInventoryTranslator.translateRestResponse2InventoryDTO(EntityUtils.toString(response.getEntity()));
}
} catch (IOException e) {
throw new OrderException(500, "調(diào)用庫存服務(wù)異常, e:" + e);
}
return null;
}
@Override
public Boolean reduceRemainQuality(Long goodsId, Integer reduceQuality) {
HttpPost httpPost = new HttpPost("http://localhost:8082/inventory/reduceRemainInventory?goodsId=" + goodsId + "&reduceQuality=" + reduceQuality);
try {
CloseableHttpResponse response = HTTP_CLIENT.execute(httpPost);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
return RestInventoryTranslator.translateRestResponse2Boolean(EntityUtils.toString(response.getEntity()));
}
} catch (IOException e) {
throw new OrderException(500, "調(diào)用庫存服務(wù)異常, e:" + e);
}
return null;
}
}
第三方服務(wù)防腐對象轉(zhuǎn)換器 io.study.order.rpc.impl.RestInventoryTranslator
/**
* 庫存服務(wù)類型轉(zhuǎn)換器(Rest)
*/
public class RestInventoryTranslator {
public static InventoryDTO translateRestResponse2InventoryDTO(String restResponse){
return JSON.parseObject(restResponse, InventoryDTO.class);
}
public static Boolean translateRestResponse2Boolean(String restResponse){
return JSON.parseObject(restResponse, Boolean.class);
}
}
庫存服務(wù) Rest 實現(xiàn)
/**
* 庫存 rest 服務(wù)
*/
@RestController
@RequestMapping("inventory")
public class InventoryController {
@GetMapping("getInventoryInfo")
public InventoryInfoDTO getInventoryInfo(@RequestParam("goodsId") Long goodsId) {
InventoryInfoDTO dto = new InventoryInfoDTO();
dto.setGoodsId(goodsId);
if (goodsId == 1L) {
dto.setRemainQuantity(100);
dto.setInTransitQuantity(101);
} else {
dto.setRemainQuantity(200);
dto.setInTransitQuantity(202);
}
return dto;
}
@PostMapping("reduceRemainInventory")
public Boolean getInventoryInfo(@RequestParam("goodsId") Long goodsId, @RequestParam("reduceQuality") Integer reduceQuality) {
return true;
}
}
再來看下 Dubbo 的服務(wù)實現(xiàn)方式考廉,Dubbo 的配置方式通常會有兩種:xml 和注解方式秘豹。這里以注解方式進行演示。首先看庫存服務(wù)提供的 Dubbo 服務(wù)昌粤。
庫存服務(wù)(Dubbo 形式)
/************************* 接口 *************************/
/**
* 庫存服務(wù)對外接口
*/
public interface InventoryFacade {
/**
* 獲取商品庫存信息
*/
InventoryInfoDTO getRemainQuality(Long goodsId);
/**
* 扣減庫存
*/
Boolean reduceRemainQuality(Long goodsId, Integer reduceQuality);
}
/************************* 實現(xiàn) *************************/
/**
* 庫存服務(wù)實現(xiàn)
*/
@DubboService(version = "1.0.0", group = "product")
public class InventoryFacadeImpl implements InventoryFacade {
@Override
public InventoryInfoDTO getRemainQuality(Long goodsId) {
InventoryInfoDTO dto = new InventoryInfoDTO();
dto.setGoodsId(goodsId);
if (goodsId == 1L) {
dto.setRemainQuantity(100);
dto.setInTransitQuantity(101);
} else {
dto.setRemainQuantity(200);
dto.setInTransitQuantity(202);
}
return dto;
}
@Override
public Boolean reduceRemainQuality(Long goodsId, Integer reduceQuality) {
return true;
}
}
/************************* 庫存服務(wù) Dubbo 接口返回值 *************************/
/**
* 庫存信息
*/
@Data
public class InventoryInfoDTO implements Serializable {
private static final long serialVersionUID = -7542780056688475990L;
/**
* 商品ID
*/
private Long goodsId;
/**
* 剩余庫存
*/
private Integer remainQuantity;
/**
* 在途庫存
*/
private Integer inTransitQuantity;
}
/************************* application.properties *************************/
dubbo.application.name=inventory
dubbo.registry.address=multicast://224.5.6.7:1234
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
/************************* pom.xml *************************/
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.8</version>
</dependency>
來看下在 ordercenter 引用第三方服務(wù)的姿勢
第三方服務(wù)實現(xiàn) io.study.order.rpc.impl.DubboInventoryAdaptorImpl
/**
* 庫存服務(wù)(Dubbo 實現(xiàn))
*/
@Component("dubboInventoryAdaptor")
public class DubboInventoryAdaptorImpl implements InventoryAdaptor {
@DubboReference(version = "1.0.0", group = "product")
private InventoryFacade inventoryFacade;
@Override
public InventoryDTO getRemainQuality(Long goodsId) {
InventoryInfoDTO inventoryInfoDTO = inventoryFacade.getRemainQuality(goodsId);
return DubboInventoryTranslator.INSTANCE.toInventoryDTO(inventoryInfoDTO);
}
@Override
public Boolean reduceRemainQuality(Long goodsId, Integer reduceQuality) {
return inventoryFacade.reduceRemainQuality(goodsId, reduceQuality);
}
}
第三方服務(wù)防腐對象轉(zhuǎn)換器 io.study.order.rpc.impl.DubboInventoryAdaptorImpl
/**
* 庫存服務(wù)防腐對象轉(zhuǎn)換器
*/
@org.mapstruct.Mapper
public interface DubboInventoryTranslator {
DubboInventoryTranslator INSTANCE = Mappers.getMapper(DubboInventoryTranslator.class);
InventoryDTO toInventoryDTO(InventoryInfoDTO inventoryInfoDTO);
}
基礎(chǔ)設(shè)層包依賴
<!-- dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
服務(wù)啟動模塊
/************************* io.study.order.OrderApplication *************************/
/**
* 應(yīng)用啟動器
*
* @author jigang
*/
@EnableOpenApi
@EnableDubbo // 啟動 Dubbo
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
/************************* application.properties *************************/
# 數(shù)據(jù)庫相關(guān)
...
# dubbo 共享
dubbo.application.name=ordercenter
dubbo.registry.address=multicast://224.5.6.7:1234
# dubbo 消費者
dubbo.consumer.check=false
這里將數(shù)據(jù)庫既绕、dubbo 的配置信息都寫在了啟動模塊中,實際上也可以將這些配置寫在他們各自使用的地方涮坐,比如可以將這些配置都寫在 infrastructure 中凄贩,同時還可以將這些配置根據(jù)功能拆分成不同的配置文件,之后在啟動類使用
@PropertySource
進行加載即可袱讹。
提供服務(wù)給第三方
如果僅提供 Rest 服務(wù)疲扎,那么當(dāng)前的用戶接口層中的 io.study.order.web.OrderController 即可。但是絕大多數(shù)情況下捷雕,需要提供類似 Dubbo 的使用方式椒丧,打成 Jar 包給第三方使用,為了避免內(nèi)部邏輯泄露救巷,以及為了打給第三方的包是一個“干凈”的包壶熏,我們抽出一個單獨的模塊 order-client
來實現(xiàn)這一目的。
設(shè)計原則:
- 創(chuàng)建
order-client
模塊:僅存儲提供給第三方的接口和對象模型- 用戶接口層來實現(xiàn)
order-client
中的接口- 領(lǐng)域?qū)又行枰褂?
order-client
中的查詢對象浦译,所以領(lǐng)域?qū)又苯右蕾?order-client
棒假,最終形成如下的依賴關(guān)系。
client 模塊
對外接口 io.study.order.facade.OrderFacade
/**
* 訂單服務(wù)
*/
public interface OrderFacade {
/**
* 查詢訂單
*/
List<OrderDTO> getOrderList(OrderQueryRequest request);
/**
* 創(chuàng)建訂單
*/
void createOrder(OrderDTO orderDTO);
}
對外接口返回模型 io.study.order.dto.OrderDTO
@Data
public class OrderDTO implements Serializable {
private static final long serialVersionUID = 8642623148247246765L;
/**
* 訂單ID
*/
private Long id;
/**
* 訂單名稱
*/
private String name;
/**
* 商品ID
*/
private Long goodsId;
/**
* 購買數(shù)量
*/
private Integer buyQuality;
}
對外接口請求參數(shù) io.study.order.dto.OrderQueryRequest
/**
* order 查詢請求參數(shù)
*/
@Data
public class OrderQueryRequest implements Serializable {
private static final long serialVersionUID = 3330101115728844788L;
/**
* 訂單ID
*/
private Long orderId;
/**
* 訂單名稱
*/
private String orderName;
}
用戶接口層
/**
* 訂單服務(wù)實現(xiàn)
*/
@DubboService(version = "1.0.0", group = "product")
public class OrderFacadeImpl implements OrderFacade {
@Resource
private OrderAppService orderAppService;
@Resource
private OrderRepository orderRepository;
@Override
public List<OrderDTO> getOrderList(OrderQueryRequest request) {
List<Order> orderList = orderRepository.ordersOfCondition(request);
return OrderDTOAssembler.INSTANCE.toDTOList(orderList);
}
@Override
public void createOrder(OrderDTO orderDTO) {
orderAppService.createOrder(OrderDTOAssembler.INSTANCE.fromDTO(orderDTO));
}
}
Dubbo 服務(wù)的配置和啟動與上述的庫存服務(wù)相同精盅,不在贅述帽哑,接下來著重看一下 OrderQueryRequest 對象的傳輸路徑。
類:OrderFacade -> OrderFacadeImpl -> OrderRepository -> OrderRepositoryImpl
層:client -> interfaces -> domain -> infrastructure
由于 domain 中要使用到 client 定義的對象渤弛,那么 domain 要依賴 client祝拯,乍一看,不符合 外層依賴內(nèi)層
的原則,實際上佳头,在 DDD 分層模型中鹰贵,是沒有 client 這個模塊的;另外康嘉, 外層依賴內(nèi)層
原則的目的是為了保證內(nèi)層的穩(wěn)定性碉输,這個穩(wěn)定怎么理解?個人理解為亭珍,模塊內(nèi)的代碼不隨外界技術(shù)的變動而變動敷钾,例如,將存儲從 mysql 換成了 oracle肄梨,我們僅需要處理 infrastructure 層即可阻荒,其他內(nèi)層不動;在比如众羡,當(dāng)前的都是直接穿數(shù)據(jù)庫的侨赡,想使用 Cache Aside Pattern 加一層緩存,那么僅需要在 infrastructure 資源庫的實現(xiàn)中進行修改即可粱侣,內(nèi)層邏輯不應(yīng)該動羊壹。但是現(xiàn)在如果是業(yè)務(wù)本身就發(fā)生了變化,那么內(nèi)部的模型除了部分可以使用開閉設(shè)計避免變動時齐婴,大部分情況下還是要動的油猫,不管是 application 還是 domain 層,client 被 domain 依賴就是這個道理柠偶,假設(shè) domain 不依賴 client情妖,那么我們需要在 domain 層也一模一樣的設(shè)計一個查詢模型,然后在用戶接口層進行轉(zhuǎn)換即可嚣州,這樣也是可以實現(xiàn)的鲫售,但是必要性是否有,可以考慮一下该肴。