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

发布于 2024-11-10 10:54:55 字数 4123 浏览 4 评论 0原文

我创建了一个 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 或其他服务与存储库通信的唯一方式,并将包含任何不适合域对象的验证(例如 - 需要调用存储库的验证)

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
    }

}

问题

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

  2. 当调用服务构造函数时,我是否创建 GiftCertificate 实体的默认实例,或者只是根据需要创建新实例(例如,对于服务中需要调用实体中的验证方法的验证方法?此外,关于创建默认存储库实例...?

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

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

  5. 在 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 的创建方式和验证方式感觉有些不对劲实体/服务之间分开。用户/消费者不必关心什么验证在哪个地方......建议?

I have created a POCO model class and a repository class which handles persistence. Since the POCO cannot access the repository, there are lots of business logic tasks in the repository which doesn't seem right. From what I have read, it looks like I need a service layer that sits between the UI consumers and the repository layer. What I am not sure of is exactly how it is supposed to work...

In addition to the service layer, should there also be a separate business logic layer, or is that the role of the service layer?

Should there be one service per repository?

Is the service layer the only way that the UI can instance the model objects or does the repository provide the new model instance to the service?

Do I put my parameter, model and other validations in the service layer that do things like check to make sure a input is valid and that a item to update exists in the database before updating?

Can the model, repository and UI all make calls to the service layer, or is it just for the UI to consume?

Is the service layer supposed to be all static methods?

What would be a typical way to call the service layer from the UI?

What validations should be on the model vs the service layer?

Here is some sample code for my existing layers:

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

UPDATE:
I am currently using web forms and classic ADO.NET. I hope to move to MVC and EF4 eventually.

UPDATE: Big thanks to @Lester for his great explanation. I now understand that I need to add a service layer for each of my repositories. This layer will be the ONLY way the UI or other services can communicate with the repository and will contain any validations that do not fit on the domain object (eg - validations that need to call the 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
    }

}

Questions

  1. Do I add the same (and possibly more) properties to the service as I have on my model (amount, code, etc) or do I only offer methods that accept GiftCertificate objects and direct parameters?

  2. Do I create a default instance of the GiftCertificate entity when the Service constructor is called or just create new ones as needed (eg - for validation methods in the service that need to call validation methods in the entity? Also, same question about creating a default repository instance...?

  3. I know i expose the functionality of the repo via the service, do I also expose the methods from the entity as well (eg - IsValidCode, etc)?

  4. It is ok for the UI to simply create a new GiftCertificate object directly without going through the service (eg - to call parameter validation methods from the entity). If not, how to enforce it?

  5. On the UI layer, when I want to create a new gift certificate, do I call the model/service validations (like IsValidExpirationDate, etc) directly from the UI layer OR do I hydrate the object first, then pass it in to be validated and then return some sort of validation summary back to the UI?

Also, if I want to Redeem from the UI layer, do I first call the model/service validation methods from the UI to give user feedback and then call the Redeem method which will run the same checks again internally?

Example for calling service to do a Redeem operation from 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);

Example for creating a new Gift certificate from 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...

Something feels wrong about way GCs are being created and how the validations are separated between entity/service. The user/consumer should not have to be concerned with what validations are in which place... advice?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

蓝眼睛不忧郁 2024-11-17 10:54:55

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

基于S#arp架构回答你的问题:

除了服务层之外,是否还应该有一个单独的业务逻辑层,或者说这就是服务层的作用?

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

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

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

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

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

ex 使用 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 对象和直接参数的方法?

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

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

控制器和服务应该分别具有服务和存储库的私有字段。

我知道我公开了存储库的功能 。通过该服务,我是否也公开这些方法也来自实体(例如 - IsValidCode 等)?

不太确定您在这里的意思。如果服务返回实体,那么实体上的那些方法已经公开,如果它们返回 DTO,则意味着您感兴趣。仅在某些信息中。

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

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

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

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

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

该服务将检查其他业务逻辑,例如商店是否已经发行了过多的礼券。如果存在多个错误代码,它会返回状态的枚举,或者引发带有错误信息的异常。

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

Take a look at S#arp Architeture . It's like a best practices architectural framework for building ASP.NET MVC applications. The general architecture pattern is to have 1 repository per entity which is responsible only for data access and 1 service per repository which is responsible only for business logic and communicating between controllers and services.

To answer your questions based on S#arp Architeture:

In addition to the service layer, should there also be a separate business logic layer, or is that the role of the service layer?

Models should be responsible for field-level validation (ex. using required field attributes) while controllers can validate data before saving (ex. checking state before saving).

Should there be one service layer per repository?

