DDD領域驅動設計之領域服務


1、DDD領域驅動設計實踐篇之如何提取模型

2、DDD領域驅動設計之聚合、實體、值對象

3、DDD領域驅動設計之領域基礎設施層

什么是領域服務,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);
        }
    }
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM