上一篇筆記只模糊提了點個人想法,少了程式碼,總覺得有些不著邊際。這次補上一點範例程式碼....

我直接把先前的 LayeredAppDemo 拿來修改,去掉 *.Domain、*.DataAccess 專案(* 號代表 NorthwindApp),以求盡量簡化。於是最終只剩下三個專案,分別是 *.ConsoleClient、*.Infrastructure、和 *.Core。其實 *.Infrastructure 也只是擺好看的,目前沒有任何程式碼,純粹預留給未來。參考下圖:



*.Core 裡面包含兩個子命名空間:Models 和 Services。如有必要,這兩個命名空間也可以分成兩個獨立的 Class Library 專案。

Models 裡面放的是 Entity Framework 產生的模型和類別檔案,Services 裡面則是展現層使用的一些服務類別,例如 CustomerService。各組件的相依關係如下圖所示:


這個簡化的架構並未使用 Repository,設計相對單純:
  • 資料存取的部分就直接交給 Entity Framework 負責。
  • 商業邏輯的部分則寫在 *.Core.Services 類別中。(有一點把 Repository 換成 Service 的味道)

這樣的切分架構,對於小型的、偏重 CRUD 操作的資料庫應用程式來說大致堪用,開發速度也快。如果將 Services 那層拿掉,直接在展現層或者 ASP.NET MVC 的 Controller 類別中直接使用 EF 的 DbContext 來操作資料,那又更快、更省事。可是當程式規模不斷成長,許多類別變得太過複雜、包含太多責任時,該進一步細切就還是得再切出來。一個切分準則是自己寫單元測試的時候感覺一下好不好寫。

程式碼的部分,主要就是先產生 entity model,然後撰寫上一層的服務,例如 CustomerService:

namespace NorthwindApp.Core.Services
{
    public class CustomerService : ServiceBase
    {
        private NorthwindEntities _context = new NorthwindEntities;

        public Customer GetCustomerByID(string id)
        {
            return _context.Customers.Find(id);
        }

        public IList<Customer> GetAllCustomers(int maxCount)
        {
            return _context.Customers.Take(maxCount).ToList();
        }

        public void AddCustomer(Customer customer)
        {
            _context.Customers.Add(customer);
            _context.SaveChanges();
        }
    }
}

最後是展現層的部分,只是很簡單的去呼叫 CustomerService 的方法:

class Program
{
    static void Main(string[] args)
    {
        CustomerService customerService = new CustomerService();
        IList<customer> customers = customerService.GetAllCustomers(10);

        foreach (var customer in customers) 
        {
            Console.WriteLine(customer.CompanyName);
        }
    }
}

這種簡化的架構,中間就只有一層服務層,再加上 Entity Framework 的協助,開發速度應該會蠻快的,而且容易入手。此外,中間的服務層可做為單元測試的重點區域,以及切分應用程式邏輯、商業邏輯、資料存取的一個緩衝地帶。

附帶說明:
  • 這裡沒有使用 ViewModel,所以展現層也是直接使用 *.Core.Models 中的 entity classes。比較複雜的狀況可能會需要加上 ViewModel,專供展現層使用。此時就會需要處理 entity 和 ViewModel 的屬性對應,這個部分可用 AutoMapper 或其他類似的工具來輔助。
  • 這裡也沒有使用 Query Objects 或 Query Specification 模式來設計資料查詢的條件,所以你會看到一堆 Get*** 方法,例如:GetFooByID、GetAllFoos、GetAllFoosWithMaxCount、GetFoosWithSomeConditions....。

這不是肯德基 Domain-Driven!

若將 NorthwindApp.Core 組件視為領域層,Entity Framework 視為基礎建設( infrastructure),那麼這種簡化的分層設計就很難稱得上嚴謹的洋蔥架構或領域驅動架構了。這是因為,以領域為中心的設計方式會儘量避免外界那些容易變動的東西「汙染」領域層;依賴的方向通常是基礎建設依賴領域層,而不是反過來。

若很在意這個問題,領域層 + Repository + 資料存取層(DAL)的設計方式可能會更適合。

從另一個角度來看--也許有點詭辯--如果我們把 Entity Framework 看成跟其他 CLR 類別一樣,亦即相對穩定,不太會變動,也不會想要把 EF 替換成其他 ORM,那麼這種設計倒也還過得去。