什么是领域服务,DDD书中是说,有些类或者方法,放实体A也不好,放实体B也不好,因为很可能会涉及多个实体或者聚合的交互(也可能是多个相同类型的实体),此时就应该吧这些代码放到领域服务中,领域服务其实就跟传统三层的BLL很相似,只有方法没有属性,也就没有状态,而且最好是用动词命名,service为后缀,但是真正到了实践的时候,很多时候是很难区分是领域实体本身实现还是用领域服务区实现的,除了那些需要操作(一般是参数了)多个实体的方法外,有些单个实体的操作是很难严格区分的,实际上放实体和领域服务都可以,只是会有技术上的实现问题,比如实体里面怎么注入仓促的问题,如果放领域服务中了,就很容易注入了;还有一点就是实体或者聚合最好是不要去调用领域服务的,真是没有必要,如果要也会存在注入问题,所以比较合适的实践是,一些方法,如果有涉及系统性判断,如用户名唯一这种查找表的,那么就放到领域服务中,让运用层来调用,领域服务在去调用仓储。
1、仓储接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DDD.Infrastructure;
using DDD.Infrastructure.Domain; namespace DDD.Domain.Arrange
{
public interface IPlanArrangeRepository : IRepository<PlanArrange>
{
/// <summary>
/// 项目名称是否存在
/// </summary>
/// <param name="xmmc"></param>
/// <returns></returns>
bool ExistsXMMC(string xmmc);
/// <summary>
/// 是否已下发
/// </summary>
/// <param name="appc"></param>
/// <param name="nd"></param>
/// <param name="XZQDM"></param>
/// <returns></returns>
bool IsSent(int appc, int nd, string XZQDM); /// <summary>
/// 统计计划安排表中,已经存储的指标数据
/// </summary>
/// <param name="year"></param>
/// <param name="xzqdm"></param>
/// <returns></returns>
IndicatorArea TotalSendToIndicator(int year, string xzqdm);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DDD.Infrastructure.Domain; namespace DDD.Domain.Indicator
{
public interface IPlanIndicatorRepository : IRepository<PlanIndicator>
{
/// <summary>
/// 获取预留指标
/// </summary>
/// <param name="year"></param>
/// <param name="xzqdm"></param>
/// <returns></returns>
IndicatorArea TotalReserveIndicator(int year, string xzqdm);
/// <summary>
/// 获取指定行政区下发的指标(计划指标)
/// </summary>
/// <param name="year"></param>
/// <param name="xzqdm"></param>
/// <returns></returns>
IndicatorArea TotalReceiveIndicator(int year, string xzqdm);
/// <summary>
/// 获取下发到指定行政区的指标
/// </summary>
/// <param name="year"></param>
/// <param name="xzqdm"></param>
/// <returns></returns>
IndicatorArea TotalSendToIndicator(int year, string xzqdm); /// <summary>
/// 是否已下发
/// </summary>
/// <param name="appc"></param>
/// <param name="nd"></param>
/// <param name="XZQDM"></param>
/// <returns></returns>
bool IsSent(int appc, int nd, string XZQDM);
/// <summary>
/// 是否存在已下发项目
/// </summary>
/// <param name="appc"></param>
/// <param name="nd"></param>
/// <param name="XZQDM"></param>
/// <param name="XFXZQDM"></param>
/// <returns></returns>
bool Exists(int appc, int nd, string XZQDM, string XFXZQDM);
}
}
2、领域服务
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DDD.Domain.Indicator;
using DDD.Infrastructure; namespace DDD.Domain.Arrange
{
/// <summary>
/// 计划安排服务
/// </summary>
public class ArrangeService
{
private readonly IPlanArrangeRepository repository;
/// <summary>
/// service可以用多个不同的Repository接口吗
/// </summary>
private readonly IPlanIndicatorRepository indicatorRepository; public ArrangeService(IPlanArrangeRepository repository, IPlanIndicatorRepository indicatorRepository)
{
this.repository = repository;
this.indicatorRepository = indicatorRepository;
} /// <summary>
/// 登记指标项目,如果是*别,那么projects可以不传
/// </summary>
/// <param name="planArrange"></param>
/// <param name="planArranges"></param>
public void Register(PlanArrange planArrange, IList<PlanArrange> planArranges)
{
CheckAndThrow(planArrange, false);
planArrange.Register();
if (planArranges != null)
{
foreach (var item in planArranges)
{
item.APPC = planArrange.APPC;
item.ND = planArrange.ND;
item.XZQDM = planArrange.XZQDM;
item.Register();
}
}
} /// <summary>
/// 这个方法是修改的时候调用判断的
/// </summary>
/// <param name="planArrange"></param>
public void CheckUpdate(PlanArrange planArrange)
{
CheckAndThrow(planArrange, true);
} private void CheckAndThrow(PlanArrange planArrange, bool isUpdate)
{
if (repository.IsSent(planArrange.APPC, planArrange.ND, planArrange.XZQDM))
{
throw new DomainException("批次已下发,不允许登记或修改");
}
if (isUpdate)
{
var original = repository.Find(planArrange.Id);
if (original.XMMC != planArrange.XMMC && repository.ExistsXMMC(planArrange.XMMC))
{
throw new DomainException("项目名称已存在");
}
}
else if(repository.ExistsXMMC(planArrange.XMMC))
{
throw new DomainException("项目名称已存在");
}
CheckOverPlus(planArrange, isUpdate);
} /// <summary>
/// 判断剩余指标是否足够
/// <p>总指标等于指标分解中预留部分,如果是县级,那么等于市级下发给县级的</p>
/// <p>剩余指标等于总指标-下发的指标(包含项目已下发和未下发的项目)</p>
/// </summary>
/// <param name="planArrange"></param>
private void CheckOverPlus(PlanArrange planArrange, bool isUpdate)
{
//总指标数,这里是不是应该用领域事件呢
IndicatorArea totalIndicator = null;
if (planArrange.ZBJB == IndicatorGrade.Province || planArrange.ZBJB == IndicatorGrade.City)
{
totalIndicator = indicatorRepository.TotalReserveIndicator(planArrange.ND, planArrange.XZQDM);
}
else if (planArrange.ZBJB == IndicatorGrade.County)
{
totalIndicator = indicatorRepository.TotalReceiveIndicator(planArrange.ND, planArrange.XZQDM);
}
if (totalIndicator != null)
{
//计划单位是亩
var xfIndicator = repository.TotalSendToIndicator(planArrange.ND, planArrange.XZQDM);
xfIndicator += planArrange.JHSY;
if (isUpdate)
{
var original = repository.Find(planArrange.Id);
xfIndicator -= original.JHSY;
}
if (GreaterThan(xfIndicator.GD, totalIndicator.GD))
{
throw new DomainException("耕地剩余指标不足");
}
if (GreaterThan(xfIndicator.NYD, totalIndicator.NYD))
{
throw new DomainException("农用地剩余指标不足");
}
if (GreaterThan(xfIndicator.WLYD, totalIndicator.WLYD))
{
throw new DomainException("未利用地剩余指标不足");
}
}
} /// <summary>
///
/// </summary>
/// <param name="mu"></param>
/// <param name="hectare"></param>
/// <returns></returns>
private bool GreaterThan(decimal mu, decimal hectare)
{
decimal left = 0;
decimal right = 0;
if (mu > 0 && mu % 15 == 0)
{
left = mu / 15;
right = hectare;
}
else
{
left = mu * 666.6666667M;
right = hectare * 10000;
}
return left > right;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DDD.Infrastructure; namespace DDD.Domain.Indicator
{
/// <summary>
/// 计划指标登记服务
/// </summary>
public class IndicatorService
{
private readonly IPlanIndicatorRepository repository; public IndicatorService(IPlanIndicatorRepository repository)
{
this.repository = repository;
} /// <summary>
/// 登记指标项目,如果是*别,那么projects为空
/// </summary>
/// <param name="planIndicator"></param>
/// <param name="planIndicators"></param>
public void Register(PlanIndicator planIndicator, IList<PlanIndicator> planIndicators)
{
if (planIndicator.ZBJB != IndicatorGrade.Country)
{
var totalArea = planIndicator.IndicatorArea +
IndicatorArea.Sum(planIndicators.Select(t => t.IndicatorArea));
CheckAndThrow(planIndicator, totalArea, false);
//保证聚合完整性
foreach (var item in planIndicators)
{
item.APPC = planIndicator.APPC;
item.ND = planIndicator.ND;
item.XZQDM = planIndicator.XZQDM;
item.Register();
}
}
planIndicator.Register();
} /// <summary>
/// 这个方法是修改的时候调用判断的
/// </summary>
/// <param name="planIndicator"></param>
public void CheckUpdate(PlanIndicator planIndicator)
{
CheckAndThrow(planIndicator, planIndicator.IndicatorArea, true);
} /// <summary>
/// 这个方法是修改的时候调用判断的
/// </summary>
/// <param name="planIndicator"></param>
private void CheckAndThrow(PlanIndicator planIndicator,IndicatorArea area, bool isUpdate)
{
var original = isUpdate ? repository.Find(planIndicator.Id) : null;
if (repository.IsSent(planIndicator.APPC, planIndicator.ND, planIndicator.XZQDM))
{
throw new DomainException("批次已下发,不允许登记或修改");
}
//下发的时候判断,一个行政区只能一个文号
if (planIndicator.XFXZQDM != planIndicator.XZQDM)
{
if (isUpdate)
{
if(original.XFXZQDM != planIndicator.XFXZQDM && Exists(planIndicator))
{
throw new DomainException("同一批次中,不允许对同一个行政区下发多次");
}
}
else if(Exists(planIndicator))
{
throw new DomainException("同一批次中,不允许对同一个行政区下发多次");
}
}
var overIndicator = TotalOverPlusIndicator(planIndicator.ND, planIndicator.XZQDM);
if (isUpdate)
{
overIndicator += original.IndicatorArea;
}
if (area.NYD > overIndicator.NYD)
{
throw new DomainException("农用地剩余指标不足");
}
if (area.GD > overIndicator.GD)
{
throw new DomainException("耕地剩余指标不足");
}
if (area.WLYD > overIndicator.WLYD)
{
throw new DomainException("未利用地剩余指标不足");
}
} /// <summary>
/// 获取剩余指标
/// </summary>
/// <param name="year"></param>
/// <param name="xzqdm"></param>
/// <returns></returns>
private IndicatorArea TotalOverPlusIndicator(int year, string xzqdm)
{
return repository.TotalReceiveIndicator(year, xzqdm) - repository.TotalReserveIndicator(year, xzqdm) - repository.TotalSendToIndicator(year, xzqdm);
} private bool Exists(PlanIndicator planIndicator)
{
return repository.Exists(planIndicator.APPC, planIndicator.ND, planIndicator.XZQDM, planIndicator.XFXZQDM);
}
}
}