一、序言
領域驅動設計是一種解決業務複雜性的設計思想,不是一種标準規則的解決方法。在本文中的實戰示例可能會與常見的DDD規則方法不太一樣,是簡單、入門級别,新手可以快速實踐版的DDD。如果不熟悉DDD設計思想可看下
基礎思想篇二、設計階段
領域模組化設計階段常見的方法有
四色模組化法、EventSourcing等 推薦一篇博文
正确了解領域模組化,本文DDD的設計方法是為了新手可以實戰落地,又能表達出DDD思想的簡單易懂設計方法(非新手階段建議了解下四色模組化、EventSoucing等領域模組化思想)
了解業務
領域驅動設計是對業務模型在系統設計中的一種表現形式,在進行DDD實戰前一定要熟悉業務,不熟悉業務無法把業務模型翻譯成域模型。了解業務-站在業務方和産品角度,梳理系統業務的所有細節,明白每一個業務細節點。
下面舉一個簡單交易系統DDD(交易系統太寬廣,本文示例是僅包含建立訂單這一個階段的行為,不包含訂單狀态變更、訂單管理這些操作)落地示例,在電商或外賣行業交易系統建立訂單都會有收貨人、下單人、商品、訂單金額等業務屬性和建立訂單行為。

業務抽象
在梳理業務過程中會把業務每一個具體的點都給羅列出來,是一盤零碎的業務點。通過對業務的了解進行抽象,把相關性的業務點進行分組聚合。業務抽象過程中涉及邊界劃分問題,比如收貨人和下單人資訊是一起抽象成使用者資訊,還是分别抽象成使用者資訊和位址資訊?在訂單業務中,一個使用者可以下多筆訂單,該使用者下的所有訂單都是同一個使用者資訊,但使用者的每筆訂單的收貨資訊可以不同(可以給自己買東西、給親朋好友買東西),在訂單業務中使用者資訊和位址資訊是需要抽象成兩個獨立的業務子產品。
模型翻譯
在經過業務抽象之後,整體業務模型已經清晰明了,在域模型設計上,隻需要把業務模型經過簡單翻譯映射成域模型即可。
子域劃分
業務模型翻譯成域模型後,當一個域模型比較複雜的時候需要把一個域模型進行子域劃分
三、實作階段
搭建項目結構
DDD項目與傳統三層項目結構比較類似,DDD中API包是接口定義負責對外打包給外部(SOA和Http)調用使用,Service包是API包接口的實作,不做具體業務邏輯處理,隻做資料的轉換,把Domain層的域模型轉換成對外使用的字段。Domain層是所有的具體業務邏輯處理層。
傳統三層架構 原生領域驅動架構
子產品和包
一個簡單的DDD項目會包含API、Service、Domain三個子產品
在Domain子產品中,一個基本的域模型會包含 Entity、Value Object、Service、Factory、Repository這幾個包(
Entity、Service含義)。metadata包是域模型中繼資料包,metadata包下的接口是一個DDD設計的辨別,這個包通常會抽象成一個獨立的子產品供其他項目依賴使用。
域模型搭建
訂單域中肯定會有位址、使用者、店鋪、訂單商品等資訊,統一直接在訂單Model中肯定會很臃腫并且不利于維護,應把訂單域拆分成一個個子域。在拆分成子域選模型(Entity、ValueObject、Service)的時候會存在模棱兩可的情況,這個訂單位址資訊子域應該是Entity、還ValueObject,如果不太确定就以最簡單化原則,Entity複雜度大于ValueObject,訂單位址域就可以以ValueObject模型存在。
metadata包類
/**
* 域模型工廠
* @param <T>
*/
public interface DomainFactory<T> {
}
/**
* 實體
* @param <T>
*/
public interface Entity<T> extends Serializable {
default boolean sameIdentityAs(T other) {
return true;
}
}
/**
* 持久化
* @param <T>
*/
public interface Repository<T> {
}
/**
*服務
* @param <T>
*/
public interface Service<T> {
}
/**
* 值對象
* @param <T>
*/
public interface ValueObject<T> extends Serializable {
default boolean sameValueAs(T other) {
return true;
}
}
entity包類
/**
* 訂單實體
*/
@Data
public class OrderE implements Entity<OrderE> {
/**
* 使用者資訊
*/
private OrderUserV orderUserV = new OrderUserV();
/**
* 購物車資訊
*/
private OrderCartV orderCartV = new OrderCartV();
/**
* 位址相關資訊
*/
private OrderAddressV orderAddressV = new OrderAddressV();
/**
* 店鋪相關操作
*/
private OrderShopV orderShopV = new OrderShopV();
/**
* 訂單基礎資訊
*/
private OrderBasicInfoV orderBasicInfoV = new OrderBasicInfoV();
/**
* 訂單金額
*/
private OrderMoneyV orderMoneyV = new OrderMoneyV();
/**
* 持久化操作
*/
private OrderR orderR;
/**
* 建立訂單
*
* @return
*/
public OrderE createOrder(OrderE orderE) throws Exception {
return orderR.createOrder(orderE);
}
/**
* 從主庫查詢
*
* @param orderE
* @return
* @throws Exception
*/
public OrderE queryOrderFromDBMater(OrderE orderE) throws Exception {
return orderR.queryOrderFromDBMaster(orderE);
}
/**
* 從Eos等三方查詢
*
* @param orderNumber
* @return
* @throws Exception
*/
public void queryOrderFromEos(String orderNumber) throws Exception {
orderR.queryOrderFromEos(orderNumber);
}
/**
* 訂單有效
*
* @param orderNumber
* @throws Exception
*/
public void enableOrder(String orderNumber) throws Exception {
orderR.enableOrder(orderNumber);
}
}
repository包類
/**
* 訂單資料源 操作層 所有的與外部的互動都走這一層
*/
public interface OrderR extends Repository<OrderR> {
/**
* 建立訂單
* @param orderE
* @return
* @throws Exception
*/
OrderE createOrder(OrderE orderE) throws Exception;
/**
* 從DB主庫查詢資訊
* @param orderE
* @return
* @throws Exception
*/
OrderE queryOrderFromDBMaster(OrderE orderE) throws Exception;
/**
* 從EOS查詢訂單詳情
* @param orderNumber
* @return
* @throws Exception
*/
void queryOrderFromEos(String orderNumber) throws Exception;
/**
* 開啟訂單
* @param orderNumber
* @throws Exception
*/
void enableOrder(String orderNumber) throws Exception;
}
@Service
public class OrderRImpl implements OrderR {
private static final Log LOG = LogFactory.getLog(OrderRImpl.class);
@Override
public OrderE createOrder(OrderE orderE) throws Exception {
return null;
}
@Override
public OrderE queryOrderFromDBMaster(OrderE orderE) throws Exception {
return null;
}
@Override
public void queryOrderFromEos(String orderNumber) throws Exception {
}
@Override
public void enableOrder(String orderNumber) throws Exception {
}
}
value_object包類
/**
* 訂單位址值對象
*/
@Data
public class OrderAddressV implements ValueObject<OrderAddressV> {
/**
* 位址ID
*/
private String addressId;
/**
* 訂單位址
*/
private String address;
/**
* 收貨人
*/
private String name;
/**
* 收貨人手機号
*/
private String phone;
/**
* 經度
*/
private BigDecimal longitude;
/**
* 緯度
*/
private BigDecimal latitude;
/**
* 位址類型
*/
private Integer addressType;
/**
* 三方位址Id
*/
private String thirdAddressId;
/**
* longitude、latitude轉HashString
*
* @param longitude
* @param latitude
* @return
*/
public String getGeoHash(BigDecimal longitude, BigDecimal latitude, int length) {
String geoHash = GeoHash.encodeHash(latitude.doubleValue(), longitude.doubleValue(), length);
return geoHash;
}
/**
* longitude、latitude轉HashString 預設12位
* @param longitude
* @param latitude
* @return
*/
public String getGeoHash(BigDecimal longitude, BigDecimal latitude) {
return getGeoHash(longitude, latitude, 12);
}
/**
* geoHash 轉經緯度
*
* @param geoHash
* @return
*/
public BigDecimal[] getLatLog(String geoHash) {
LatLong latLong = GeoHash.decodeHash(geoHash);
BigDecimal[] bigDecimals = new BigDecimal[2];
bigDecimals[0] = BigDecimal.valueOf(latLong.getLat());
bigDecimals[1] = BigDecimal.valueOf(latLong.getLon());
return bigDecimals;
}
}
@Data
public class OrderBasicInfoV implements ValueObject<OrderBasicInfoV> {
/**
* 訂單Id
*/
private String orderId;
/**
* 訂單建立時間
*/
private LocalDateTime createAt;
/**
* 訂單狀态
*/
private Integer orderStatus;
}
@Data
public class OrderCartV implements ValueObject<OrderCartV> {
/**
* 購物車Id
*/
private String cartId;
/**
* 購物車建立時間
*/
private LocalDateTime createTime;
/**
* 購物車總價
*/
private BigDecimal total;
/**
* 購物車原價
*/
private BigDecimal originalTotal;
/**
* 最低多少元起送
*/
private BigDecimal minDeliverAmount;
/**
* 配送費
*/
private BigDecimal deliveryFee;
/**
* 商品總數量
*/
private Integer totalQuantity;
/**
* 商品List
*/
private List<Object> groups;
/**
* 優惠資訊資訊
*/
private List<Object> extraList;
}
@Data
public class OrderMoneyV implements ValueObject<OrderMoneyV> {
/**
* 訂單原價originPrice
*/
private BigDecimal originalPrice = BigDecimal.ZERO;
/**
* 訂單現價 price
*/
private BigDecimal price = BigDecimal.ZERO;
}
@Data
public class OrderShopV implements ValueObject<OrderShopV> {
/**
* 店鋪Id
*/
private Long shopId;
/**
* 店鋪名稱
*/
private String shopName;
}
/**
* 訂單使用者值對象
*/
@Data
public class OrderUserV implements ValueObject<OrderUserV> {
/**
* 使用者Id
*/
private Long userId;
/**
* 使用者姓名
*/
private String userName;
/**
* 使用者手機号
*/
private String phone;
}
API和Service層搭建
/**
* 示例demo 隻是為了示範DDD如何落地,寫的簡單,可能不太符合集團代碼規範
*
*
*/
public interface OrderService {
/**
* 建立訂單
* @param createOrderDto
*/
OrderDto createOrder(CreateOrderDto createOrderDto);
/**
* 從主庫查詢
*
* @param orderNumber
* @return
* @throws Exception
*/
OrderDto queryOrderFromDBMater(String orderNumber);
}
@Data
public class CreateOrderDto {
/**
* 訂單位址
*/
private String address;
/**
* 收貨人
*/
private String name;
/**
* 收貨人手機号
*/
private String phone;
/**
* 經度
*/
private BigDecimal longitude;
/**
* 緯度
*/
private BigDecimal latitude;
/**
* 使用者Id
*/
private String userId;
/**
* 購物車Id
*/
private String cartId;
/**
* 店鋪Id
*/
private Long shopId;
}
@Data
public class OrderDto {
/**
* 訂單位址
*/
private String address;
/**
* 收貨人
*/
private String name;
/**
* 收貨人手機号
*/
private String phone;
/**
* 經度
*/
private BigDecimal longitude;
/**
* 緯度
*/
private BigDecimal latitude;
/**
* 使用者Id
*/
private Long userId;
/**
* 購物車Id
*/
private String cartId;
/**
* 店鋪Id
*/
private String shopId;
/**
* 訂單Id
*/
private String orderId;
/**
* 訂單價格
*/
private BigDecimal orderPrice;
}
package me.ele.eo.enterprise.service;
import me.ele.eo.enterprise.order.entity.OrderE;
import me.ele.eo.enterprise.order.factory.OrderFactory;
import me.ele.eo.enterprise.order.value_object.OrderAddressV;
import me.ele.eo.enterprise.order.value_object.OrderShopV;
import me.ele.eo.enterpriser.CreateOrderDto;
import me.ele.eo.enterpriser.OrderDto;
import me.ele.eo.enterpriser.api.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderFactory orderFactory;
@Override
public OrderDto createOrder(CreateOrderDto createOrderDto) {
OrderE orderE = orderFactory.createOrderE();
buildOrderE(createOrderDto, orderE);
try {
orderE.createOrder(orderE);
} catch (Exception e) {
e.printStackTrace();
}
return convertToResponse(orderE);
}
@Override
public OrderDto queryOrderFromDBMater(String orderNumber) {
OrderE orderE = orderFactory.createOrderE();
orderE.getOrderBasicInfoV().setOrderId(orderNumber);
try {
orderE.queryOrderFromDBMater(orderE);
} catch (Exception e) {
e.printStackTrace();
}
return convertToResponse(orderE);
}
private void buildOrderE(CreateOrderDto createOrderDto, OrderE orderE) {
OrderAddressV orderAddressV = orderE.getOrderAddressV();
orderAddressV.setAddress(createOrderDto.getAddress());
orderAddressV.setLatitude(createOrderDto.getLatitude());
orderAddressV.setLongitude(createOrderDto.getLongitude());
orderAddressV.setName(createOrderDto.getName());
orderAddressV.setPhone(createOrderDto.getPhone());
orderE.setOrderAddressV(orderAddressV);
OrderShopV orderShopV = orderE.getOrderShopV();
orderShopV.setShopId(createOrderDto.getShopId());
//TODO 轉換其他屬性值 (這裡不列舉了)
}
private OrderDto convertToResponse(OrderE orderE) {
OrderDto responseDto = new OrderDto();
responseDto.setUserId(orderE.getOrderUserV().getUserId());
responseDto.setName(orderE.getOrderAddressV().getName());
//TODO 轉換其他屬性值 (這裡不列舉了)
return responseDto;
}
}
一些釋義
Model生命周期管理方式
域模型中Model在系統環境中的生命周期管理有兩種方式,一種是使用容器管理生命周期在Spring中Model類上打@Service、@Component等注解 ;另一種是通過手動管理生命周期可以與普通DTO、POJO一樣通過new對象 的形式使用(本文中就是與普通的DTO一樣通過new對象形式使用的),我比較推薦new對象的形式使用。
new對象形式的Model如何動态注入Repository資料源(是一個接口),資料源操作負責實作這個接口,與資料源互動(DB、HTTP......)。如何在一個普通new對象中注入Spring容器管理的對象?通過控制反轉在使用時再值設定進去,通過Factory實作控制反轉,這點是避免通過容器托管Model的關鍵所在。@輝子
盒馬領域驅動設計實踐這篇博文詳細的闡述了領域模型下的依賴注入。
其他
@Data