從壹開始微服務(wù) [ DDD ] 之二 ║ DDD入門 & 項目結(jié)構(gòu)粗搭建

前言

哈嘍大家好在岂,今天是周二,我們的DDD系列文章今天正式開始講解僧鲁,我這兩天一直在學(xué)習(xí)忽孽,也一直在思考如何才能把這一個系列給合理的傳遞給大家皆的,并且達到學(xué)習(xí)的目的,還沒有特別好的路線竞慢,只是一個大概的模糊的安排本冲,畢竟我沒有做過講師澎灸,但是我感覺還是需要對自己負責(zé),至少要對得起這個熬夜寫的博客吧 ??榴徐,我簡單設(shè)計了下整體流程攒巍,可能以后還會變動搀擂,不過大致方向是不會變的:

我打算通過一個最簡單一個例子來講,而且這個例子也是《實現(xiàn)領(lǐng)域驅(qū)動設(shè)計》這一本書的第一個小例子镀岛,至于為什么要引用這個小栗子捻悯,我想的是,希望把整本書的大致精髓柔和到我這個 Project 中囱淋,最好給大家一個感覺需纳,以后大家再看的的時候俱箱,不會感覺在天上飄飄然不知其所往雇初,相反拢肆,是會感覺似曾相識,會感覺看過靖诗,不會那么生硬無趣郭怪,這個就是我希望達到的兩個目的的其中之一:在了解并認(rèn)識DDD領(lǐng)域驅(qū)動設(shè)計的情況下,也簡單的了解了這邊書的相關(guān)內(nèi)容刊橘。

今天要說的是我們平時百分之百會遇到的鄙才,就是新建一個實體類,并對其進行CURD操作促绵,看到這里攒庵,你也許會胸有成竹的說,已經(jīng)玩兒的很溜了败晴,當(dāng)然這是肯定的叙甸,今天不是說哪些新知識,而且說一下其實DDD驅(qū)動設(shè)計位衩,已經(jīng)在我們平時的開發(fā)中體現(xiàn)出來,當(dāng)然又沒有完全貫徹下來(至少我是這么一個)熔萧,也說一下在我們的開發(fā)當(dāng)中為什么需要使用DDD領(lǐng)域驅(qū)動設(shè)計糖驴。

這里說明下:今天搭建的是一個小雛形,我會以后慢慢細化佛致,像上一個系列那樣贮缕,逐漸增加功能,差不多最后的框架是這樣的:

image

零俺榆、今天要實現(xiàn)橙色的部分

image

一感昼、為什么要使用DDD

這個已經(jīng)是老生常談了,相信大家也稍微或多或少的了解一些罐脊,在領(lǐng)域驅(qū)動設(shè)計中定嗓,我們的中心不在代碼是開發(fā)技術(shù)上蜕琴,而且在業(yè)務(wù)上,在這個設(shè)計中宵溅,會涉及到開發(fā)人員和領(lǐng)域?qū)<遥赡苁琼椖抗芾碚吡杓颍蛘卟块T經(jīng)理,甚至是公司總經(jīng)理恃逻,這些都可以算上領(lǐng)域?qū)<遥┏Вm然在使用DDD的時候,需要我們在前期投入較多的時間和精力去考慮如何建模寇损,已經(jīng)開發(fā)過程中會遇到的各種問題凸郑,到那時這樣的投入是完全值得的,相信這個系列的結(jié)束矛市,大家有一個中肯的評價芙沥。

1、會增加我們項目的業(yè)務(wù)價值

在開發(fā)中尘盼,什么是最寶貴的憨愉,當(dāng)然是開發(fā)人員的時間效率最重要,然后還有就是溝通上卿捎,如果我們自己自定義的開發(fā)出一套代碼配紫,沒有一系列的業(yè)務(wù)模塊,這樣的話午阵,就是一個純技術(shù)的項目躺孝,雖然本來領(lǐng)域?qū)<乙部床欢侨绻覀儍H僅專注于技術(shù)方面底桂,而沒有把業(yè)務(wù)人員的思想所映射到開發(fā)者中植袍,那整個項目也只有可能僅僅是你成為這個領(lǐng)域?qū)<遥驗橹挥虚_發(fā)者當(dāng)事人才能看懂籽懦。

這樣隨著時間的發(fā)展于个,隨著開發(fā)者當(dāng)事人的離職或者轉(zhuǎn)向其他項目,本應(yīng)該駐留在軟件中的領(lǐng)域知識也就丟失了暮顺。在以后的任何修改代碼中厅篓,新的開發(fā)人員雖然可以重新和領(lǐng)域?qū)<疫M行思想的溝通,但是之前開發(fā)的代碼已經(jīng)無法撼動捶码,這也就是為什么現(xiàn)在我們找工作的時候羽氮,不喜歡修改別人的代碼,而且也會經(jīng)常聽到這個情況:盡量不要修改原來的代碼惫恼,自己新建一個档押,嗯,這個更是要命了的。

試想一下令宿,如果我們把注意力重點放到了業(yè)務(wù)上叼耙,這樣就很清晰的進行迭代,而且也可以進一步的與領(lǐng)域?qū)<疫M行對接掀淘。那我們的項目中都需要用到 DDD 領(lǐng)域驅(qū)動設(shè)計么旬蟋?答案當(dāng)時是否定的,如果滿足以下幾點革娄,我建議還是需要使用 DDD 吧:

1倾贰、如果你的系統(tǒng)中有30~40個用戶故事或者用例流的時候,軟件的復(fù)雜性就出來了拦惋。

這里說明下用戶故事和用例流:就比如一個商城匆浙,我們有普通用戶,會員厕妖,公司管理員首尼,企業(yè)合作伙伴,站長等等多個角色言秸,當(dāng)然還有其他的角色软能,每一個角色又會有一系列的操作,比如用戶會瀏覽商品举畸,下單查排,修改個人信息等等一系列的操作,這個就是用例流抄沮,這些角色所進行的全部操作跋核,就是用戶故事。

2叛买、如果你的系統(tǒng)現(xiàn)在不是很復(fù)雜砂代,但是以后會復(fù)雜。就比如我們的商城后臺:本來是僅僅的對數(shù)據(jù)庫表的CURD率挣,但是在真正的用戶(管理員)使用的時候刻伊,會發(fā)現(xiàn)在商品商家的時候,并不是很方便椒功,需要用到價格規(guī)格表捶箱,或者發(fā)現(xiàn)商家信息已經(jīng)實現(xiàn)商品的多對多分配(現(xiàn)在可能是一對多),那想想后期的修改是很龐大的蛾茉。

3、如果你的系統(tǒng)所在的領(lǐng)域不是很清晰撩鹿,就連領(lǐng)域?qū)<乙膊皇呛芮逦妫枰谖磥韼啄甑穆懻摚兓星斑M的時候。

2键思、貧血癥和失憶癥

相信大家都聽過這兩個名詞础爬,雖然聽著不是很舒服,但是卻天天在我們的手中被設(shè)計出來吼鳞,如果你說不會看蚜,那好,請看下邊兩個情況是否在你的系統(tǒng)中出現(xiàn)(這里說明下赔桌,這些栗子都是我現(xiàn)在手中的項目):

1供炎、你的領(lǐng)域?qū)ο螅ň褪侵笇嶓w類對象)是不是主要包含些共有的get 和 set,并且?guī)缀鯖]有業(yè)務(wù)邏輯疾党?比如這樣:

  
    public class BlogArticle
    {
        public int bID { get; set; }
        public string bsubmitter { get; set; }
        public string btitle { get; set; }
        public string bcategory { get; set; }
        public string bcontent { get; set; }
        public int btraffic { get; set; }
        public int bcommentNum { get; set; }
        public DateTime bUpdateTime { get; set; }
        public System.DateTime bCreateTime { get; set; }
        public string bRemark { get; set; }
    }

2音诫、你的軟件組件(就是指我們的方法定義)中是否經(jīng)常會用到領(lǐng)域?qū)ο髞韺崿F(xiàn)一些業(yè)務(wù)邏輯,而且還是通過 get 和 set 的方式雪位,直接將其拋給服務(wù)層或者應(yīng)用層竭钝,甚至是直接拋到頁面上。

        //添加一個車票
        public ActionResult AddTicket(FormCollection form)
        { var TitleHead = form["TitleHead"].ToString(); var txtSubmitter = form["txtSubmitter"].ToString(); var TicketChannelOperateJson = form["TicketChannelOperateJson"].ToString(); try { if (!string.IsNullOrEmpty(TitleHead) && !string.IsNullOrEmpty(TicketChannelOperateJson) && !string.IsNullOrEmpty(txtSubmitter))
                {

                    Tickets tickets = new Tickets
                    {
                        Submiter = txtSubmitter,
                        SubmitDate = DateTime.Now,
                        SubmitData = TicketChannelOperateJson,
                        ActionType = Common.Enums.ActionType.Add,
                        ActionStatus = Common.Enums.ActionStatus.New,
                        Approver = "",
                        LastUpdateDate = DateTime.Now,
                        IsDelete = false,
                        IsCleanUp = false,
                        IsCheckIn = false,
                        TicketType = TicketType.Channel,
                    };

                    TicketsBLL ticketsBLL = new TicketsBLL(); bool flag = ticketsBLL.Add(tickets) > 0;
                }
            } catch (Exception e)
            {
                Logger.Log(e.InnerException + e.Message);
            }

        }

如果兩個情況你都說的是 NO 雹洗,那么恭喜你香罐,你的代碼設(shè)計很健康,沒有檢查出來有貧血問題时肿,如果你的回答中有一個 YES庇茫,那是不存在的,如果是兩個YES嗜侮,我們就請繼續(xù)往下看吧港令。

二、貧血對象對我們做了什么

如果你上邊的都已經(jīng)看明白了锈颗,那我們就開始建我們的項目了顷霹,當(dāng)然這里要說明下,以后會對項目進行修繕击吱,前幾章的代碼可能很 low 淋淀,甚至是不好的,只是為了能說明問題覆醇。

1朵纷、新建一個 .net core web 項目

在指定的文件夾下,新建一個 Christ3D 解決方案永脓,然后再新建一個 web 項目袍辞,具體過程相信大家都會了,如果不會常摧,請會看第一個系列《系列教程一目錄:.netcore+vue 前后端分離

image

2搅吁、在Models 文件夾中威创,新增我們的領(lǐng)域?qū)ο?Customer.cs 和持久化虛擬類 CustomerDao.cs

   /// <summary>
    /// Customer 領(lǐng)域?qū)ο?    /// </summary>
    public class Customer
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public DateTime BirthDate { get; set; }
    }


    /// <summary>
    /// 領(lǐng)域?qū)ο蟪志没瘜?    /// </summary>
    public class CustomerDao
    {
        public static Customer GetCustomer(string id)
        {
            return new Customer() { Id = "1", Name = "Christ", Email = "Christ@123.com" };
        }


        public static string SaveCustomer(Customer customer)
        {
            return "保存成功";
        }
    }

3、在默認(rèn)的 HomeController.cs 控制器中谎懦,添加保存顧客方法 saveCustomer()

/// <summary>
/// 保存顧客方法:add & update /// </summary>
/// <param name="id"></param>
/// <param name="name"></param>
/// <param name="email"></param>
/// <param name="birthDate"></param>
public void saveCustomer(string id, string name, string email, string birthDate)
{
    Customer customer = CustomerDao.GetCustomer(id); if (customer == null)
    {
        customer = new Customer();
        customer.Id = id;
    } if (name != null)
    {
        customer.Name = name;
    } if (email != null)
    {
        customer.Email = email;
    } //...還有其他屬性
 CustomerDao.SaveCustomer(customer);
}

到這里肚豺,這就是一個領(lǐng)域 —— 對顧客領(lǐng)域?qū)ο筮M行保存,這個功能還是很強大的界拦,不管一個 Customer 是新增還是更新吸申,甚至是不管名字換了,還是郵件重新申請了享甸,都可以在這里體現(xiàn)截碴,相信這樣的方法我們都寫過,至少我是這樣枪萄。

這個方法真的很強大么隐岛,這里我們看一下,我們并不知道 saveCustomer() 的具體的應(yīng)用場景(是后臺管理瓷翻,還是前臺用戶自己操作聚凹,還是更新手機號等等),經(jīng)過幾周或者幾年后齐帚,我們完全不知道當(dāng)時創(chuàng)建這個方法的本來目的和意圖妒牙,當(dāng)然你會說可以通過注釋的方法,我表示是反對的对妄,我們對程序的設(shè)計湘今,不能依賴于注釋,這是很可怕的剪菱。

還有摩瞎,這些方法嵌套在一起,我們不能對其進行單元測試孝常,都不用說細節(jié)旗们,可能數(shù)據(jù)庫的約束就能對這個方法造成崩潰:比如時間字段,比如字符串長度過長构灸,比如不能為空等上渴,而且因為有很多地方調(diào)用這個看似功能強大的基方法,我們需要一個一個的往上游去研究喜颁,看看到底是哪個地方稠氮,哪個方法,哪個業(yè)務(wù)邏輯調(diào)用了半开,可能需要幾個小時的時間隔披,甚至更久。

