1. 程式人生 > >記錄一次使用UnifOfWork改造項目的過程。

記錄一次使用UnifOfWork改造項目的過程。

距離 創建 值對象 領取 模式 ram scope 移動端 技術

一、前言

  UnifOfWork模式,一般稱為“工作單元”模式,是DDD(Domain-Driven Design,領域驅動設計)的一個組成部分,用於在應用服務方法中,控制一個應用服務方法的所有數據庫操作,都在一個事務內。最近對個人的一個Web項目進行了改造,按照DDD的設計模式進行了分層,雖然在領域層沒有實現完整的領域對象(實體+值對象+領域服務 ),但是DDD也並不是鐵板一塊,架構設計指南最終也是要服務於具體項目的,領悟思想為我所用才是一個提高的過程,記錄下來,以備日後溫習。

二、DDD模式分析

技術分享

  

  經典的DDD架構,由如圖四層組成,第一層是用戶層,是直接面向用戶的UI界面層,放到開發環境中,可以指代比如瀏覽器前端、移動端APP、Windows客戶端等。第二層是指應用服務層,這層直接面向的是現實的需求,比如某公司的“新員工入職”,這一層根據DDD理論,應該是很薄的一層,主要是組合領域層中的不同對象,來實現最終的現實需求。本文主要實現的UnitOfWork也是在這一層。

  第三層領域層,這是整個系統中最核心的一層,表達了業務的邏輯,按照通用語言、和使用通用語言對於業務範圍進行限界上下文的劃分,劃分出一塊一塊的領域出來,並使用“實體對象、值對象、Repository、領域服務”等概念進行組織。其中Repository應該是抽象的接口,為了解耦合具體的ORM,Repository模式應該是在領域層定義接口、在第四層基礎設施層實現(以備日後可能的進行更換ORM行為)。

  第四層基礎設施層,是項目中一些具體底層操作的實現,比如領域層的Repository的實現,文件的保存,郵件的發送,等等。

  領域驅動設計是一門精深的學問,以上只簡單介紹,如果有興趣,可以去看看這兩本書: 《領域驅動設計 軟件核心復雜性應對之道》《實現領域驅動設計》。

三、項目分層簡介

 個人的項目規模也不大,但是會有“在同一次Http請求內同一事務內組合業務邏輯”的需求,結合ASP.NET的已有功能,分層如下:

  用戶界面層:Web SPA(Angular、React等,前端分離另行開發,故VS中不會有相關的項目)

  應用服務層:ASP.NET WebAPI

  領域層:一個單獨的類庫項目,定義Model(貧血模型,只包含屬性)、服務的接口IXXXService以及其實現XXXService,倉儲接口IRepository。

  基礎設施層:一個單獨的類庫項目:實現了領域層中定義的IRepository,目前已有了EF以及Dapper的兩種實現。

  項目引用(依賴)順序:最頂層是領域層,無任何依賴;應用訪問層(WebAPI)依賴領域層以及基礎設施層。

  在領域層中,我定義如下兩種基礎接口,其中IUnitOfWork接口包含一個虛擬事務對象、以及一個Commit()方法。IRepository對象用於實現對於每一個實體(model或者叫entity都可以)的持久化操作,處於簡單期間並沒有加入比如分頁查詢方法等。

    public interface IUnitOfWork
    {
        /// <summary>
        /// 事務對象,Dapper為IDbTransaction,EF為DbContext
        /// </summary>
        object VirtualTransaction { get; }

        void Commit();
    }

    public interface IRepository<T> where T:class
    {
        int Insert(T t);

        T Get(string id);

        int Update(T t);

        int Delete(T t);       
    }

  因為ASP.NET Core自帶了一個方便的IOC容器,可以實現Transient(每次使用都創建新的對象)、Scope(每個Http請求內只創建唯一的對象)、Singletion(單例)三種生命周期的依賴註入,結合ASP.NET的工作原理:對於每一個Http請求,都會實例化一個Controller對其處理,因此我們可以進行如下設計:

  在ASP.NET Core的StartUp中,將IUnitOfWork與其實現類UnitOfwork,進行Scope方式註入。

  這樣,每次Http請求中,因為自始至終只會有一個UnitOfWork對象,這個對象裏面保存著一個用於事務的對象(EF是DbContext,Dapper是IDBTransaction),在Action結束的時候,調用UnitOfWork的Commit()方法,進行提交,即實現了每個Controller中只會有一個事務。

  //UnitOfWork會被註入到Repo對象中,Repo對象會被註入到Service對象中,Service對象會被註入到Controller對象中

  services.AddScoped<IUnitOfWork,UnitOfWork>();

  services.AddScoped<IUserRepository, UserRepository>();
  services.AddScoped<IBookRepository, BookRepository>();

  services.AddScoped<IAccountService, AccountService>();
  services.AddScoped<IPublishService, PublishService>();

