DDD领域驱动设计之领域服务
什么是领域服务,DDD书中是说,有些类或者方法,放实体A也不好,放实体B也不好,因为很可能会涉及多个实体或者聚合的交互(也可能是多个相同类型的实体),此时就应该吧这些代码放到领域服务中,领域服务其实就跟传统三层的BLL很相似,只有方法没有属性,也就没有状态,而且最好是用动词命名,service为后缀,但是真正到了实践的时候,很多时候是很难区分是领域实体本身实现还是用领域服务区实现的,除了那些需要操作(一般是参数了)多个实体的方法外,有些单个实体的操作是很难严格区分的,实际上放实体和领域服务都可以,只是会有技术上的实现问题,比如实体里面怎么注入仓促的问题,如果放领域服务中了,就很容易注入了;还有一点就是实体或者聚合最好是不要去调用领域服务的,真是没有必要,如果要也会存在注入问题,所以比较合适的实践是,一些方法,如果有涉及系统性判断,如用户名唯一这种查找表的,那么就放到领域服务中,让运用层来调用,领域服务在去调用仓储。
1、仓储接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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、领域服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | 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; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | 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); } } } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架