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

Posted

技术标签:

【中文标题】服务层如何适应我的存储库实现?【英文标题】:How does a service layer fit into my repository implementation? 【发布时间】:2011-09-02 13:52:54 【问题描述】:

我创建了一个 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;
                  

更新: 我目前正在使用 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
    


问题

    我是否向服务中添加了与我的模型(数量、代码等)相同(可能更多)的属性,还是只提供接受 GiftCertificate 对象和直接参数的方法?

    我是在调用 Service 构造函数时创建 GiftCertificate 实体的默认实例,还是只根据需要创建新实例(例如 - 对于需要调用实体中的验证方法的服务中的验证方法?另外,关于创建默认存储库实例的同样问题...?

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

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

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

另外,如果我想从 UI 层进行兑换,我是否首先从 UI 调用模型/服务验证方法以提供用户反馈,然后调用将在内部再次运行相同检查的 Redeem 方法?

从 UI 调用服务进行兑换操作的示例:

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);

从 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...

在创建 GC 的方式以及如何在实体/服务之间分离验证方面感觉有些不对劲。用户/消费者不必关心什么验证在什么地方......建议?

【问题讨论】:

相关帖子 - ASP.NET MVC (Domain Model, Repository, Fluent, Services - Structure for my Project) & Best Structure for ASP.NET MVC Solution 【参考方案1】:

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

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

除了服务层,应该还有单独的业务逻辑层,还是服务层的作用?

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

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

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

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

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

例如使用 DTO:

GiftCertificateModel.CreateGiftCertificate(int GiftCerticiateId, string Code, decimal Amount, DateTime ExpirationDate)

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

模型验证字段级值,例如。通过检查必填字段、年龄或日期范围等来确保输入有效。服务应进行任何需要检查模型值外部的验证。检查礼券尚未兑换,检查礼券所在商店的属性。

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

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

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

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

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

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

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

giftCertificateService.Redeem(giftCertificate);

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

上面已经回答了。

更新

由于您使用的是 WebForms,因此掌握一些概念可能会有些困难,但我所提到的一切都是适用的,因为我所描述的是一个通用的 MVC 范式。用于数据访问的 ADO.NET 无关紧要,因为数据访问是通过存储库分离的。

我是否向服务添加了与我的模型(金额、代码等)相同(可能更多)的属性,还是只提供接受 GiftCertificate 对象和直接参数的方法?

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