總結(jié)來說寂拆,上邊的 saveCustomer() 方法存在三個弊端:

1奢米、saveCustomer() 方法的業(yè)務(wù)意圖不明確芥炭,當(dāng)時我們?yōu)榱素潏D功能的強大,把注意力都放到了技術(shù)上恃慧,從而忽略了業(yè)務(wù)核心。

2渺蒿、saveCustomer() 方法的實現(xiàn)本來就增加了潛在的復(fù)雜性痢士,雖然看著強大了,可是復(fù)雜度卻直線向上茂装。

3怠蹂、Customer 顧客領(lǐng)域?qū)ο蟾静皇菍ο螅淦淞烤褪且粋€數(shù)據(jù)持有者少态,僅僅是把我們的數(shù)據(jù)從持久化的數(shù)據(jù)庫中拿出來到內(nèi)容的一個工具城侧。

那既然如此,我們需要怎么辦呢彼妻,沒錯嫌佑,就是我們平時使用到的分層,一個專注領(lǐng)域業(yè)務(wù)的分層 —— DDD領(lǐng)域驅(qū)動設(shè)計侨歉,就出現(xiàn)了屋摇。

三、一切皆從領(lǐng)域開始 —— Domain

還記得下邊這個圖么幽邓,這個是我上一篇文章中提到的《領(lǐng)域驅(qū)動設(shè)計架構(gòu)圖》炮温,我個人表示,這個圖已經(jīng)很貼切和較為詳細的表示了DDD領(lǐng)域驅(qū)動設(shè)計是整體框架圖牵舵,大家從整體的箭頭走向就能看清楚(誰指向誰柒啤,表示前者依賴后者后者實現(xiàn)前者)畸颅,其中的內(nèi)容我們都會說到担巩,今天先簡單的把這個整體框架搭起來,至少先從什么的DDD框架講起來——當(dāng)然也就是從被依賴最多的領(lǐng)域?qū)娱_始重斑。

image

從上邊的文章中兵睛,我們知道了,在軟件開發(fā)中窥浪,我們已經(jīng)把重點放到領(lǐng)域業(yè)務(wù)上祖很,在上邊的 saveCustomer() 的方法中,所有的邏輯和用例都一股腦的放在一起漾脂,完全背離了這個思想假颇,所以,那我們?nèi)绻胍ㄟ^領(lǐng)域設(shè)計的思路來創(chuàng)建骨稿,會是怎么樣的呢笨鸡,請往下看姜钳。

1、定義領(lǐng)域?qū)ο?Customer.cs(值對象/聚合/根形耗,以后會說)

在解決方案中哥桥,新建 .net core 類庫 Christ3D.Domain ,作為我們的領(lǐng)域?qū)樱ㄟ@是一個臃腫的領(lǐng)域?qū)蛹さ樱院笪覀儠杨I(lǐng)域核心給抽象出來拟糕,現(xiàn)在簡化是為了說明),然后在該層下倦踢,新建 Models 文件夾送滞,存放我們以后的全部領(lǐng)域?qū)ο螅覀兊膶I(yè)領(lǐng)域設(shè)計辱挥,都是基于領(lǐng)域?qū)ο鬄榛A(chǔ)犁嗅。

老張:這里并沒有增加業(yè)務(wù)邏輯,以后會說明

 /// <summary>
    /// 定義領(lǐng)域?qū)ο?Customer /// </summary>
    public class Customer
    { protected Customer() { } public Customer(Guid id, string name, string email, DateTime birthDate)
        {
            Id = id;
            Name = name;
            Email = email;
            BirthDate = birthDate;
        } public Guid Id { get; private set; } public string Name { get; private set; } public string Email { get; private set; } public DateTime BirthDate { get; private set; }
    }

2晤碘、定義泛型接口 IRepository.cs

這里說下為什么開發(fā)中都需要接口層:

在層級結(jié)構(gòu)中褂微,上層模塊調(diào)用下層模塊提供的服務(wù),這里就會存在一種依賴關(guān)系园爷,Rebort C. Martin提出了依賴倒置原則大致是如下:

上層模塊不應(yīng)該依賴于下層模塊蕊梧,兩者都應(yīng)該依賴于抽象;

抽象不應(yīng)該依賴于實現(xiàn)腮介,實現(xiàn)應(yīng)該依賴于抽象;

這是一個面向接口編程的思想肥矢。

在我們的領(lǐng)域?qū)酉拢陆?Interfaces 文件夾叠洗,然后添加泛型接口