再貼一下我的Controller的示意代碼

    [Route("api/[controller]")]
    public class BusinessController : Controller
    {
        private readonly IAccountService _accountService;
        private readonly IPublishService _publishService;
        private readonly IUnitOfWork _unitOfWork;

        public BusinessController( IPublishService publishService, IAccountService accountService, IUnitOfWork unitOfWork)
        {
            
            _accountService = accountService;
            _publishService = publishService;
            _unitOfWork = unitOfWork;
        }

         //這裏寫具體的WebAPI的Action方法,方法中可以調用各種Service中的業務邏輯方法,然後在方法的末尾,加上_unitOfWork.Commit()即可
       
    }

  WebAPI作為整個項目的應用服務層、自身沒有任何業務邏輯,而是組合業務邏輯層中的各種Service,完成具體的現實的用戶使用需求。

  比如一個現實需求是“一個作家,註冊成為了作協會員,並登記了他的出版作品”。這個需求可以拆解為兩個業務邏輯:1.註冊 2.登記出版物 。我們在領域層中實現了這兩個業務邏輯後,就可以在應用訪問層(我們的Controller中)進行組合、並在同一個事務過程中控制他們了。有人可能會問:你這麽做,有什麽必要呢,我直接三層架構那樣,Controller調用業務邏輯層BLL中的一個“註冊並登記出版物”方法,然後這個BLL方法直接去調用DAL中各種數據庫操作方法,不也可以實現你所說的嗎?

  好,這時候,應用服務層+UnitOfWork模式的優點就可以體現出來了。如果是按照傳統三層那樣,一個BLL方法“註冊並登記出版物”,他就只能用於“註冊並登記出版物”了,無法實現業務邏輯的組合復用。而使用DDD提倡的編程模式,就可以實現多種業務邏輯組合復用,比如我前面距離的“註冊”“登記出版物”這兩個領域服務方法,同時還可以組合其他業務邏輯實現更多不同的現實需求中,比如“註冊”+“繳費”、“登記出版物”+“領取津貼”,這裏每一個需求都是在同一個事務內的,也可以保證數據的一致性。

  貼一個IService方法和Service,都在領域層中

    public interface IPublishService
    {
        void PublishNewBook(Book book);
    }

    public class PublishService : IPublishService
    {
        private readonly IBookRepository _bookRepository;

        public PublishService(IBookRepository bookRepository)
        {
            _bookRepository = bookRepository;
        }

        public void PublishNewBook(Book book)
        {
            _bookRepository.Insert(book);
        }
    }

貼一個IRepository(領域層中)和它的對應實現(Repository)

  

    //領域層中的Book倉儲接口,這裏簡單起見,沒有使用更多的接口方法
    public interface IBookRepository:IRepository<Book>
    {
        
    }

    //基礎設施層中使用Dapper操作數據庫的實現
    public abstract class BaseRepository
    {

        private readonly IDbTransaction _dbTransaction;

        protected internal IDbConnection DbConnection { get; }


        protected BaseRepository(IUnitOfWork unitOfWork)
        {
            _dbTransaction = (unitOfWork.VirtualTransaction) as IDbTransaction;

            DbConnection = _dbTransaction.Connection;
        }

        public CommandDefinition GenCmd(string cmdText, object paramObj)
        {
            CommandDefinition cmd = new CommandDefinition(cmdText, paramObj, _dbTransaction);

            return cmd;
        }
    }

    public class BookRepository :BaseRepository, IBookRepository
    {
        

        public BookRepository(IUnitOfWork unitOfWork):base(unitOfWork)
        {
            
        }

        //這裏只寫了Insert方法,其他的可類比
        public int Insert(Book t)
        {
            string cmdText = "INSERT INTO Book (Id,BookName,Author,Price,PublishTime) VALUES (@Id,@BookName,@Author,@Price,@PublishTime)";

            var cmd = GenCmd(cmdText, t);

      
var result = DbConnection.Execute(cmd); return result; } } //另外一個基礎設施層項目中,使用EF操作數據庫的實現 public abstract class BaseRepository { protected internal DbContext DbContext { get; } protected BaseRepository(IUnitOfWork unitOfWork) { DbContext = unitOfWork.VirtualTransaction as DbContext; } } public class BookRepository : BaseRepository ,IBookRepository { public BookRepository(IUnitOfWork unitOfWork):base(unitOfWork) { } //這裏只寫了Inert方法,其他的可以類比實現 public int Insert(Book t) { DbContext.Book.Add(t);
       //這裏不能寫DbContext.SaveChanges(),因為EF的SaveChanges()是一個對所有更改的事務性提交,而根據我們的設計事務不在此處提交,而是在應用服務層的Controller中
return 1; } }

然後,我們就可以在Controller中組合不同Service提供的業務邏輯了,並且可以在同一個事務內,有效的提高了業務邏輯層的復用程度同時保證了同一個Http請求內數據的事務一致性。

記錄一次使用UnifOfWork改造項目的過程。