我是在调用 Service 构造函数时创建 GiftCertificate 实体的默认实例,还是只根据需要创建新实例(例如 - 对于需要调用实体中的验证方法的服务中的验证方法?另外,关于创建默认存储库实例的同样问题...?

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

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

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

对于验证,我可以理解您为什么有点担心,因为直接在模型上完成了验证,而在服务中完成了其他类型的验证。我使用的经验法则是,如果验证需要调用数据库,那么它应该在服务层完成。

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

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

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

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

该服务将检查其他业务逻辑,例如商店是否已发放过多礼券。如果有多个错误代码,它要么返回状态的枚举,要么抛出带有错误信息的异常。

最后,服务调用gcRepository.Save(gc)

【讨论】:

@Lester - 你太棒了!终于开始把它拉到一起。我应该在帖子中指出,我正在使用 Web 表单和经典 ADO 进行数据访问。请查看我上面的其他问题和代码,让我知道您的想法。对于如何实例化新的 GiftCertificate 对象并从 UI 层验证参数,我仍然有点模糊。 更新了您的新问题的答案。我很高兴能提供帮助,但我认为如果您使用的是 ASP.NET MVC,它会更容易并且更容易理解。虽然就像我提到的 MVC 范式在 WebForms 中仍然有效。 @Lester - 请您查看我的 UI 调用代码示例,让我知道它是否应该这样工作?对于 UI 中的字段级验证,它是否应该根据需要调用模型和/或服务上的验证方法?另外,根据您的说法,用户界面可以在服务之外创建 gc 对象,然后将它们提交给服务...... 我也计划转向 MVC,但由于我有一些基本的 Web 表单经验并且一次只能承担这么多,因此我试图了解一些架构基础知识。 两个代码示例看起来都很好,除了 GiftCertificate gc = new GiftCertificate();我在回答中提到你不应该仅仅为了验证而实例化。看看 S#arp,他们有完整的代码解决方案供您查看。【参考方案2】:

    您不必为每个实体创建存储库,see here for more,

    通常每个人定义一个存储库 在域中聚合。那就是:我们 每个实体都没有存储库!如果 我们来看一个简单的订单输入 系统实体 Order 可能是 Order 聚合的根。因此我们 将有一个订单存储库。

    每个存储库是否应该有一项服务? -> 并非总是如此,因为您可能会在一项服务中使用多个存储库。

    服务创建模型实例,存储库永远不会与模型交互,实际上它返回模型随后将使用的实体。

    在 UI 级别处理输入/范围等类型的验证(您可以使用 javascript 或任何其他库),并让服务仅处理业务方面。您可以获得同样作用的属性的好处。

    UI->Service->Repository,如果存储库正在调用服务,那么 IMO 一定有问题。


您的代码更改,

    将模型和存储库分开。

    public class GiftCertificateModel
    
    
    public class GiftCertificateRepository
    
       //Remove Model related code from here, and just put ONLY database specific code here, (no business logic also). Common methods would be Get, GetById, Insert, Update etc. 
    
        Since essence of Repository is to have common CRUD logic at one place soyou don't have to write entity specific code. 
        You will create entity specific repository in rare cases, also by deriving base repository.
    
    
    
    public class GiftCertificateService()
    
        //Create Model instance here
        // Use repository to fill the model (Mapper)
    
    
    

【讨论】:

@alliswell - 当您说“3.Service 创建模型实例时,存储库将永远不会与模型交互,实际上它返回模型随后将使用的实体。”模型和实体有什么区别?现在我有服务/存储库/POCO。存储库获取数据并调用映射到我的 POCO(域对象/实体/任何东西)的映射器。您是说当我需要创建一个新实体时,该服务应该创建新的空实体?对我最近的 2 个代码示例有什么建议吗? 实体,它是相关的数据库(存储库)和这里的模型,它与 UI 相连。所以,你的方法是正确的,除了我的意思是服务将调用存储库来填充/插入/更新数据,并且 UI 将与服务交互。因此存储库永远不会直接与 UI 交互。 (关注点分离) @alliswell - 明白了!新的礼券需要一个特殊的代码,该代码必须在数据库中生成并检查其唯一性。如果我的用户界面创建新的礼券实体并从服务层获取代码,然后调用服务进行保存,或者它是否应该简单地调用服务层,该服务层将返回一个新的礼券对象,其中代码已经填充,或者用户界面是否只调用一个方法在创建新的服务上,它接受数量、expDate 的参数,它会一步完成创建对象、获取代码、保存到数据库并传回完整对象的所有操作? 好的,在这种情况下首先查询代码唯一性,如果它是唯一的,则执行相应的步骤,例如保存或获取。此外,如果您的 UI 不需要该代码而不是传递所有内容,则首先生成它而不是使用它并传递值以进行修改。 Re: #4,购买礼品卡不是需要哪些字段由企业定义,从而获得企业访问权限吗?如果您想构建一个支持相同用例的 WP7 应用程序怎么办,这个建议是否意味着您必须在 WP7 UI 中重新编码验证?干呢?【参考方案3】:

您可以创建一个名为 GiftCertificateService 的服务。

这样您就可以将任何不属于 GiftCertificateModel 职责的任务协调到它的服务中。 (不要与 WCF 服务混淆)。

该服务将控制所有任务,因此您的 UI(或任何可能的调用者)将使用该服务中定义的方法。

然后该服务将调用模型上的方法、使用存储库、创建事务等。

例如。 (基于您提供的示例代码):

public class GiftCertificateService 

   public void CreateCertificate() 
   
      //Do whatever needs to create a certificate.
      GiftCertificateRepository gcRepo = new GiftCertificateRepository();
      GiftCertificateModel gc = gcRepo.CreateNew();
      gc.Amount = 10.00M;
      gc.ExpirationDate = DateTime.Today.AddMonths(12);
      gc.Notes = "Test GC";
      gcRepo.Save(gc);
   

UI 将调用 CreateCertificate 方法(传递参数等),该方法也可能返回一些内容。

注意:如果您希望该类在 UI 上起作用,那么请创建一个控制器类(如果您正在使用 MVC)或演示者类(如果您正在使用 MVVM,并且不想将所有内容都放在 ViewModel 中) 并使用该类中的 GiftCertificateService。

【讨论】:

我正在使用带有经典 ADO 的 Web 表单进行数据库交互。那么,我是否需要在服务上创建与我在模型上的金额、到期日期等相同的属性?澄清一下,我永远不会从 UI 调用新的 repo 或新模型,而只会从服务层调用。例如 GiftCertificate gc = GiftCertificateService.NewGiftCertificateObject(),然后完成对象并调用服务保存方法?跨度> 如果您使用 Web 表单,大多数情况下您的视图要么使用事件处理程序自治,要么遵循 model-view-x 模式(演示者或控制器)。事件处理程序或控制器/演示者是您可以在服务上调用方法的正确位置(甚至执行数据访问,尽管大多数指南不鼓励这样做)。您不需要创建所有相同的属性。您可以在顶部添加一个可重用层(这里是服务),然后从那里调用协调操作的方法(数据访问、验证、对模型对象的调用等)。

以上是关于服务层如何适应我的存储库实现?的主要内容,如果未能解决你的问题,请参考以下文章

胖模型/瘦控制器与服务层[关闭]

我的 Entity Framework 存储库和服务层方法应返回哪些类型:List、IEnumerable、IQueryable?

存储库和服务层之间的区别?

使用Spring从控制器层调用存储库和服务

使用 Spring 从控制器层调用存储库和服务

服务层和数据访问对象层如何适应 Spring MVC 架构?