本文簡要說明如何使用 Rafy 框架中的領域控制器。
簡介
領域控制器是 Rafy 框架中用於封裝領域邏輯的主要方式。
在控制器中,開發者可以封裝大量的業務邏輯,並向外暴露業務介面。內部的邏輯在實現時,往往呼叫一個或多個實體倉庫的 CDUQ 方法來實現。
示例
以下程式碼為 Rafy.Accounts 帳戶外掛 中 AccountController 型別的真實程式碼。
/// <summary>
/// 帳戶外掛的領域控制器。
/// </summary>
public class AccountController : DomainController
{
/// <summary>
/// 註冊指定的使用者。
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
[ControllerLogic]
public virtual Result Register(User user)
{
if (user == null) throw new ArgumentNullException("user");
var userNameAsId = _identityMode.HasFlag(UserIdentityMode.UserName);
if (userNameAsId && string.IsNullOrEmpty(user.UserName)) return new Result(ResultCodes.RegisterUserNameInvalid, "使用者名稱不能為空。");
var emailAsId = _identityMode.HasFlag(UserIdentityMode.Email);
if (emailAsId && !TextFormatter.ReEmail.IsMatch(user.Email)) return new Result(ResultCodes.RegisterEmailInvalid, "郵箱格式不正確。");
if (!userNameAsId && !emailAsId) throw new InvalidProgramException("!userNameAsId && !useEmailAsId");
//驗證其它屬性。
var brokenRules = Validator.Validate(user);
if (brokenRules.Count > 0) return new Result(ResultCodes.RegisterPropertiesInvalid, brokenRules.ToString());
//檢查使用者名稱、郵箱的重複性。
var repo = RF.ResolveInstance<UserRepository>();
var criteria = new CommonQueryCriteria();
criteria.Concat = BinaryOperator.Or;
if (userNameAsId)
{
criteria.Add(new PropertyMatch(User.UserNameProperty, user.UserName));
}
if (emailAsId)
{
criteria.Add(new PropertyMatch(User.EmailProperty, user.Email));
}
var exists = repo.GetFirstBy(criteria);
if (exists != null)
{
if (emailAsId && exists.Email == user.Email)
{
return new Result(ResultCodes.RegisterEmailDuplicated, string.Format("註冊失敗,已經存在郵箱為:{0} 的使用者。", user.Email));
}
else
{
return new Result(ResultCodes.RegisterUserNameDuplicated, string.Format("註冊失敗,已經存在使用者名稱為:{0} 的使用者。", user.UserName));
}
}
//儲存這個使用者
user.PersistenceStatus = PersistenceStatus.New;
repo.Save(user);
this.OnRegisterSuccessed(user);
return true;
}
/// <summary>
/// 註冊成功的事件。
/// </summary>
public event EventHandler<AccountEventArgs> RegisterSuccessed;
/// <summary>
/// 註冊成功的事件。
/// </summary>
/// <param name="user"></param>
protected virtual void OnRegisterSuccessed(User user)
{
var handler = this.RegisterSuccessed;
if (handler != null) handler(this, new AccountEventArgs(user));
}
}
呼叫方的程式碼如下:
var controller = DomainControllerFactory.Create<AccountController>();
var res = controller.Register(new User
{
UserName = "hqf",
RealName = "hqf",
Password = controller.EncodePassword("hqf")
});
通過 DomainControllerFactory 來建立一個控制器(也可用簡寫 DCF),即可呼叫其中的方法。
特點
-
支援本地呼叫,也支援分散式呼叫
領域控制器是除了倉庫查詢以外,提供分散式資料傳輸的另一機制。控制器的呼叫,支援本地呼叫,也支援分散式呼叫。詳見:部署。
-
無狀態
領域控制器本身應該是無狀態的。每次使用工廠建立時,都會建立一個新例項。特殊情況下,如果需要傳遞狀態,需要對屬性新增 [ControllerClientSettings] 標記。
-
支援領域控制器事件及依賴管理
詳見後文。
-
支援使用介面來定義控制器契約。參見:IDomainControllerContract 介面。
遠端呼叫
DomainController 中,所有可遠端呼叫的方法,都需要滿足:一、標記為虛方法;二、新增 [ControllerLogic] 標記。工廠會為在執行時建立控制器的子類,並這些方法實現遠端呼叫。
所以,此類方法需要注意,引數及返回值應該都是要支援序列化的。否則會在遠端呼叫時失敗。
領域控制器事件
各業務模組可以分別定義大量的領域控制器,而模組之間的業務,往往需要進行互動。除直接的呼叫關係以外,領域控制器還提供了事件依賴及管理功能。
例如,我們往往希望在使用者註冊成功後,各業務模組(例如部落格模組)再額外註冊一些其它內容。這時,我們又不希望修改使用者的註冊程式碼。那麼我們可以在部落格模組的領域控制器中,指定該控制器依賴 AccountController,這時再監聽 RegisterSuccessed 事件新增自己的業務邏輯。
下面示例程式碼中,基礎庫存模組與入庫管理外掛,後者依賴前者。程式碼展示了,庫存業務外掛的 StockChanged 事件發生時,入庫模組會發生一些特定的邏輯。
//業務外掛一:庫存模組
public class StockController : DomainController
{
public event EventHandler StockChanged;
protected virtual void OnStockChanged()
{
var handler = this.StockChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
}
//業務外掛二:入庫管理外掛
public class RecieveController : DomainController
{
static RecieveController()
{
Depend<RecieveController>().On<StockController>();
}
protected override void OnAlwaysDependon(DomainController controller)
{
var sc = controller as StockController;
if (sc != null)
{
sc.StockChanged += OnStockChanged;
}
}
private void OnStockChanged(object sender, EventArgs e)
{
//根據庫存變化資訊,來實現特定功能
}
}
PS:該文已經納入《 Rafy 使用者手冊》中。