在我們專注的領(lǐng)域業(yè)務(wù)中甘改,我們只需要定義該領(lǐng)域Customer 的相關(guān)用例即可(就比如如何CURD,如何發(fā)郵件等等灭抑,這些都是用戶角色Customer的用例流)十艾,而不用去關(guān)心到底是如何通過哪種技術(shù)來實現(xiàn)的,那種ORM去持久化的腾节,這就是領(lǐng)域設(shè)計的核心忘嫉,當(dāng)然現(xiàn)在有很多小伙伴還是喜歡直接把接口和實現(xiàn)放在一起,也無可厚非案腺,但是不符合DDD領(lǐng)域設(shè)計的思想庆冕。

可能這個時候你會說,領(lǐng)域?qū)优ィx接口和實現(xiàn)方法放在一起也可以嘛访递,現(xiàn)在我們是看不出來效果的,以后我們會在這里說到領(lǐng)域驅(qū)動同辣,領(lǐng)域通知拷姿,事件驅(qū)動等等知識點的時候惭载,你就會發(fā)現(xiàn),在Domain層來對接口進行實現(xiàn)是那么格格不入响巢,沒關(guān)系慢慢來~~~

    /// <summary>
    /// 定義泛型倉儲接口描滔,并繼承IDisposable,顯式釋放資源 /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public interface IRepository<TEntity> : IDisposable where TEntity : class { /// <summary>
        /// 添加 /// </summary>
        /// <param name="obj"></param>
        void Add(TEntity obj); /// <summary>
        /// 根據(jù)id獲取對象 /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
 TEntity GetById(Guid id); /// <summary>
        /// 獲取列表 /// </summary>
        /// <returns></returns>
        IQueryable<TEntity> GetAll(); /// <summary>
        /// 根據(jù)對象進行更新 /// </summary>
        /// <param name="obj"></param>
        void Update(TEntity obj); /// <summary>
        /// 根據(jù)id刪除 /// </summary>
        /// <param name="id"></param>
        void Remove(Guid id); /// <summary>
        /// 保存 /// </summary>
        /// <returns></returns>
        int SaveChanges();
    }

3踪古、定義 Customer 領(lǐng)域接口 ICustomerRepository.cs

我們以后的每一個子領(lǐng)域中特有的接口伴挚,還是需要定義的,并且繼承自我們的泛型倉儲接口

    /// <summary>
    /// ICustomerRepository 接口 /// 注意灾炭,這里我們用到的業(yè)務(wù)對象,是領(lǐng)域?qū)ο?/// </summary>
    public interface ICustomerRepository : IRepository<Customer> { //一些Customer獨有的接口
        Customer GetByEmail(string email);
 } 

好了颅眶,這個時候我們的最最最簡單的領(lǐng)域?qū)泳痛罱ê昧蓑诔觯镞呌形覀兊淖宇I(lǐng)域 Customer的相關(guān)接口實現(xiàn),整體結(jié)構(gòu)是這樣的:

image

四涛酗、基礎(chǔ)層對領(lǐng)域進行實現(xiàn) —— Infrastructure

上邊咱們定義了 Domian 領(lǐng)域?qū)诱≡@是一個接口層,那我們必須來實現(xiàn)它們商叹,大家可以再往上看那個DDD領(lǐng)域驅(qū)動設(shè)計架構(gòu)圖燕刻,你應(yīng)該能找到,是誰對領(lǐng)域?qū)舆M行了實現(xiàn)剖笙,答案當(dāng)然是基礎(chǔ)設(shè)施層卵洗。

我們在解決方案下,新建一個 Christ3D.Infrastruct.Data 類庫弥咪,你一定會問过蹂,為什么不直接是 Christ3D.Infrastruct ,反而在后邊還需要多一個 .Data 后綴呢聚至,這里要說的就是酷勺,在這個基礎(chǔ)設(shè)施層中,會有很多很多的內(nèi)容扳躬,比如驗證層脆诉,IoC層,事務(wù)贷币,工作單元等等击胜,以后都會說到,至少從名字上你也能明白——基礎(chǔ)設(shè)施役纹,這些基礎(chǔ)東西是不能放在領(lǐng)域?qū)拥那钡模@是肯定的,因為我們關(guān)心的是業(yè)務(wù)領(lǐng)域字管,這個不是我們的業(yè)務(wù)啰挪,也不會是下文的應(yīng)用層信不,至于為什么,你可以先想一想亡呵,數(shù)據(jù)驗證和AOP這些為何不放在應(yīng)用層抽活。