Yes - there should be one service per repository (not 1 service layer per repository but I'm guessing you meant that).

Is the service layer the only way that the UI can instance the model objects or does the repository provide the new model instance to the service?

Repositories and services can return a single entity, a collection of entities, or data transfer objects (DTOs) as required. Controllers will pass these values to a static constructor method in the model which will return an instance of the model.

ex Using DTOs:

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

Do I put my parameter, model and other validations in the service layer that do things like check to make sure a input is valid and that a item to update exists in the database before updating?

Models validate field-level values ex. making sure input is valid by checking for required fields, age or date ranges, etc. Services should do any validation needed that requires checking outside of the model value ex. Checking that the gift certificate hasn't been redeemed yet, checking properties of the store the gift certificate is for).

Can the model, repository and UI all make calls to the service layer, or is it just for the UI to consume?

Controllers and other services should be the only ones making calls to the service layer. Services should be the only one makes making calls to repositories.

Is the service layer supposed to be all static methods?

They can be but it's easier to maintain and extend if they aren't. Changes to entities and adding/removing subclasses are easier to change if there's 1 service per entity / subclass.

What would be a typical way to call the service layer from the UI?

Some examples of controllers calling the service layer:

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

giftCertificateService.Redeem(giftCertificate);

What validations should be on the model vs the service layer?

Already answered above.

UPDATE

Since you're using WebForms it may be a little harder to grasp some of the concepts but everything I've mentioned is applicable since what I'm describing is a general MVC paradigm. ADO.NET for data access doesn't matter since data access is decoupled via repositories.

Do I add the same (and possibly more) properties to the service as I have on my model (amount, code, etc) or do I only offer methods that accept GiftCertificate objects and direct parameters?

You need to look at the services as exactly what their name implies - actions that controllers can invoke. You won't need properties that are defined in your model since they are already available in the model.

Do I create a default instance of the GiftCertificate entity when the Service constructor is called or just create new ones as needed (eg - for validation methods in the service that need to call validation methods in the entity? Also, same question about creating a default repository instance...?

Controllers and services should have private fields for services and repositories respectively. You shouldn't be instantiating for every action / method.

I know i expose the functionality of the repo via the service, do I also expose the methods from the entity as well (eg - IsValidCode, etc)?

Not too sure what you mean here. If services return entities then those methods on the entities are already exposed. If they return DTOs then that implies you're interested only in certain information.

For validation I can see why you're a bit concerned since there's validation done directly on the model and other types of validation done in services. The rule of thumb I've used is that if validation requires calls to the db then it should be done in the service layer.

It is ok for the UI to simply create a new GiftCertificate object directly without going through the service (eg - to call parameter validation methods from the entity). If not, how to enforce it?

On the UI layer, when I want to create a new gift certificate, do I call the model/service validations (like IsValidExpirationDate, etc) directly from the UI layer OR do I hydrate the object first, then pass it in to be validated and then return some sort of validation summary back to the UI?

For these 2 questions lets go through a scenario:

User enters information to create a new certificate and submits. There is field level validation so if a textbox is null or if the dollar amount is negative it throws a validation error. Assuming all fields are valid the controller will call the service gcService.Save(gc).

The service will check other business logic, such as if the store already has issued too many gift certificates. It either returns an enum for the status if there are multiple error codes or throws an exception with the error information.

Finally, the service calls gcRepository.Save(gc).

老街孤人 2024-11-17 10:54:55
  1. 您不必为每个实体创建存储库,请参阅此处了解更多信息,

    <块引用>

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

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

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

    服务创建

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

  5. UI->Service->Repository,如果存储库正在调用服务,则 thr 一定是错误的 IMO。


您编写更改代码,

  1. 将模型和存储库分开。

    公共类 GiftCertificateModel
    {
    }
    公共类 GiftCertificateRepository
    {
       //从这里删除与模型相关的代码,只在这里放置数据库特定的代码(也没有业务逻辑)。常见的方法有 Get、GetById、Insert、Update 等。 
    
        由于存储库的本质是在一处拥有通用的 CRUD 逻辑,因此您不必编写特定于实体的代码。 
        在极少数情况下,您还将通过派生基础存储库来创建特定于实体的存储库。
    
    }
    
    公共类 GiftCertificateService()
    {
        //在这里创建模型实例
        // 使用存储库来填充模型(Mapper)
    
    }
    
  1. You don't have to create repository per entity,see here for more,

    Usually one defines a repository per
    aggregate in the domain. That is: we
    don't have a repository per entity! If
    we have a look at a simple order entry
    system the entity Order might be the
    root of a Order aggregate. Thus we
    will have an Order Repository.

  2. Should there be one service per repository? -> Not always, as you may use multiple repositories in one service.

  3. Service creates Model instance, repository will never interact to Model, in fact it returns the Entity which model will use subsequently.

  4. Handle Input/range etc kind of validation at UI level(u can use javascript or any other library), and let Services handle only business aspects. You can get the benefits of Attributes which will does the same.

  5. UI->Service->Repository, if repository is calling service than thr must be something wrong IMO.


You code changes,

  1. Make Model and Repositories separate.

    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)
    
    }
    
彻夜缠绵 2024-11-17 10:54:55

您可以创建一个名为 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。

You can create a service called GiftCertificateService.

That way you will coordinate any task that does not belong to the responsibility of the GiftCertificateModel into it's service. (Not to be confused with a WCF service).

The service will control all the tasks so your UI (or whatever caller that might be) will use the methods defined in the service.

The service will then call methods on the model, use the repository, create transactions, etc.

For ex. (based on the sample code you provided):

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

The UI will call the CreateCertificate method (passing arguments, etc) and the method may return something also.

NOTE: If you want the class to act on the UI though then create a controller class (if you are doing MVC) or a presenter class (if you are doing MVVM, and don't want to put everything inside the ViewModel) and use the GiftCertificateService from that class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文