我的业务对象使用以下架构编码:
示例业务对象接口:
public interface IAmARegistration
{
string Nbk { get; set; } //Primary key?
string Name { get; set; }
string Email { get; set; }
string MailCode { get; set; }
string TelephoneNumber { get; set; }
int? OrganizationId { get; set; }
int? OrganizationSponsorId { get; set; }
}
业务对象存储库接口:
/// <summary>
/// Handles registration persistance or an in-memory repository for testing
/// requires a business object instead of interface type to enforce validation
/// </summary>
public interface IAmARegistrationRepository
{
/// <summary>
/// Checks if a registration record exists in the persistance mechanism
/// </summary>
/// <param name="user">Takes a bare NBK</param>
/// <returns></returns>
bool IsRegistered(string user); //Cache the result if so
/// <summary>
/// Returns null if none exist
/// </summary>
/// <param name="user">Takes a bare NBK</param>
/// <returns></returns>
IAmARegistration GetRegistration(string user);
void EditRegistration(string user,ModelRegistration registration);
void CreateRegistration(ModelRegistration registration);
}
然后,实际的业务对象如下所示:
public class ModelRegistration : IAmARegistration//,IDataErrorInfo
{
internal ModelRegistration() { }
public string Nbk
{
get
{
return _nbk;
}
set
{
if (String.IsNullOrEmpty(value))
throw new ArgumentException("Nbk is required");
_nbk = value;
}
}
... //other properties omitted
public static ModelRegistration CreateModelAssessment(IValidationDictionary validation, IAmARegistration source)
{
var result = CopyData(() => new ModelRegistration(), source, false, null);
//Any other complex validation goes here
return result;
}
/// <summary>
/// This is validated in a unit test to ensure accuracy and that it is not out of sync with
/// the number of members the interface has
/// </summary>
public static Dictionary<string, Action> GenerateActionDictionary<T>(T dest, IAmARegistration source, bool includeIdentifier)
where T : IAmARegistration
{
var result = new Dictionary<string, Action>
{
{Member.Name<IAmARegistration>(x=>x.Email),
()=>dest.Email=source.Email},
{Member.Name<IAmARegistration>(x=>x.MailCode),
()=>dest.MailCode=source.MailCode},
{Member.Name<IAmARegistration>(x=>x.Name),
()=>dest.Name=source.Name},
{Member.Name<IAmARegistration>(x=>x.Nbk),
()=>dest.Nbk=source.Nbk},
{Member.Name<IAmARegistration>(x=>x.OrganizationId),
()=>dest.OrganizationId=source.OrganizationId},
{Member.Name<IAmARegistration>(x=>x.OrganizationSponsorId),
()=>dest.OrganizationSponsorId=source.OrganizationSponsorId},
{Member.Name<IAmARegistration>(x=>x.TelephoneNumber),
()=>dest.TelephoneNumber=source.TelephoneNumber},
};
return result;
}
/// <summary>
/// Designed for copying the model to the db persistence object or ui display object
/// </summary>
public static T CopyData<T>(Func<T> creator, IAmARegistration source, bool includeIdentifier,
ICollection<string> excludeList) where T : IAmARegistration
{
return CopyDictionary<T, IAmARegistration>.CopyData(
GenerateActionDictionary, creator, source, includeIdentifier, excludeList);
}
/// <summary>
/// Designed for copying the ui to the model
/// </summary>
public static T CopyData<T>(IValidationDictionary validation, Func<T> creator,
IAmARegistration source, bool includeIdentifier, ICollection<string> excludeList)
where T : IAmARegistration
{
return CopyDictionary<T, IAmARegistration>.CopyData(
GenerateActionDictionary, validation, creator, source, includeIdentifier, excludeList);
}
我在编写隔离测试时遇到问题的示例存储库方法:
public void CreateRegistration(ModelRegistration registration)
{
var dbRegistration = ModelRegistration.CopyData(()=>new Registration(), registration, false, null);
using (var dc=new LQDev202DataContext())
{
dc.Registrations.InsertOnSubmit(dbRegistration);
dc.SubmitChanges();
}
}
的问题:
我正在使用其他持久性机制和ui机制(Windows窗体,asp.net,asp.net mvc 1等)来完全重用此业务层。此外,团队成员可以轻松地针对此业务层/架构进行开发。
有没有办法强制执行不可变的经过验证的模型对象,或强制说ui或persistance层在没有这些麻烦的情况下都无法获得无效的模型对象?
答案 0 :(得分:2)
这对我来说非常复杂。
IMO,域对象应该是保护其不变量的POCO。您不需要工厂来执行此操作:只需在构造函数中请求任何必要的值,并为其余值提供有效的默认值。
如果您拥有属性设置器,请通过调用您已经执行的验证方法来保护它们。简而言之,您绝不允许实例处于不存在的状态 - 工厂或没有工厂。
在C#中编写不可变对象很简单 - 只需确保使用readonly
关键字声明所有字段。
但请注意,如果您关注Domain-Driven Design,则Domain对象往往属于三个存储桶
根据这个定义,只有值对象应该是不可变的。
答案 1 :(得分:0)
我过去采用了不同的方法。我没有使用验证异常保护属性设置器,而是采用以下约定:A)所有域对象提供按需验证自身的方法(例如,验证方法),以及B)存储库断言持久性操作所在的所有对象的验证请求(例如,通过调用Validate方法并在失败时抛出异常)。这意味着使用存储库是信任存储库维护此约定。
这在.NET世界中的优势至少是一个易于使用的域表面和更简洁和可读的代码(没有巨大的try-catch块或迂回异常处理机制)。这也与ASP.NET MVC验证方法很好地集成。
我会说在域对象暴露的非属性操作中(例如Demeter法对组合集合所暗示的那些),我倾向于返回一个提供即时验证反馈的ValidationResult。 Validate方法也返回此反馈。我这样做的原因是对消费代码的结构或可读性没有任何不利影响。如果调用者不想检查返回值,则他们不必;如果他们愿意,他们可以。在不影响消费代码结构的情况下(使用try-catch块),属性设置器无法实现这样的好处。同样,域服务返回ValidationResults。只有存储库抛出ValidationExceptions。
总的来说,这确实允许您将域对象置于无效状态,但它们与调用者的执行上下文(调用者的“工作空间”)隔离,并且无法持久保存到系统中。