1、新建泛型倉儲 Repository

Repository繼承了IRepository接口锰什,這里我們先不寫具體的實現(xiàn)下硕,我們先定義好方法體,以后再慢慢填上汁胆,大家還記得如何快速的實現(xiàn)接口吧梭姓,Ctrl+. 可能有的小伙伴沒有這個功能,那就只能手動了嫩码。

image
   /// <summary>
    /// 泛型倉儲誉尖,實現(xiàn)泛型倉儲接口
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        public void Add(TEntity obj)
        {
            throw new NotImplementedException();
        }

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public IQueryable<TEntity> GetAll()
        {
            throw new NotImplementedException();
        }

        public TEntity GetById(Guid id)
        {
            throw new NotImplementedException();
        }

        public void Remove(Guid id)
        {
            throw new NotImplementedException();
        }

        public int SaveChanges()
        {
            throw new NotImplementedException();
        }

        public void Update(TEntity obj)
        {
            throw new NotImplementedException();
        }
    }

2、實現(xiàn)子領(lǐng)域Customer 倉儲

  /// <summary>
    /// Customer倉儲铸题,操作對象還是領(lǐng)域?qū)ο?/// </summary>
    public class CustomerRepository : Repository<Customer>, ICustomerRepository
    { //對特例接口進行實現(xiàn)
        public Customer GetByEmail(string email)
        { throw new System.NotImplementedException();
        }
    }

最終結(jié)構(gòu)

image

相信大家看到這里铡恕,基本還是很輕松的,我們在領(lǐng)域?qū)佣洌褬I(yè)務(wù)定義清楚探熔,把領(lǐng)域?qū)ο笤O(shè)計好,然后在基礎(chǔ)層對其進行實現(xiàn)烘挫,是一個很好的過程诀艰,這么看還是可以的。

但是一定會有小伙伴會看不慣這么寫饮六,他會說涡驮,領(lǐng)域設(shè)計定義接口我懂,那就定義喜滨,定義實現(xiàn)也可以捉捅,比如用 EFCore 或者其他 ORM 框架,那這樣直接在展示層調(diào)用不就好了虽风,為啥還需要單拿出來一個應(yīng)用 Application 層呢(等同于我們之前的 Service 層)棒口,到時候肯定又多出來一套接口和實現(xiàn)的過程,麻煩不麻煩辜膝?

我在上一個系列教程中无牵,本來也是這么嘗試使用DDD領(lǐng)域驅(qū)動設(shè)計,可以中間沒有看《實現(xiàn)領(lǐng)域驅(qū)動設(shè)計》這本書厂抖,導(dǎo)致出現(xiàn)了漏洞茎毁,也被各種小伙伴吐槽,這個系列就再證明一下吧,具體有什么好處七蜘,或者說為什么要在基礎(chǔ)設(shè)施層之上谭溉,再增加一個應(yīng)用層(也就是我們的 Service 層),這里先不說橡卤,下邊請繼續(xù)看扮念。

五、定義系統(tǒng)的業(yè)務(wù)功能 —— Application

如果Repository 應(yīng)用在應(yīng)用層碧库,會出現(xiàn)什么情況:這樣就致使應(yīng)用層和基礎(chǔ)層(我把數(shù)據(jù)持久化放在基礎(chǔ)層了)通信柜与,忽略了最重要的領(lǐng)域?qū)樱I(lǐng)域?qū)釉谄渲衅鸬降淖饔米疃嘁簿褪莻鬟f一個非常貧血的領(lǐng)域模型嵌灰,然后通過 Repository 進行“CRUD”弄匕,這樣的結(jié)果是,應(yīng)用層不變成所謂的 BLL(常說的業(yè)務(wù)邏輯層)才怪沽瞭,另外迁匠,因為業(yè)務(wù)邏輯都放在應(yīng)用層了,領(lǐng)域模型也變得更加貧血秕脓。

Application為應(yīng)用層(也就是我們常說的 Service 層),定義軟件要完成的任務(wù)儒搭,并且指揮表達領(lǐng)域概念的對象來解決問題吠架。這一層所負責(zé)的工作對業(yè)務(wù)來說意義重大,也是與其它系統(tǒng)的應(yīng)用層進行交互的必要渠道搂鲫。應(yīng)用層要盡量簡單傍药,不包含業(yè)務(wù)規(guī)則或者知識,而只為下一層中的領(lǐng)域?qū)ο髤f(xié)調(diào)任務(wù)魂仍,分配工作拐辽,使它們互相協(xié)作。它沒有反映業(yè)務(wù)情況的狀態(tài)擦酌,但是卻可以具有另外一種狀態(tài)俱诸,為用戶或程序顯示某個任務(wù)的進度。

