一、DDD領域驅動設計實踐篇之如何提取模型html
二、DDD領域驅動設計之聚合、實體、值對象app
三、DDD領域驅動設計之領域基礎設施層this
什麼是領域服務,DDD書中是說,有些類或者方法,放實體A也很差,放實體B也很差,由於極可能會涉及多個實體或者聚合的交互(也多是多個相同類型的實體),此時就應該吧這些代碼放到領域服務中,領域服務其實就跟傳統三層的BLL很類似,只有方法沒有屬性,也就沒有狀態,並且最好是用動詞命名,service爲後綴,可是真正到了實踐的時候,不少時候是很難區分是領域實體自己實現仍是用領域服務區實現的,除了那些須要操做(通常是參數了)多個實體的方法外,有些單個實體的操做是很難嚴格區分的,實際上放實體和領域服務均可以,只是會有技術上的實現問題,好比實體裏面怎麼注入倉促的問題,若是放領域服務中了,就很容易注入了;還有一點就是實體或者聚合最好是不要去調用領域服務的,真是沒有必要,若是要也會存在注入問題,因此比較合適的實踐是,一些方法,若是有涉及系統性判斷,如用戶名惟一這種查找表的,那麼就放到領域服務中,讓運用層來調用,領域服務在去調用倉儲。spa
一、倉儲接口設計
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); } }
二、領域服務htm
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); } } }