表示一个无状态的操作,用于实现某个特定的领域任务,某些操作不适合放在聚合和值对象上就用领域服务,它是无状态的,表示领域中具有显著流程的行为。
1.理解领域服务
领域服务代表了领域的概念,是问题域中的行为,与领域专家对话中产生,肯定是通用语言的一部分。比如计价
、认证
。
1.1 何时使用领域服务
某些行为不适合放在任何一个实体中,尝试强制归属实体时就该考虑用领域服务了。
- 封装业务策略和过程
用领域服务执行一些设计实体或者值对象的行为,比如一个Competitor
表示参与玩家,OnlineDeathMatch
表示比赛,规则是玩家参与比赛结束后分别会获取不同的分数,如果不用领域服务一般会将比赛分数结算规则放在OnlineDeathMatch
实体的方法中,如果奖励本身比较复杂,可以定义一个奖励结算类,不同的规则可以叠加。得分和奖励本身是重要的概念,下面案例表示封装了积分结算的单依职责的领域服务。
package main
import "fmt"
type Competitor struct {
Id int64
Score int
Name string
Rank int
}
// 表示计算分数的规则
type IScoreApply interface {
Apply(IGame)
}
// 普通规则
type NormalScoreRule struct {
}
func (r *NormalScoreRule) Apply(game IGame) {
var amount = 200
if game.Winner() != nil {
game.Winner().Score += amount
}
if game.Loser() != nil {
game.Loser().Score -= amount
}
}
// 如果对手是前10名的,胜利者加100分
type ExtraRewardRule struct {
}
func (r *ExtraRewardRule) Apply(game IGame) {
var amount = 100
if game.Winner() != nil && game.Loser().Rank < 100 {
game.Winner().Score += amount
}
}
type IGame interface {
Winner() *Competitor
Loser() *Competitor
}
type OnlineDeathMatch struct {
player1 *Competitor
player2 *Competitor
winner *Competitor
loser *Competitor
scores []IScoreApply
}
func (m *OnlineDeathMatch) Winner() *Competitor {
return m.loser
}
func (m *OnlineDeathMatch) Loser() *Competitor {
return m.winner
}
func (m *OnlineDeathMatch) Battle() {
// 其他逻辑
m.winner = m.player1
m.loser = m.player2
m.updateScoreAndReward()
}
func (m *OnlineDeathMatch) updateScoreAndReward() {
for _, scoreRule := range m.scores {
scoreRule.Apply(m)
}
}
func main() {
var player1 = &Competitor{
Id: 1,
Score: 1000,
Name: "one",
Rank: 50,
}
var player2 = &Competitor{
Id: 2,
Score: 1000,
Name: "two",
Rank: 60,
}
var match = OnlineDeathMatch{
player1: player1,
player2: player2,
scores: []IScoreApply{&ExtraRewardRule{}, &NormalScoreRule{}},
}
match.Battle()
fmt.Println(player1.Score)
fmt.Println(player2.Score)
}
2.表示约定
领域中表示约定的一些概念比较重要,但是需要依赖基础架构,在领域模型中是不能使用基础架构的,这部分也要封装成领域服务
- 税务查询
- 实时通知
- 汇率查询
2. 领域服务解析
代表行为、不具备身份、无状态是领域服务的特点。
2.1避免使用贫血领域模型
如果认同了不是所有的行为都应该放在实体上,尝试将所有的行为都拆分到领域服务来实现,会导致领域模型只有数据没有行为、只有一些get和set方法,形成贫血模型,降低模型的内聚性,所以要在领域服务和实体绑定方法之间取一个平衡。
2.2 与应用程序服务对比
领域服务:代表了问题域的概念,接口在领域模型中,有时会依赖用于通知领域逻辑的基础架构
应用程序服务:不代表领域概念,不包含业务规则,基础架构问题是为了让领域模型正确运行。
3.利用领域服务
3.1 在服务层
在应用服务层从存储库提取数据构造实体后传递给领域服务,购物车的商品计价也是类似的例子,接收各种商品、优惠信息然后进行价格计算并输出结果。
![](https://img.haomeiwen.com/i2747346/f0cabe789f0f05f4.png)
3.2 领域中使用
有时实体需要依赖领域服务实现其行为,比如执行完以后产生一个通知。
// 表示餐品预定实体
type RestaurantBooking struct {
customerId int64
confirmed bool
restaurantId int64
}
type RestaurantNotify struct {
}
func (n*RestaurantNotify)NotifyConfirm(restaurantId int64,customerId int64) {
}
func (b*RestaurantBooking)ConfirmBook() {
notify := RestaurantNotify{}
notify.NotifyConfirm(b.restaurantId,b.customerId)
}
如何让RestaurantNotify
在RestaurantBooking内部可用有很多实现方式。
-
构造方法实现,就是在实例化RestaurantBooking的时候手动传入,但是有的时候可能外接没有必要感知。
-
可用工厂方法,工厂方法接收其他参数,在工厂方法内部将
RestaurantNotify
对象和其他参数一起传递给RestaurantBooking
的构造方法。
3.动态依赖注入,PHP里面见过,go语言没有见过实现,可能go的反射性能问题比较大吧,这个的前提还是要将RestaurantNotify放在构造方法中。
4.双向派发
type RestaurantNotify struct {
}
func (n *RestaurantNotify) NotifyConfirm(r *RestaurantBooking) bool {
return true
}
func (b *RestaurantBooking) ConfirmBook(notify *RestaurantNotify) {
b.confirmed = notify.NotifyConfirm(b)
}
- 使用领域事件
如果需要完全避免将领域服务引入实体,可以由实体发出领域事件,在消息处理的handle方法中根据类型调用领域服务逻辑去处理。
网友评论