服务层如何适合我的存储库实现?

jps*_*ook 52 c# asp.net domain-driven-design repository repository-pattern

我创建了一个POCO模型类和一个处理持久性的存储库类.由于POCO无法访问存储库,因此存储库中有许多业务逻辑任务似乎不对.根据我的阅读,看起来我需要一个位于UI使用者和存储库层之间的服务层.我不确定的是它应该如何工作......

除了服务层,还应该有一个单独的业务逻辑层,还是那个服务层的角色?

每个存储库应该有一个服务吗?

服务层是UI可以实例化模型对象还是存储库为服务提供新模型实例的唯一方式?

我是否将我的参数,模型和其他验证放在服务层中进行检查以确保输入有效以及更新前数据库中是否存在要更新的项目?

模型,存储库和UI都可以调用服务层,还是仅供UI使用?

服务层应该是所有静态方法吗?

从UI调用服务层的典型方法是什么?

模型与服务层应该进行哪些验证?

以下是我现有图层的示例代码:

public class GiftCertificateModel
{
    public int GiftCerticiateId {get;set;}
    public string Code {get;set;}
    public decimal Amount {get;set;}
    public DateTime ExpirationDate {get;set;}

    public bool IsValidCode(){}
}


public class GiftCertificateRepository
{
    //only way to access database
    public GiftCertificateModel GetById(int GiftCertificateId) { }
    public List<GiftCertificateModel> GetMany() { }
    public void Save(GiftCertificateModel gc) { }
    public string GetNewUniqueCode() { //code has to be checked in db }

    public GiftCertificateModel CreateNew()
    {
        GiftCertificateModel gc = new GiftCertificateModel();
        gc.Code = GetNewUniqueCode();
        return gc;
    }              
}
Run Code Online (Sandbox Code Playgroud)

更新: 我目前正在使用Web表单和经典的ADO.NET.我希望最终转向MVC和EF4.

更新:非常感谢@Lester的出色解释.我现在明白我需要为每个存储库添加一个服务层.该层将是UI或其他服务可以与存储库通信的唯一方式,并且将包含任何不适合域对象的验证(例如 - 需要调用repo的验证)

public class GiftCertificateService()
{

    public void Redeem(string code, decimal amount)
    {
        GiftCertificate gc = new GiftCertificate();
        if (!gc.IsValidCode(code))
        {
            throw new ArgumentException("Invalid code");
        }

        if (amount <= 0 || GetRemainingBalance(code) < amount)
        {
            throw new ArgumentException("Invalid amount");
        }

        GiftCertificateRepository gcRepo = new GiftCertificateRepository();
        gcRepo.Redeem(code, amount);
    }

    public decimal GetRemainingBalance(string code)
    {
        GiftCertificate gc = new GiftCertificate();            
        if (!gc.IsValidCode(code))
        {
            throw new ArgumentException("Invalid code");
        }

        GiftCertificateRepository gcRepo = new GiftCertificateRepository();
        gcRepo.GetRemainingBalance(code);
    }

    public SaveNewGC(GiftCertificate gc)
    {
        //validates the gc and calls the repo save method
        //updates the objects new db ID
    }

}
Run Code Online (Sandbox Code Playgroud)

问题

  1. 难道我添加相同(甚至更多)性质的服务,因为我有我的模型(量,代码等),或者我只提供接受GiftCertificate对象和直接的参数的方法?

