Servicestack - 架构&为所有事情重用 POCO [英] Servicestack - architecture & reusing POCOs for everything

查看:21
本文介绍了Servicestack - 架构&为所有事情重用 POCO的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我指的是

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

并行客户端开发

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

就规则而言,确保明确定义的服务合同 (DTO) 与其实施分离深入了解服务的本质及其提供的价值.

请求和响应 DTO

至于哪些 DTO 适合作为数据模型重用,您不想使用请求 DTO除了定义外部服务 API 之外的任何事情,通常是一个动词,理想情况下按调用语义和响应类型分组,例如:

公共类 SearchProducts : IReturn{公共字符串类别{获取;放;}公共小数点?PriceGreaterThan { 得到;放;}}

您的 RDBMS 表通常是定义为名词的实体,即您的服务返回的内容:

公共类 SearchProductsResponse{公开列表<产品>结果{得到;放;}公共 ResponseStatus ResponseStatus { 获取;放;}}

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

除了请求和响应 DTO 之外,您的服务返回的所有其他类型都是作为数据模型重用的候选对象,我经常这样做,将它们保留在 ServiceModel 项目中由于上述原因.

I refer to ServiceStack documentation reg use of POCOs:

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"

I love servicestack & how easy it is to write web services with it. I am trying to understand how best to setup my project & not run into any issues down the road.

Specifically, I am battling with the architectural idea of returning a response object that is also a data model (as suggested by SS). The idea of seperation of concerns is too strongly ingrained within me. Won't one run into problems down the road if you use the same POCOs for everything. Is it not "safer" to e.g return eg view objects instead?

解决方案

Software's biggest enemy

Firstly I want to iterate that Complexity and Large Code bases is the single worst enemy of software development, and that along with meeting the project requirements (i.e. deriving value from our software), managing complexity and maintaining a minimal and low friction, evolvable code-base should be at the forefront of our minds as we're continually enhancing our software with new features and requirements. Any guidelines, rules or processes we add to increase software quality should be directly focused on managing its essential complexity. One of the best things we can do to reduce complexity is to reduce code-base size, i.e. DRYing repeatable code and eliminating any unnecessary abstractions, indirection, concepts, types and friction that isn't absolutely essential to the software's function.

In this light, YAGNI is one of the best principles to follow to ensure a simple and lean code-base by focusing on what's essential to delivering value.

Avoid blanket rules

I avoid "blanket rules" which I consider one of the primary causes of unnecessary complexity in Software, where it's often liberally and thoughtlessly applied, infecting a code-base without justification. Every time you impose an artificial limitation, you're creating friction and inertia to develop within its bounds in order to satisfy it, which is why any rule you enforce should be thoughtfully and carefully applied and limited to places where it adds value.

Be wary of invalid Rules and Patterns

Even Software Design Patterns are in many cases programming language deficiencies, where what's a useful in one language is unnecessary and more elegantly solved in more expressive and powerful languages. Likewise with "rules", what's a cautionary guideline in one domain may not be applicable in others. Therefore what's more important than "the rule" itself, is the value it actually provides and what concrete side-effect it's trying to prevent. Once we understand its true value, we can optimize to derive maximum value from it and together with YAGNI, know when to selectively apply it.

The Simple POCO Life

As you've noticed ServiceStack achieves a lot of its simplicity and reuse by being able to reuse the same POCOs indiscriminately anywhere to interface and freely communicate between its different libraries and components. This enables maximum value and reuse of your Models and reduces the friction in mapping between different domains which typically require having purpose-specific types, each with its own unique configuration limiting its applicability and potential re-use.

Heavy ORM models are poor DTOs

Not reusing data models as DTOs applies to Heavy ORM's which encourage Data Models with cyclical dependencies and proxied objects with tight coupling and embedded logic that can trigger unintended N+1 data access, making these models poor candidates for use as DTOs and why you should always copy them into purpose-specific DTOs that your Services can return so they're serializable without issue.

Clean POCOs