1赊舶、視圖模型——Rich 領(lǐng)域模型(DTO以后說到)

在文章的最后睁搭,咱們再回顧下文章開頭說的貧血對象模型,相信你應(yīng)該還有印象笼平,這個就是對剛剛上邊這個問題最好的回答园骆,如果我們直接把展示層對接到了基層設(shè)施層,那我們勢必需要用到領(lǐng)域模型來操作寓调,甚至是對接到視圖里锌唾,不僅如此,我們還需要驗證操作夺英,傳值操作等等晌涕,那我們又把領(lǐng)域?qū)ο竽P瓦^多的寫到了業(yè)務(wù)邏輯里去滋捶,嗯,這個就不是DDD領(lǐng)域驅(qū)動設(shè)計了渐排,所以我們需要一個應(yīng)用層炬太,對外進行數(shù)據(jù)接口的提供,這里要強調(diào)一點驯耻,千萬不要把應(yīng)用層最后寫滿了業(yè)務(wù)邏輯亲族,業(yè)務(wù)應(yīng)該在領(lǐng)域?qū)樱。可缚。?/p>

在項目根路徑下霎迫,新建 Christ3D.Application 類庫,作為我們的應(yīng)用層帘靡,然后新建 ViewModels 文件夾知给,用來存放我們的基于UI 的視圖模型,它是如何來的描姚,這個下邊說到涩赢。

 /// <summary>
    /// 子領(lǐng)域Customer的視圖模型 /// </summary>
    public class CustomerViewModel
    {
        [Key] public Guid Id { get; set; }

        [Required(ErrorMessage = "The Name is Required")]
        [MinLength(2)]
        [MaxLength(100)]
        [DisplayName("Name")] public string Name { get; set; }

        [Required(ErrorMessage = "The E-mail is Required")]
        [EmailAddress]
        [DisplayName("E-mail")] public string Email { get; set; }

        [Required(ErrorMessage = "The BirthDate is Required")]
        [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
        [DataType(DataType.Date, ErrorMessage = "Data em formato inválido")]
        [DisplayName("Birth Date")] public DateTime BirthDate { get; set; }
    }

這里僅僅是增加了特性,更多的業(yè)務(wù)邏輯還是在 領(lǐng)域?qū)?來實現(xiàn)的轩勘。

2筒扒、定義應(yīng)用服務(wù)接口 ICustomerAppService,依賴抽象思想

在我們的應(yīng)用層下绊寻,新建 Interfaces 文件夾花墩,用來存放我們的對外服務(wù)接口,然后添加 Customer服務(wù)接口類澄步,這里要說明下冰蘑,在應(yīng)用層對外接口中,我們就不需要定義泛型基類了村缸,因為已經(jīng)沒有必要祠肥,甚至是無法抽象的,


    /// <summary>
    /// 定義 ICustomerAppService 服務(wù)接口
    /// 并繼承IDisposable梯皿,顯式釋放資源
    /// 注意這里我們使用的對象搪柑,是視圖對象模型
    /// </summary>
    public interface ICustomerAppService : IDisposable
    {
        void Register(CustomerViewModel customerViewModel);
        IEnumerable<CustomerViewModel> GetAll();
        CustomerViewModel GetById(Guid id);
        void Update(CustomerViewModel customerViewModel);
        void Remove(Guid id);
    }

3、實現(xiàn)應(yīng)用服務(wù)接口 CustomerAppService.cs 索烹,對接基層設(shè)施層