  2. 难道我创建GiftCertificate实体的默认实例时,服务调用构造函数或根据需要(例如,只需要创建新的 - 在服务,需要调用验证方法在实体此外,有关创建一个默认的相同的问题,验证方法?存储库实例......?

  3. 我知道我通过服务公开了repo的功能,我是否也从实体中公开了方法(例如 - IsValidCode等)?

  4. UI可以直接创建新的GiftCertificate对象而无需通过服务(例如 - 从实体调用参数验证方法).如果没有,如何执行它?

  5. 在UI层,当我想创建一个新的礼品证书时,我是直接从UI层调用模型/服务验证(如IsValidExpirationDate等)还是首先对该对象进行水合,然后将其传递给要验证,然后将某种验证摘要返回给UI?

另外,如果我想从UI层兑换,我先请从UI模型/服务的验证方法给用户的反馈,然后调用兑换,这将运行相同的再次检查内部的方法?

从UI调用服务以执行Redeem操作的示例:

string redeemCode = RedeemCodeTextBox.Text;

GiftCertificateService gcService = new GiftCertificateService();
GiftCertificate gc = new GiftCertificate(); //do this to call validation methods (should be through service somehow?)

if (!gc.IsValid(redeemCode))
{
    //give error back to user
}

if (gcService.GetRemainingBalance(redeemCode) < amount)
{
    //give error back to user
}

//if no errors
gcService.Redeem(code,amount);
Run Code Online (Sandbox Code Playgroud)

从UI创建新礼品证书的示例:

GiftCertificateService gcService = new GiftCertificateService();
GiftCertificate gc = new GiftCertificate();

if (!gc.IsValidExpDate(inputExpDate))
{
    //give error to user..
}

//if no errors...
gc.Code = gcService.GetNewCode();
gc.Amount = 10M;
gc.ExpirationDate = inputExpDate;
gcService.SaveNewGC(gc);
//method updates the gc with the new id...
Run Code Online (Sandbox Code Playgroud)

关于创建GC以及如何在实体/服务之间分离验证的方法感觉不对.用户/消费者不应该关心哪些验证在哪个地方......建议?

Les*_*ter 42

看看S#arp Architeture.它就像是构建ASP.NET MVC应用程序的最佳实践架构框架.一般的体系结构模式是每个实体有1个存储库,它只负责数据访问和每个存储库1个服务,它只负责业务逻辑和控制器与服务之间的通信.

根据S#arp Architeture回答您的问题:

除了服务层,还应该有一个单独的业务逻辑层,还是那个服务层的角色?

模型应负责字段级验证(例如使用必需的字段属性),而控制器可以在保存之前验证数据(例如,在保存之前检查状态).

每个存储库应该有一个服务层吗?

是的 - 每个存储库应该有一个服务(每个存储库不是一个服务层,但我猜你是这个意思).

服务层是UI可以实例化模型对象还是存储库为服务提供新模型实例的唯一方式?

存储库和服务可以根据需要返回单个实体,实体集合或数据传输对象(DTO).控制器将这些值传递给模型中的静态构造函数方法,该方法将返回模型的实例.

使用DTO:

GiftCertificateModel.CreateGiftCertificate(int GiftCerticiateId, string Code, decimal Amount, DateTime ExpirationDate)
Run Code Online (Sandbox Code Playgroud)

我是否将我的参数,模型和其他验证放在服务层中进行检查以确保输入有效以及更新前数据库中是否存在要更新的项目?

模型验证字段级值ex.通过检查所需的字段,年龄或日期范围等确保输入有效.服务应该进行任何需要在模型值ex之外进行检查的验证.检查礼品券尚未兑换,检查礼品券的商店属性).

模型,存储库和UI都可以调用服务层,还是仅供UI使用?

控制器和其他服务应该是唯一调用服务层的服务.服务应该是唯一一个调用存储库的服务.

服务层应该是所有静态方法吗?

它们可以但是如果不是这样的话,它们更容易维护和扩展.如果每个实体/子类有1个服务,则更改实体和添加/删除子类更容易更改.

从UI调用服务层的典型方法是什么?

调用服务层的控制器的一些示例:

giftCertificateService.GetEntity(giftCertificateId); (which in turn is just a call to the giftCertificateRepository.GetEntity(giftCertificateId)

giftCertificateService.Redeem(giftCertificate);
Run Code Online (Sandbox Code Playgroud)

模型与服务层应该进行哪些验证?

已在上面回答.

UPDATE

既然你正在使用WebForms,那么掌握一些概念可能会有点困难,但我提到的一切都是适用的,因为我所描述的是一般的MVC范式.用于数据访问的ADO.NET无关紧要,因为数据访问是通过存储库分离的.

难道我添加相同(甚至更多)性质的服务,因为我有我的模型(量,代码等),或者我只提供接受GiftCertificate对象和直接的参数的方法?

您需要查看服务,正如其名称所暗示的那样 - 控制器可以调用的操作.您不需要在模型中定义的属性,因为它们已在模型中可用.

难道我创建GiftCertificate实体的默认实例时,服务调用构造函数或根据需要(例如,只需要创建新的 - 在服务,需要调用验证方法在实体此外,有关创建一个默认的相同的问题,验证方法?存储库实例......?

控制器和服务应分别具有服务和存储库的私有字段.您不应该为每个操作/方法实例化.

我知道我通过服务公开了repo的功能,我是否也从实体中公开了方法(例如 - IsValidCode等)?

不太清楚你的意思.如果服务返回实体,则实体上的那些方法已经公开.如果他们返回DTO,则意味着您只对某些信息感兴趣.

为了验证,我可以看到为什么你有点担心,因为直接在模型和服务中完成的其他类型的验证上进行了验证.我使用的经验法则是,如果验证需要调用db,那么它应该在服务层中完成.

UI可以直接创建新的GiftCertificate对象而无需通过服务(例如 - 从实体调用参数验证方法).如果没有,如何执行它?

在UI层,当我想创建一个新的礼品证书时,我是直接从UI层调用模型/服务验证(如IsValidExpirationDate等)还是首先对该对象进行水合,然后将其传递给要验证,然后将某种验证摘要返回给UI?

对于这两个问题,我们来看一个场景:

用户输入信息以创建新证书并提交.有字段级验证,因此如果文本框为空或美元金额为负,则会抛出验证错误.假设所有字段都有效,控制器将调用该服务gcService.Save(gc).

该服务将检查其他业务逻辑,例如商店是否已经颁发了太多礼品券.如果存在多个错误代码,则返回状态的枚举,或者引发包含错误信息的异常.

最后,服务电话gcRepository.Save(gc).