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
上下文,就没有获取附件的用例。
"我不打算为附件创建控制器,因为如果没有产品的上下文,就没有获取附件的用例。
反过来呢?您是否想获得没有附件的产品?
我发现尝试做太多事情的方法不那么可重用,因为如果我获得大量产品并且对附件不感兴趣,我不希望同时获得所有这些产品的附件的开销。 然后我后悔设计(无论是我的还是同事的(,添加一种新方法只是为了获得产品。
在我的控制器中,我会调用 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 方法中重用更为重要,并且避免它们变得比需要的更复杂。 此外,我们仍然有你的简单模型,例如Product
和Attachment
,它们是存储库层的一部分,因此调用存储库的每个人都使用相同的语言并使用相同的通用类型。 因此,不要错误地让存储库层返回 ProductResponseModel。 控制器的工作是获取Product
并填充ProductResponseModel
。
我使用命名约定 *ResponseModel,因为如果我也有操作方法的复杂参数,那么还有一个 *RequestModel。 即请求/响应。 如果我有一个比 REST 风格更 RPC 风格的 API 方法,例如 ProductController.DiscontinueDistribution,它的返回可能非常专业,所以我会有一个 ProductDiscontinueDistributionResponseModel。 尽管如此,这些都是简单的POCO,就像MVC中的ViewModels一样。
或其他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
};
}
。我认为这里的重要性在于,你的"对象"Product
和Attachment
应该反映你的数据库设计。您的存储库应以直观的架构方式运行。
例如,如果Name
(产品名称(是Attachments
表上的外键,则上面的AttachmentsRepository.GetAttachmentsByProductName()
方法是完全直观的(可能应该 Product.ID 但我正在使用上面的示例运行。
但是,这些决策与您的设计要求/目标有关。例如,如果一个Attachment
对象与另一个对象(假设AttachmentChild
(具有一对多关系,那么可以合理地假设您可能希望按Product
名称或 ID 选择所有AttachmentChild
对象。
。在这种情况下,添加像AttachmentChildRepository.GetAttachmentsByProductName()
这样的方法似乎有点可疑。
为了显著性,您可以在AttachmentChild
对象上包含Product
名称(或 ID(,即使这在技术上不是必需的。
我希望这有所帮助。