在我們的應(yīng)用層下工碾,新建 Services 文件夾,用來存放我們對服務(wù)接口的實現(xiàn)類

   /// <summary>
    /// CustomerAppService 服務(wù)接口實現(xiàn)類,繼承 服務(wù)接口
    /// 通過 DTO 實現(xiàn)視圖模型和領(lǐng)域模型的關(guān)系處理
    /// 作為調(diào)度者百姓,協(xié)調(diào)領(lǐng)域?qū)雍突A(chǔ)層渊额,
    /// 這里只是做一個面向用戶用例的服務(wù)接口,不包含業(yè)務(wù)規(guī)則或者知識
    /// </summary>
    public class CustomerAppService : ICustomerAppService
    {
        private readonly ICustomerRepository _customerRepository;

        public CustomerAppService(ICustomerRepository customerRepository)
        {
            _customerRepository = customerRepository;
        }

        public IEnumerable<CustomerViewModel> GetAll()
        {
            return null;
            //return _customerRepository.GetAll().ProjectTo<CustomerViewModel>();
        }

        public CustomerViewModel GetById(Guid id)
        {
            return null;
            //return _mapper.Map<CustomerViewModel>(_customerRepository.GetById(id));
        }

        public void Register(CustomerViewModel customerViewModel)
        {
            //var registerCommand = _mapper.Map<RegisterNewCustomerCommand>(customerViewModel);
        }

        public void Update(CustomerViewModel customerViewModel)
        {
            //var updateCommand = _mapper.Map<UpdateCustomerCommand>(customerViewModel);
        }

        public void Remove(Guid id)
        {
            //var removeCommand = new RemoveCustomerCommand(id);
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }
    }

目前這里還沒有具體使用基礎(chǔ)層的倉儲,為什么呢,因為應(yīng)用層是面向視圖對象模型旬迹,不涉及到業(yè)務(wù)火惊,而基礎(chǔ)設(shè)施層和領(lǐng)域?qū)邮腔?領(lǐng)域?qū)ο竽P停嫦驑I(yè)務(wù)的奔垦,所以我們需要用到 DTO 屹耐,這一塊以后我們會說到。

image

六椿猎、結(jié)語

好啦惶岭,今天的講解基本就到這里了,到目前為止犯眠,僅僅是實現(xiàn)了DDD領(lǐng)域驅(qū)動設(shè)計的第一個 D 領(lǐng)域模型按灶,還沒有說到驅(qū)動的概念,通過經(jīng)典的DDD四層筐咧,大家應(yīng)該也了解了各層的作用鸯旁,這里有簡單的三個問題,不知道你是否已經(jīng)真的看懂了量蕊,如果都能回答上來铺罢,恭喜!如果不是很確定残炮,那抱歉韭赘,還需要再看看,或者查資料看書吉殃,或者來群里咨詢我吧辞居。

1楷怒、什么是貧血對象模型蛋勺?

2、我們的業(yè)務(wù)接口和業(yè)務(wù)實現(xiàn)鸠删,分別在哪一層抱完?( Answer:領(lǐng)域?qū)雍突A(chǔ)設(shè)施層 )

3、為什么還需要定義一個應(yīng)用層刃泡,而不是直接在應(yīng)用層(Service層)對接基礎(chǔ)層(Repository層)巧娱?

好啦,周四我們會繼續(xù)推進烘贴,DDD 領(lǐng)域?qū)ο笤O(shè)計是如何實現(xiàn) 領(lǐng)域禁添、子域、界限上下文的

七桨踪、Github & Gitee

https://github.com/anjoy8/ChristDDD

https://gitee.com/laozhangIsPhi/ChristDDD

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末老翘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铺峭,老刑警劉巖墓怀,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件月趟,死亡現(xiàn)場離奇詭異瑟枫,居然都是意外死亡引润,警方通過查閱死者的電腦和手機待笑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門狡忙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铐炫,“玉大人毁渗,你說我怎么就攤上這事题暖∧馗ぃ” “怎么了官扣?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長羞福。 經(jīng)常有香客問我惕蹄,道長,這世上最難降的妖魔是什么治专? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任卖陵,我火速辦了婚禮,結(jié)果婚禮上张峰,老公的妹妹穿的比我還像新娘泪蔫。我一直安慰自己,他們只是感情好喘批,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布撩荣。 她就那樣靜靜地躺著,像睡著了一般饶深。 火紅的嫁衣襯著肌膚如雪餐曹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天敌厘,我揣著相機與錄音台猴,去河邊找鬼。 笑死俱两,一個胖子當(dāng)著我的面吹牛饱狂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宪彩,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼休讳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了尿孔?” 一聲冷哼從身側(cè)響起俊柔,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤磺樱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后婆咸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竹捉,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年尚骄,在試婚紗的時候發(fā)現(xiàn)自己被綠了块差。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡倔丈,死狀恐怖憨闰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情需五,我是刑警寧澤鹉动,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站宏邮,受9級特大地震影響泽示,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜜氨,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一械筛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧飒炎,春花似錦埋哟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至煞赢,卻和暖如春抛计,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耕驰。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工爷辱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留录豺,地道東北人朦肘。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像双饥,于是被迫代替她去往敵國和親媒抠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容