Servicestack - 架构和重用 POCOs 的一切

Servicestack - architecture & reusing POCOs for everything

我参考 ServiceStack documentation POCO 的常规使用:

Since it promotes clean, re-usable code, ServiceStack has always encouraged the use of code-first POCO's for just about everything.

i.e. the same POCO can be used:
In Request and Response DTO's (on client and server)
In JSON, JSV and CSV Text Serializers
As the data model in OrmLite, db4o and NHibernate
As the entities stored in Redis
As blobs stored in Caches and Sessions
Dropped and executed in MQ's services"

我喜欢 servicestack 以及用它编写 Web 服务是多么容易。我正在尝试了解如何最好地设置我的项目,而不是 运行 遇到任何问题。

具体来说,我正在与 returning 响应对象的体系结构思想作斗争,该响应对象也是数据模型(如 SS 所建议的)。关注点分离的想法在我心中根深蒂固。如果您对所有内容都使用相同的 POCO,那么 运行 不会在以后遇到问题。它不是“更安全”例如 return 例如查看对象吗?

软件最大的敌人

首先我想重复一下 Complexity and Large Code bases 是软件开发的最大敌人,并且满足项目要求 (即从我们的软件中获取价值),管理复杂性并保持最小和低摩擦, 随着我们不断增强我们的软件,可演进的代码库应该是我们的首要考虑 新功能和要求。我们为提高软件质量而添加的任何指南、规则或流程都应该 直接专注于管理其基本的复杂性。我们可以做的最好的事情之一就是降低复杂性 是为了减少代码库的大小,即 DRYing repeatable 代码并消除任何不必要的抽象, 对软件功能而言并非绝对必要的间接、概念、类型和摩擦。

从这个角度来看,YAGNI 是最好的原则之一 通过专注于交付价值所必需的内容来确保简单而精简的代码库。

避免一揽子规则

我避免 "blanket rules",我认为这是软件不必要的复杂性的主要原因之一, 它经常被随意和轻率地应用,没有正当理由感染代码库。 每次你强加一个人为的限制,你就在制造摩擦和惯性以在其范围内发展 为了满足它,这就是为什么你执行的任何规则都应该经过深思熟虑和仔细应用 并仅限于增加价值的地方。

警惕无效的规则和模式

甚至软件设计模式在很多情况下也是 programming language deficiencies,其中 在一种语言中有用的东西是不必要的 用更具表现力和更强大的语言更优雅地解决。同样 "rules",什么是 一个领域的警示指南可能不适用于其他领域。所以比什么更重要 "the rule" 本身,是它实际提供的价值以及它试图防止的具体副作用。 一旦我们了解了它的真正价值,我们就可以优化以从中获得最大价值,并与 YAGNI 一起, 知道何时有选择地应用它。

简单的POCO生活

正如您所注意到的,ServiceStack 通过重用相同的服务实现了很多简单性和重用性 POCO 可以在任何地方不分青红皂白地在其不同的库和组件之间进行接口和自由通信。 这可以实现模型的最大价值和重用,并减少不同域之间映射的摩擦 这通常需要具有特定用途的类型,每个类型都有自己独特的配置限制其 适用性和潜在的重复使用。

繁重的 ORM 模型是糟糕的 DTO

不重用数据模型,因为 DTO 适用于重型 ORM,它鼓励具有循环依赖性的数据模型 以及具有紧密耦合和嵌入式逻辑的代理对象,可以触发意外的 N+1 数据访问,使得 这些模型不适合用作 DTO,以及为什么您应该始终将它们复制到特定目的 您的服务可以 return 的 DTO,因此它们可以毫无问题地序列化。

清理 POCO

复杂的数据模型存储在 OrmLiteRedis 没有遇到任何这些问题,可以使用 干净、断开连接的 POCO。它们是松散耦合的,其中只有 POCO 的 "Shape" 是重要的,即 移动项目和更改命名空间不会影响序列化,它在 RDBMS tables 中的存储方式, Redis 数据结构、缓存提供程序等。 你也没有耦合到特定类型,你可以使用不同的类型在 OrmLite 中插入数据而不是你 用于读取它,也不需要是 "exact Shape",因为 OrmLite 可以只用一个填充 DTO 基础 table 中可用字段的子集。 Table、View 或 存储过程,OrmLite 会愉快地将任何结果集映射到指定 POCO 上的任何匹配字段, 忽略别人。

实际上这意味着 ServiceStack 中的 POCO 具有极强的弹性和互操作性,因此您可以愉快地 在 OrmLite 中重复使用相同的 DTO,反之亦然。如果 DTO 和数据模型只是略有偏差, 您可以 hide them from being serialized 或存储在 OrmLite 中 以下属性:

public class Poco
{
    [Ignore]
    public int IgnoreInOrmLite { get; set; }

    [IgnoreDataMember]
    public int IgnoreInSerialization { get; set; }
}

否则当你需要将它们分开时,例如添加到 RDBMS table 的字段比您想要的多 return,DTO 包括从其他来源填充​​的其他字段,或者您只需要您的服务 以不同的方式投射它们。那时(YAGNI)您可以获取 DTO 的副本并将其添加到您的服务中 实施,以便他们可以独立成长,不受他们不同关注点的阻碍。 然后,您可以使用
毫不费力地在它们之间进行转换 ServiceStack's built-in Auto Mapping,例如:

var dto = dbPoco.ConvertTo<Poco>();

The built-in Auto Mapping is also very tolerant and can co-erce properties with different types, e.g. to/from strings, different collection types, etc.

数据传输对象 - DTO

因此,如果您使用没有外部依赖项的干净、可序列化的 POCO(例如来自 OrmLite、Redis 或 alt ServiceStack 源)你可以愉快地将它们重新用作 DTO,并自由地将它们重构为不同的模型 当你需要的时候。但是,当您将数据模型重新用作 DTO 时,它们仍应保留在 ServiceModel 项目(又名 DTO .dll)应包含 所有类型 您的服务 return。 DTO 应该是无逻辑且无依赖性的,因此 ServiceModel 项目引用的唯一依赖项是 impl-free ServiceStack.Interfaces.dll 因为它是一个 PCL .dll,可以从所有 .NET Mobile and Desktop platforms.

您要确保所有类型的服务 return 都在 DTO .dll 中,因为此,连同基础 url 您的服务的托管位置是您的服务消费者需要知道的所有信息,以便 使用您的服务。他们可以将其与任何 .NET Service Clients 在没有代码生成、工具或任何其他人工机器的情况下获得端到端的 Typed API。 如果客户更喜欢源代码,他们可以使用 Add ServiceStack Reference 以他们首选的平台和选择的语言访问服务器类型的 DTO。

服务

服务是封装复杂性的最终形式,并提供最高级别的软件重用。 他们将其功能打包并让您的消费者远程使用它们,再也不用 比调用服务的成本还要复杂。

DTO 定义了您的服务合同,将它们与任何服务器实现隔离开来 您的服务如何能够封装其功能(可能具有无限的复杂性)并使它们 在远程外观后面可用。它将您的服务提供的内容与它如何提供的复杂性分开 意识到它。它为您的服务定义 API 并告诉服务消费者他们需要知道的最少信息 发现您的服务提供什么功能以及如何使用它们 (在 C/C++ 源代码中保持与头文件类似的作用)。 定义明确的服务合同与实施分离,强制执行互操作性确保 您的服务不要求特定的客户端实现,确保它们可以被任何 HTTP 使用 任何平台上的客户端。 DTO 还定义了服务线格式的形状和结构,确保它们可以 被干净地反序列化为本机数据结构,消除了手动解析服务响应的工作。

并行客户端开发

由于他们捕获了整个合同,因此它还使客户能够在 服务得以实现,因为它们能够将应用程序绑定到其具体的 DTO 模型,并且可以轻松地 模拟他们的服务客户端 return 测试数据,直到后端服务实现。

就规则而言,确保定义明确的服务合同 (DTO) 与其实施脱钩 深入了解什么是服务及其提供的价值。

请求和响应 DTO

至于哪些 DTO 适合重新用作数据模型,您不想使用 请求 DTO 对于除了定义外部服务 API 之外的任何其他内容,它通常是一个 Verb ,这是理想的 grouped by Call Semantics and Response Types,例如:

public class SearchProducts : IReturn<SearchProductsResponse> 
{
    public string Category { get; set; }
    public decimal? PriceGreaterThan { get; set; }
}

您的 RDBMS tables 通常是定义为 Nouns 的实体,即您的服务 returns:

public class SearchProductsResponse
{
    public List<Product> Results { get; set; }        
    public ResponseStatus ResponseStatus { get; set; }
}

即使是包含 Response DTO 的定义您的服务 returns 的内容也不适合重用 作为数据模型。我通常会使用离散的 DTO 进行服务响应,因为它允许自由扩展现有的 在不破坏现有客户端的情况下为 return 额外数据或元数据提供服务。

除了请求和响应 DTO,您的服务 return 将是的所有其他 类型 作为数据模型重用的候选人,我经常这样做,将它们保存在 ServiceModel 项目中 由于上述原因。