learn.lianglianglee.com/专栏/领域驱动设计实践(完)/088 对象关系映射(下).md.html
2022-05-11 19:04:14 +08:00

1223 lines
44 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>088 对象关系映射(下).md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/领域驱动设计实践(完)/001 「战略篇」访谈 DDD 和微服务是什么关系?.md.html">001 「战略篇」访谈 DDD 和微服务是什么关系?.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/002 「战略篇」开篇词:领域驱动设计,重焕青春的设计经典.md.html">002 「战略篇」开篇词:领域驱动设计,重焕青春的设计经典.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/003 领域驱动设计概览.md.html">003 领域驱动设计概览.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/004 深入分析软件的复杂度.md.html">004 深入分析软件的复杂度.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/005 控制软件复杂度的原则.md.html">005 控制软件复杂度的原则.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/006 领域驱动设计对软件复杂度的应对(上).md.html">006 领域驱动设计对软件复杂度的应对(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/007 领域驱动设计对软件复杂度的应对(下).md.html">007 领域驱动设计对软件复杂度的应对(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/008 软件开发团队的沟通与协作.md.html">008 软件开发团队的沟通与协作.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/009 运用领域场景分析提炼领域知识(上).md.html">009 运用领域场景分析提炼领域知识(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/010 运用领域场景分析提炼领域知识(下).md.html">010 运用领域场景分析提炼领域知识(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/011 建立统一语言.md.html">011 建立统一语言.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/012 理解限界上下文.md.html">012 理解限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/013 限界上下文的控制力(上).md.html">013 限界上下文的控制力(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/014 限界上下文的控制力(下).md.html">014 限界上下文的控制力(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/015 识别限界上下文(上).md.html">015 识别限界上下文(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/016 识别限界上下文(下).md.html">016 识别限界上下文(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/017 理解上下文映射.md.html">017 理解上下文映射.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/018 上下文映射的团队协作模式.md.html">018 上下文映射的团队协作模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/019 上下文映射的通信集成模式.md.html">019 上下文映射的通信集成模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/020 辨别限界上下文的协作关系(上).md.html">020 辨别限界上下文的协作关系(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/021 辨别限界上下文的协作关系(下).md.html">021 辨别限界上下文的协作关系(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/022 认识分层架构.md.html">022 认识分层架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/023 分层架构的演化.md.html">023 分层架构的演化.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/024 领域驱动架构的演进.md.html">024 领域驱动架构的演进.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/025 案例 层次的职责与协作关系(图文篇).md.html">025 案例 层次的职责与协作关系(图文篇).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/026 限界上下文与架构.md.html">026 限界上下文与架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/027 限界上下文对架构的影响.md.html">027 限界上下文对架构的影响.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/028 领域驱动设计的代码模型.md.html">028 领域驱动设计的代码模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/029 代码模型的架构决策.md.html">029 代码模型的架构决策.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/030 实践 先启阶段的需求分析.md.html">030 实践 先启阶段的需求分析.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/031 实践 先启阶段的领域场景分析(上).md.html">031 实践 先启阶段的领域场景分析(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/032 实践 先启阶段的领域场景分析(下).md.html">032 实践 先启阶段的领域场景分析(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/033 实践 识别限界上下文.md.html">033 实践 识别限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/034 实践 确定限界上下文的协作关系.md.html">034 实践 确定限界上下文的协作关系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/035 实践 EAS 的整体架构.md.html">035 实践 EAS 的整体架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/036 「战术篇」访谈DDD 能帮开发团队提高设计水平吗?.md.html">036 「战术篇」访谈DDD 能帮开发团队提高设计水平吗?.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/037 「战术篇」开篇词:领域驱动设计的不确定性.md.html">037 「战术篇」开篇词:领域驱动设计的不确定性.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/038 什么是模型.md.html">038 什么是模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/039 数据分析模型.md.html">039 数据分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/040 数据设计模型.md.html">040 数据设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/041 数据模型与对象模型.md.html">041 数据模型与对象模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/042 数据实现模型.md.html">042 数据实现模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/043 案例 培训管理系统.md.html">043 案例 培训管理系统.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/044 服务资源模型.md.html">044 服务资源模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/045 服务行为模型.md.html">045 服务行为模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/046 服务设计模型.md.html">046 服务设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/047 领域模型驱动设计.md.html">047 领域模型驱动设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/048 领域实现模型.md.html">048 领域实现模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/049 理解领域模型.md.html">049 理解领域模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/050 领域模型与结构范式.md.html">050 领域模型与结构范式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/051 领域模型与对象范式(上).md.html">051 领域模型与对象范式(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/052 领域模型与对象范式(中).md.html">052 领域模型与对象范式(中).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/053 领域模型与对象范式(下).md.html">053 领域模型与对象范式(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/054 领域模型与函数范式.md.html">054 领域模型与函数范式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/055 领域驱动分层架构与对象模型.md.html">055 领域驱动分层架构与对象模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/056 统一语言与领域分析模型.md.html">056 统一语言与领域分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/057 精炼领域分析模型.md.html">057 精炼领域分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/058 彩色 UML 与彩色建模.md.html">058 彩色 UML 与彩色建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/059 四色建模法.md.html">059 四色建模法.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/060 案例 订单核心流程的四色建模.md.html">060 案例 订单核心流程的四色建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/061 事件风暴与业务全景探索.md.html">061 事件风暴与业务全景探索.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/062 事件风暴与领域分析建模.md.html">062 事件风暴与领域分析建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/063 案例 订单核心流程的事件风暴.md.html">063 案例 订单核心流程的事件风暴.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/064 表达领域设计模型.md.html">064 表达领域设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/065 实体.md.html">065 实体.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/066 值对象.md.html">066 值对象.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/067 对象图与聚合.md.html">067 对象图与聚合.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/068 聚合设计原则.md.html">068 聚合设计原则.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/069 聚合之间的关系.md.html">069 聚合之间的关系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/070 聚合的设计过程.md.html">070 聚合的设计过程.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/071 案例 培训领域模型的聚合设计.md.html">071 案例 培训领域模型的聚合设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/072 领域模型对象的生命周期-工厂.md.html">072 领域模型对象的生命周期-工厂.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/073 领域模型对象的生命周期-资源库.md.html">073 领域模型对象的生命周期-资源库.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/074 领域服务.md.html">074 领域服务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/075 案例 领域设计模型的价值.md.html">075 案例 领域设计模型的价值.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/076 应用服务.md.html">076 应用服务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/077 场景的设计驱动力.md.html">077 场景的设计驱动力.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/078 案例 薪资管理系统的场景驱动设计.md.html">078 案例 薪资管理系统的场景驱动设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/079 场景驱动设计与 DCI 模式.md.html">079 场景驱动设计与 DCI 模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/080 领域事件.md.html">080 领域事件.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/081 发布者—订阅者模式.md.html">081 发布者—订阅者模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/082 事件溯源模式.md.html">082 事件溯源模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/083 测试优先的领域实现建模.md.html">083 测试优先的领域实现建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/084 深入理解简单设计.md.html">084 深入理解简单设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/085 案例 薪资管理系统的测试驱动开发(上).md.html">085 案例 薪资管理系统的测试驱动开发(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/086 案例 薪资管理系统的测试驱动开发(下).md.html">086 案例 薪资管理系统的测试驱动开发(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/087 对象关系映射(上).md.html">087 对象关系映射(上).md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/领域驱动设计实践(完)/088 对象关系映射(下).md.html">088 对象关系映射(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/089 领域模型与数据模型.md.html">089 领域模型与数据模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/090 领域驱动设计对持久化的影响.md.html">090 领域驱动设计对持久化的影响.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/091 领域驱动设计体系.md.html">091 领域驱动设计体系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/092 子领域与限界上下文.md.html">092 子领域与限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/093 限界上下文的边界与协作.md.html">093 限界上下文的边界与协作.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/094 限界上下文之间的分布式通信.md.html">094 限界上下文之间的分布式通信.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/095 命令查询职责分离.md.html">095 命令查询职责分离.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/096 分布式柔性事务.md.html">096 分布式柔性事务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/097 设计概念的统一语言.md.html">097 设计概念的统一语言.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/098 模型对象.md.html">098 模型对象.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/099 领域驱动设计参考过程模型.md.html">099 领域驱动设计参考过程模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/100 领域驱动设计的精髓.md.html">100 领域驱动设计的精髓.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/101 实践 员工上下文的领域建模.md.html">101 实践 员工上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/102 实践 考勤上下文的领域建模.md.html">102 实践 考勤上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/103 实践 项目上下文的领域建模.md.html">103 实践 项目上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/104 实践 培训上下文的业务需求.md.html">104 实践 培训上下文的业务需求.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/105 实践 培训上下文的领域分析建模.md.html">105 实践 培训上下文的领域分析建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/106 实践 培训上下文的领域设计建模.md.html">106 实践 培训上下文的领域设计建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/107 实践 培训上下文的领域实现建模.md.html">107 实践 培训上下文的领域实现建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/108 实践 EAS 系统的代码模型.md.html">108 实践 EAS 系统的代码模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/109 后记:如何学习领域驱动设计.md.html">109 后记:如何学习领域驱动设计.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>088 对象关系映射(下)</h1>
<h3>JPA 的应对之道</h3>
<h4>对象模式的阻抗不匹配</h4>
<p>符合面向对象设计原则的领域模型,其中一个重要特征是建立了高内聚低耦合的对象图。要做到这一点,就需得将具有高内聚关系的概念<strong>封装</strong>为一个类,通过显式的类型体现领域中的概念,这样既提高了代码的可读性,又保证了职责的合理分配,避免出现一个庞大的实体类。领域驱动设计更强调这一点,并因此还引入了值对象的概念,用以表现那些无需身份标识却又具有内聚知识的领域概念。因此,一个设计良好的领域模型,往往会形成一个具有嵌套层次的对象图模型结构。</p>
<p>虽然嵌套层次的领域模型与扁平结构的关系数据模型并不匹配,但通过 JPA 提供的 @Embedded 与 @Embeddable 标注可以非常容易实现这一嵌套组合的对象关系,例如 Employee 类的 address 属性和 email 属性:</p>
<pre><code class="language-java">@Entity
@Table(name=&quot;employees&quot;)
public class Employee extends AbstractEntity&lt;EmployeeId&gt; implements AggregateRoot&lt;Employee&gt; {
@EmbeddedId
private EmployeeId employeeId;
private String name;
@Embedded
private Email email;
@Embedded
private Address address;
}
@Embeddable
public class Address {
private String country;
private String province;
private String city;
private String street;
private String zip;
public Address() {
}
}
@Embeddable
public class Email {
@Column(name = &quot;email&quot;)
private String value;
public String value() {
return this.value;
}
}
</code></pre>
<p>以上定义的领域类,都是 Employee 实体的值对象。注意,为了支持 JPA 实现框架通过反射创建对象,若为值对象定义了带参的构造函数,就需要显式定义默认构造函数,如 Address 类的定义。</p>
<p>对比 EmployeeId 类的定义,你会发现该类的定义仍然属于值对象的范畴,只是由于该类型在数据模型中作为主键,故而应将该字段声明为 @EmbeddedId 标注。</p>
<p>无论是 Address、Email 还是 EmployeeId 类,它们在领域对象模型中虽然被定义为独立的类,但在数据模型中却都是 employees 表中的列。其中 Email 类仅仅是表中的一个列,定义为类的目的是体现电子邮件的领域概念,并有利于封装对邮件地址的验证逻辑; Address 类封装了多个内聚的值,体现为 country、province 等列,以利于维护地址概念的完整性,同时也可以实现对领域概念的重用。创建 employees 表的 SQL 脚本如下所示:</p>
<pre><code class="language-sql">CREATE TABLE employees(
id VARCHAR(50) NOT NULL,
name VARCHAR(20) NOT NULL,
email VARCHAR(50) NOT NULL,
employeeType SMALLINT NOT NULL,
gender VARCHAR(10),
salary DECIMAL(10, 2),
currency VARCHAR(10),
country VARCHAR(20),
province VARCHAR(20),
city VARCHAR(20),
street VARCHAR(100),
zip VARCHAR(10),
mobilePhone VARCHAR(20),
homePhone VARCHAR(20),
officePhone VARCHAR(20),
onBoardingDate DATE NOT NULL
PRIMARY KEY(id)
);
</code></pre>
<p>如果一个值对象在数据模型中被设计为一个独立的表,但由于它无需定义主键,需要依附于一个实体表,因此在领域模型中依旧标记为 @Embeddable。这既体现了面向对象的封装思想又表达了一对一或一对多的关系。SalariedEmployee 聚合中的 Absence 值对象就遵循了这样的设计原则。</p>
<p>面向对象的封装思想体现了对细节的隐藏,正确的封装还体现为对职责的合理分配。遵循“信息专家模式”,无论是领域模型中的实体,还是值对象,都应该从它们拥有的数据出发,判断领域行为是否应该分配给这些领域模型类。如 HourlyEmployee 实体类的 payroll(Period) 方法、Absence 值对象的 isIn(Period) 与 isPaidLeave() 方法,乃至于 Salary 值对象的 add(Salary) 等方法,都充分体现了对领域行为的合理封装,避免了贫血模型的出现:</p>
<pre><code class="language-java">public class HourlyEmployee extends AbstractEntity&lt;EmployeeId&gt; implements AggregateRoot&lt;HourlyEmployee&gt; {
public Payroll payroll(Period period) {
if (Objects.isNull(timeCards) || timeCards.isEmpty()) {
return new Payroll(this.employeeId, period.beginDate(), period.endDate(), Salary.zero());
}
Salary regularSalary = calculateRegularSalary(period);
Salary overtimeSalary = calculateOvertimeSalary(period);
Salary totalSalary = regularSalary.add(overtimeSalary);
return new Payroll(this.employeeId, period.beginDate(), period.endDate(), totalSalary);
}
}
public class Absence {
public boolean isIn(Period period) {
return period.contains(leaveDate);
}
public boolean isPaidLeave() {
return leaveReason.isPaidLeave();
}
}
public class Salary {
public Salary add(Salary salary) {
throwExceptionIfNotSameCurrency(salary);
return new Salary(value.add(salary.value).setScale(SCALE), currency);
}
public Salary subtract(Salary salary) {
throwExceptionIfNotSameCurrency(salary);
return new Salary(value.subtract(salary.value).setScale(SCALE), currency);
}
public Salary multiply(double factor) {
return new Salary(value.multiply(toBigDecimal(factor)).setScale(SCALE), currency);
}
public Salary divide(double multiplicand) {
return new Salary(value.divide(toBigDecimal(multiplicand), SCALE, BigDecimal.ROUND_DOWN), currency);
}
}
</code></pre>
<p>这充分证明领域模型对象既可以作为持久化对象,搭建起对象与关系表之间的桥梁;又可以体现丰富的包含领域行为在内的领域概念与领域知识。合二者为一体的领域模型对象被定义在领域层,位于基础设施层的资源库实现可以访问它们,避免定义重复的领域模型与数据模型。</p>
<p>对象模式中的继承更为特殊,因为关系表自身不具备继承能力,这与对象之间的组合关系不同。若仅仅为了重用而使用继承,那么在数据模型中只需保证关系表的列无需重复定义即可。因此,可以简单地将继承了父类的子类看做是一张关系表,父类与所有子类对应的字段都放在这一张表中,就好似对集合求并集一般。这种策略在 ORM 中被称之为 Single-Table 策略。为了区分子类,这一张单表必须额外定义一个列,作为区分子类的标识列,在 JPA 中被定义为 @DiscriminatorColumn。例如如果需要为 Employee 建立继承体系,则它的标识列就是 employeeType 列。</p>
<p>若子类之间的差异太大,采用 Single-Table 策略实现继承的方式会让表的冗余显得格外明显。因为有的子类并没有这些列,却不得不为属于该类型的行记录提供这些列的存储空间。要避免这种冗余,可以采用 Joined-Subclass 策略实现继承。采用这种策略时,继承关系中的每一个实体类,无论是具体类还是抽象类,数据库中都有一个单独的表与之对应。子实体对应的表无需定义从根实体继承而来的列,而是通过共享主键的方式进行关联。</p>
<p>由于 Single-Table 策略是 ORM 默认的继承策略,若要采用 Joined-Subclass 策略,需要在父实体类的定义中显式声明其继承策略,如下所示:</p>
<pre><code class="language-java">@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name=&quot;employees&quot;)
public class Employee {}
</code></pre>
<p>采用 Joined-Subclass 策略实现继承时,数据模型中子实体表与父实体表之间的关系实则是一对一的连接关系,这可以认为是为了解决对象模式阻抗不匹配的无奈之举,毕竟用连接关联关系表达继承,怎么看都显得有些别扭。当领域模型中继承体系的子类较多时,这一设计还会影响查询效率,因为它可能牵涉到多张表的连接。</p>
<p>如果既不希望产生不必要的数据冗余,又不愿意表连接拖慢查询的速度,则可以采用 Table-Per-Class 策略。采用这种策略时,继承体系中的每个实体类都对应一个独立的表,其中,父实体对应的表仅包含父实体的字段,子实体对应的表不仅包含了自身的字段,同时还包含了父实体的字段。这相当于用数据表样式的冗余来避免数据的冗余,用单表来避免不必要的连接。如果子类之间的差异较大,我更倾向于采用 Table-Per-Class 策略,而非 Joined-Subclass 策略。</p>
<p>继承的目的绝不仅仅是为了重用,甚至可以说重用并非它的主要价值,毕竟“聚合/合成优先重用原则”已经成为了面向对象设计的金科玉律。继承的主要价值在于支持多态,这样就能利用 Liskov 替换原则,子类能够替换父类而不改变其行为,并允许定义新的子类来满足功能扩展的需求,保证对扩展是开放的。在 Java 或 C# 这样的语言中,由于受到单继承的约束,定义抽象接口以实现多态更为普遍。无论是继承多态还是接口多态,都应站在领域逻辑的角度,思考是否需要引入合理的抽象来应对未来需求的变化。在采用继承多态时,需要考虑对应的数据模型是否能够在对象关系映射中实现继承,并选择合理的继承策略来确定关系表的设计。至于接口多态是对领域行为的抽象,与领域模型的持久化无关,在定义抽象接口时,无需考虑领域模型与数据模型之间的映射。</p>
<h4>与持久化无关的领域模型</h4>
<p>并非所有的领域模型对象都需要持久化到数据表,一些领域概念之所以定义为值对象,仅仅是为了封装领域行为,表达一种高内聚的领域概念,以便于领域对象更好地分配职责,隐藏实现细节,支持良好的行为协作。例如,与 HourlyEmployee 聚合根交互的 Period 类,其作用是体现一个结算周期,作为薪资计算的条件:</p>
<pre><code class="language-java">public class Period {
private LocalDate beginDate;
private LocalDate endDate;
public Period(LocalDate beginDate, LocalDate endDate) {
this.beginDate = beginDate;
this.endDate = endDate;
}
public Period(YearMonth yearMonth) {
int year = yearMonth.getYear();
int month = yearMonth.getMonthValue();
int firstDay = 1;
int lastDay = yearMonth.lengthOfMonth();
this.beginDate = LocalDate.of(year, month, firstDay);
this.endDate = LocalDate.of(year, month, lastDay);
}
public Period(int year, int month) {
if (month &lt; 1 || month &gt; 12) {
throw new InvalidDateException(&quot;Invalid month value.&quot;);
}
int firstDay = 1;
int lastDay = YearMonth.of(year, month).lengthOfMonth();
this.beginDate = LocalDate.of(year, month, firstDay);
this.endDate = LocalDate.of(year, month, lastDay);
}
public LocalDate beginDate() {
return beginDate;
}
public LocalDate endDate() {
return endDate;
}
public boolean contains(LocalDate date) {
if (date.isEqual(beginDate) || date.isEqual(endDate)) {
return true;
}
return date.isAfter(beginDate) &amp;&amp; date.isBefore(endDate);
}
}
</code></pre>
<p>结算周期必须提供成对儿的起止日期,缺少任何一个日期,就无法正确地进行薪资计算。将 beginDate 与 endDate 封装到 Period 类中,再利用构造函数限制实例的创建,就能避免起止日期任意一个值的缺失。引入 Period 类还能封装领域行为,让对象之间的协作变得更加合理。由于这样的类没有声明 @Entity因此是一种 POJO 类。因为它并不需要持久化为示区别可称呼这样的类为瞬态类Transient Class。对应的倘若在一个支持持久化的领域类中需要定义一个无需持久化的字段可称呼这样的字段为瞬态字段Transient Field。JPA 定义了 @Transient 标注用以显式声明这样的字段,例如:</p>
<pre><code class="language-java">@Entity
@Table(name=&quot;employees&quot;)
public class Employee extends AbstractEntity&lt;EmployeeId&gt; implements AggregateRoot&lt;Employee&gt; {
@EmbeddedId
private EmployeeId employeeId;
private String firstName;
private String middleName;
private String lastName;
@Transient
private String fullName;
}
</code></pre>
<p>Employee 类对应的数据表定义了 firstName、middleName 与 lastName 列,为了调用方便,该类又定义了 fullName 字段,该值并不需要持久化到数据库中,因此需声明为瞬态字段。</p>
<p>理想的领域模型类应该如瞬态类这样的 POJO 类这也符合整洁架构的思想即处于内部核心的领域类不依赖任何外部框架。由于需要为领域模型与数据模型建立关系映射就必须通过某种元数据机制对其进行表达ORM 框架才能实现对象与关系的映射。在 Java 语言中,可供选择的元数据机制就是 XML 或标注Annotation。XML 因其冗长繁杂与不直观的表现力等缺陷,在相对大型的产品或项目开发中,已被渐渐摒弃,因而更建议使用标注。由于 JPA 是 OracleSun为持久化接口制定的规范我们也可自我安慰地说这些运用到领域模型类上的标注仍然属于 Java 语言的一部分,不算是违背整洁架构的设计原则。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/领域驱动设计实践(完)/087 对象关系映射(上).md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/领域驱动设计实践(完)/089 领域模型与数据模型.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997ecaadae3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>