ASP.NET Web API 破坏存储库/服务模式

本文关键字:服务 模式 存储 NET Web API ASP | 更新日期: 2023-09-27 18:33:14

我是构建 Web API 的新手,并且已经达到了可以使用一些方向的地步。例如,假设我有一个Product模型和一个Attachment模型,如下所示:

 public class Product
 {
     public Product()
     {
        Attachments = new List<Attachment>();
     }
     public int ID { get; set; }
     public string Name { get; set; }
     public List<Attachment> Attachments { get; set; }
 }
 public class Attachment
 {
     public int ID { get; set; }
     public string Name { get; set; }
 }

请注意,Product模型具有类型 Attachment 的属性列表。当我初始化 ProductRepository 类中的 Property 对象时,利用 AttachmentRepository 填充此属性是否不好?

所以在ProductRepository我会做这样的事情:

Product product = new Product
{
    ID = SomeId,
    Name = SomeName
    Attachments = AttachmentRepository.GetAttachments(SomeName)
};

这是糟糕的设计,我正在错误地接近它吗?我不打算为Attachment创建一个控制器,因为如果没有Product上下文,就没有获取附件的用例。

ASP.NET Web API 破坏存储库/服务模式

"我不打算为附件创建控制器,因为如果没有产品的上下文,就没有获取附件的用例。

反过来呢?您是否想获得没有附件的产品?

我发现尝试做太多事情的方法不那么可重用,因为如果我获得大量产品并且对附件不感兴趣,我不希望同时获得所有这些产品的附件的开销。 然后我后悔设计(无论是我的还是同事的(,添加一种新方法只是为了获得产品。

在我的控制器中,我会调用 GetProducts,然后调用 GetAttachments。 这将允许您根据需要混合匹配这些。

另一种方法是在产品存储库中执行此操作,有一个只获取产品(GetProducts(的方法,以及另一个调用GetProducts并调用GetAttachments的GetProductsWithAttachments方法。 或者你当然可以有一个布尔参数来, includeAttachments = false)。 我不喜欢我的数据库层变得那么混乱。 我让控制器成为将所有内容整合在一起的地方。

我甚至可能没有产品模型中的附件属性,而是具有特定于控制器的产品响应模型,这是视图模型的类比。 它将是同时具有产品信息和附件属性的那个,您将从调用两个单独的存储库方法的结果中填充它。

如果您在数据库层

执行所有操作,并且数据库层中的产品模型具有仅在调用 GetProductsWithAttachments 时填充的附件属性,则您的产品模型将填充一半随着时间的推移,添加许多其他类似的属性,你会得到一个非常混乱的模型,因为有时只有它的一些属性是根据你调用的存储库的方法填充的。开发人员必须开始深入研究 repo 方法,以确定填充了什么。 如果您向产品添加促销,您是否拥有GetProductWithAttachments,GetProducts,GetProductWithPromotions,GetProductWithAttachments和Promotions? 您可以开始看到这是如何失控的,如果您真的想将其烘焙到存储库中,一些默认参数会更好。 然而:

推荐:控制器中的合成

这就是为什么我更喜欢让控制器编排整个事情。在控制器中单独调用存储库方法。 不确定您使用的是 REST 样式还是 Web API 控制器中的样式,因此只需考虑以下伪代码:

public class ProductController: ApiController
{
  public ProductResponseModel Get(int productId) 
  {
     var model = new ProductResponseModel{
        Product = ProductRepository.Get(productId);
     };
     model.Attachments = AttachmentRepository.GetList(model.Product.Name);
     // I could have flattened out the Product into its properties instead of having a model.Product,
     // but that can be a maintenance problem and requires something like AutoMapper to manage well
     return model;
  }
}
public class ProductResponseModel {
  public Product Product {get;set;}
  public IEnumerable<Attachment> Attachments {get;set;}
}

ProductResponseModel 是组合的一个例子。 它创建了与数据库层的松散耦合,因此您可以自由混合和匹配,即为每个 API 控制器所需的数据组合模型。 ProductResponseModel 本身不是很可重用,也许只在该控制器中。 另一个需要不同产品数据组合的控制器将拥有自己的 SomethingResponseModel,并调用单独的存储库方法来填充它。 即使我们不能重用我们的 *响应模型,这也不是一个很大的损失,因为它们是简单的 POCO。 从我们的 repo 方法中重用更为重要,并且避免它们变得比需要的更复杂。 此外,我们仍然有你的简单模型,例如ProductAttachment,它们是存储库层的一部分,因此调用存储库的每个人都使用相同的语言并使用相同的通用类型。 因此,不要错误地让存储库层返回 ProductResponseModel。 控制器的工作是获取Product并填充ProductResponseModel

我使用命名约定 *ResponseModel,因为如果我也有操作方法的复杂参数,那么还有一个 *RequestModel。 即请求/响应。 如果我有一个比 REST 风格更 RPC 风格的 API 方法,例如 ProductController.DiscontinueDistribution,它的返回可能非常专业,所以我会有一个 ProductDiscontinueDistributionResponseModel。 尽管如此,这些都是简单的POCO,就像MVC中的ViewModels一样。

如果您不使用EF

或其他ORM,那么您的Product对象可能不应该包含Attachment对象的列表 - 这应该是某种形式的DTO的构造(就像@AaronLS所说的那样(类似于MVC中的视图模型。

像这样:

public class DTOProductAttachment
{
    Product Product { get; set; }
    List<Attachment> Attachments { get; set; }
    public DTOProductAttachment(int id, string name)  // <-- Product ID, Product Name
    { 
        Product = ProductRepository.GetProduct(id); // <-- Product ID
        Attachments = AttachmentRepository.GetAttachmentsByProductName(name); // <-- Product Name, or ID or whatever joins your tables
    }; 
}

。我认为这里的重要性在于,你的"对象"ProductAttachment应该反映你的数据库设计。您的存储库应以直观的架构方式运行。

例如,如果Name(产品名称(是Attachments表上的外键,则上面的AttachmentsRepository.GetAttachmentsByProductName()方法是完全直观的(可能应该 Product.ID 但我正在使用上面的示例运行。

但是,这些决策与您的设计要求/目标有关。例如,如果一个Attachment对象与另一个对象(假设AttachmentChild(具有一对多关系,那么可以合理地假设您可能希望按Product名称或 ID 选择所有AttachmentChild对象。

。在这种情况下,添加像AttachmentChildRepository.GetAttachmentsByProductName()这样的方法似乎有点可疑。

为了显著性,您可以在AttachmentChild对象上包含Product名称(或 ID(,即使这在技术上不是必需的。

我希望这有所帮助。