Rafy 框架:領域控制器

BloodyAngel發表於2022-03-23

Rafy

本文簡要說明如何使用 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 使用者手冊》中。

相關文章