The complex Data Models stored in OrmLite or Redis doesn't suffer from any of these issues which are able to use clean, disconnected POCOs. They're loosely-coupled, where only the "Shape" of the POCO is significant, i.e. moving projects and changing namespaces won't impact serialization, how it's stored in RDBMS tables, Redis data structures, Caching providers, etc. You're also not coupled to specific types, you can use a different type to insert data in OrmLite than what you use to read from it, nor does it need to be the "exact Shape", as OrmLite can populate a DTO with only a subset of the fields available in the underlying table. There's also no distinction between Table, View or Stored procedure, OrmLite will happily map any result-set into any matching fields on the specified POCO, ignoring others.

Effectively this means POCOs in ServiceStack are extremely resilient and interoperable, so you can happily re-use the same DTOs in OrmLite and vice-versa without issue. If the DTO and Data models only deviate slightly, you can hide them from being serialized or stored in OrmLite with the attributes below:

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

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

Otherwise when you need to separate them, e.g. more fields were added to the RDBMS table than you want to return, the DTO includes additional fields populated from alternative sources, or you just want your Services to project them differently. At that point (YAGNI) you can take a copy of the DTO and add it to your Services Implementation so they can grow separately, unimpeded by their different concerns. You can then effortlessly convert between them using
ServiceStack's built-in Auto Mapping, e.g:

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.

Data Transfer Objects - DTOs

So if you're using clean, serializable POCOs without external dependencies (e.g. from OrmLite, Redis or alt ServiceStack sources) you can happily re-use them as DTOs and freely refactor them out into different models as-and-when you need to. But when you are re-using Data Models as DTOs they should still be maintained in the ServiceModel project (aka DTO .dll) which should contain all the types that your Service returns. DTOs should be logic and dependency-free so the only dependency the ServiceModel project references is the impl-free ServiceStack.Interfaces.dll which as it's a PCL .dll, can be freely referenced from all .NET Mobile and Desktop platforms.

You want to ensure all types your Services return are in the DTO .dll since this, along with the base url of where your Services are hosted is all that's required for your Service Consumers to know in order to consume your Services. Which they can use with any of the .NET Service Clients to get an end-to-end Typed API without code-gen, tooling or any other artificial machinery. If clients prefer source code instead, they can use Add ServiceStack Reference to access the Servers typed DTOs in their preferred platform and language of choice.

Services

Services are the ultimate form of encapsulating complexity and offers the highest level of software reuse. They package its capabilities and makes them available remotely to your consumers with never any more complexity than the cost of a Service call.

The DTOs are what defines your Services contract, keeping them isolated from any Server implementation is how your Service is able to encapsulate its capabilities (which can be of unbounded complexity) and make them available behind a remote facade. It separates what your Service provides from the complexity in how it realizes it. It defines the API for your Service and tells Service Consumers the minimum info they need to know to discover what functionality your Services provide and how to consume them (maintaining a similar role to Header files in C/C++ source code). Well-defined Service contracts decoupled from implementation, enforces interoperability ensuring that your Services don't mandate specific client implementations, ensuring they can be consumed by any HTTP Client on any platform. DTOs also define the shape and structure of your Services wire-format, ensuring they can be cleanly deserialized into native data structures, eliminating the effort in manually parsing Service Responses.

Parallel Client development

Since they capture the entire contract it also enables clients to develop their applications before the Services are implemented as they're able to bind their application to its concrete DTO models and can easily mock their Service client to return test data until the back-end Services are implemented.

As far as rules go, ensuring a well-defined Service Contract (DTOs) decoupled from its implementation goes to very essence of what a Service is and the value it provides.

Request and Response DTOs

As for which DTOs make good candidates for re-use as Data Models, you don't want to use Request DTOs for anything other than defining your external Services API which is typically a Verb that's ideally grouped by Call Semantics and Response Types, e.g:

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

Your RDBMS tables are normally entities defined as Nouns, i.e. what your Service returns:

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

Even the containing Response DTO which defines what your Service returns isn't a good candidate for re-use as a Data Model. I'd typically use discrete DTOs for Service Responses as it allows freely extending existing Services to return extra data or metadata without breaking existing clients.

Other than the Request and Response DTOs, all other the Types that your Service returns would be candidates for re-use as Data Models, which I frequently do, keeping them in the ServiceModel project for the reasons above.

这篇关于Servicestack - 架构&amp;为所有事情重用 POCO的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