ServiceStack 请求和响应对象

3

使用POCO作为请求和响应DTO是否可行(良好实践)?我们的POCO很轻量级(ORM Lite),只有属性和一些装饰属性。

或者,我应该为请求和/或响应创建其他对象吗?

谢谢。

4个回答

8
我认为这取决于你的系统,以及它现在有多大和复杂,以及可能变得有多么复杂。
ServiceStack文档没有指定您应该使用哪种设计模式。最终,它提供了将数据库模型POCO与DTO分离的灵活性,但同时也支持对它们进行重复使用。
使用OrmLite时:
OrmLite的设计目的是让您可以重复使用数据模型POCO作为请求和响应DTO。正如ServiceStack文档中所述,这是框架的一个故意设计目标:
“Micro ORMS中使用的POCO非常适合重复用作DTO,因为它们不包含任何 Heavy ORMs(例如EF)具有的循环引用。OrmLite更进一步,从NoSQL的策略中借鉴页面,其中任何复杂属性(例如List)都会在无模式文本字段中透明地成为Blob,促进了无摩擦纯POCOS的设计,这些纯POCOS未受到RDBMS问题的限制。”
考虑:
如果您选择重用POCO,因为它是受支持的,您应该知道,在某些情况下,使用单独的请求和响应DTO会更明智。在许多情况下,这些POCO数据模型已经成为良好的DTO,并且可以直接返回,而不是映射到特定于域的DTO。
并非所有情况都适用。有时选择设计模式的难点在于预见可能不适合重用的情况。希望通过一个场景来说明潜在问题。
场景: - 用户可以注册您的服务。 - 作为管理员,您有能力列出您的服务的用户。
如果您采用OrmLite POCO重用方法,则可能拥有此User POCO。
public class User
{
    [PrimaryKey, AutoIncrement, Alias("Id")]
    public int UserId { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public string Salt { get; set; }
    public bool Enabled { get; set; }
}

当您发起创建用户请求时,需要将UsernamePassword填入您的User POCO中并发送至服务器。
我们不能直接将此POCO推送到数据库中,因为:
  • Password字段中的密码将是明文。我们是优秀的程序员,安全性很重要,因此我们需要创建一个盐,将其添加到Salt属性中,并使用盐散列Password并更新Password字段。好的,这不是一个大问题,在插入之前几行代码就可以解决。

  • 客户端可能已经设置了UserId,但对于创建操作,这不是必需的,会导致数据库查询失败。因此,在插入数据库之前,我们必须将此值设置为默认值。

  • 请求中可能传递了Enabled属性。如果有人设置了这个属性,那怎么办?我们只想处理UsernamePassword,但现在我们必须考虑其他会影响数据库插入的字段。同样,他们可能会设置Salt (尽管这不是问题,因为我们无论如何都会覆盖该值。)因此,现在你需要添加验证。

但是现在考虑当我们返回一个List<User>时。

如果您将POCO作为响应类型进行重用,则有许多字段不希望向客户端公开。这样做并不明智:
return Db.Select<User>();

由于您没有为列出用户构建的紧密目的响应,所以在逻辑中需要删除Password哈希和Salt,以防止它被序列化输出到响应中。
还要考虑到,在注册用户时,作为创建请求的一部分,我们想询问是否应该发送欢迎电子邮件。因此,我们将更新POCO:
public class User
{
    // Properties as before
    ...
    [Ignore] // This isn't a database field
    public bool SendWelcomeEmail { get; set; }
}

我们现在拥有一个仅在用户创建过程中有用的附加属性。如果您一遍又一遍地使用 User POCO,您会发现随着时间的推移,您正在添加越来越多不适用于某些上下文的属性。
例如,当我们返回用户列表时,现在可能会填充一个可选的 SendWelcomeEmail 属性,但这毫无意义。这样就很难维护代码了。
需要记住的关键是:当共享一个 POCO 对象,使其既用作请求对象又用作响应对象时:您发送为响应的属性将在请求中公开。 您将不得不对请求进行更多的验证,最终共享 POCO 可能并不节省工作量。
在这种情况下,做以下操作是否更容易:
public class CreateUserRequest
{
    public string Username { get; set; }
    public string Password { get; set; }
    public bool SendWelcomeEmail { get; set; }
}

public class UserResponse
{
    public int UserId { get; set; }
    public string Username { get; set; }
    public bool Enabled { get; set; }
}

public class User
{
    [PrimaryKey, AutoIncrement, Alias("Id")]
    public int UserId { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public string Salt { get; set; }
    public bool Enabled { get; set; }
}

我们现在知道,当创建一个请求(CreateUserRequest)时,我们不必考虑UserIdSaltEnabled
返回用户列表时,现在是List<UserResponse>,客户端不会看到我们不想让他们看到的任何属性。
对于查看代码的其他人来说,请求所需的属性和响应中将公开的内容是清晰明确的。
总结:
抱歉,这是一个非常长的答案,但我认为它解决了一些人忽略或最初无法掌握的共享POCOs的方面问题,我曾经也是其中之一。
  • 是的,您可以重复使用POCO来进行请求和响应。
  • 文档说这样做是可以的。事实上,这是按设计来的。
  • 在许多情况下,重用是可以的。
  • 有些情况不适合这样做。(我的场景试图展示这一点,但您将在开发自己的实际情况时发现。)
  • 请考虑由于您共享的POCO尝试支持多个操作而可能公开的许多其他属性,以及可能需要的额外验证工作量。
  • 最终取决于您感到舒适的维护方式。

希望这能帮到您。


1
感谢您抽出时间。我完全同意并已经使用响应、请求DTO进行了实现。我还在考虑将接口抽象出来,以便外部客户端可以使用协议缓冲区,并且他们只需要使用这些接口即可。 - click2install
@click2install 不客气,很高兴它有用。如果您有许多客户使用您的Api,那听起来是个好计划。 - Scott
1
我通过吃亏的方式学习了所有这些教训。经历了使用自己的 API。我真心不推荐使用 POCO 作为 DTO。你永远记不住操作所需的内容。 - uriDium

3
我们有不同的方法,我的答案是有主见的。因为我们不仅与C#客户端合作,而且主要与JavaScript客户端合作。请求和响应DTO、路由和数据实体是由客户和前端分析师协商确定的,并以详细形式作为规范的一部分。即使在某些情况下,“客户”是我们的产品UI。这些DTO没有重要原因不会改变,并且可以在两个方向上重复使用。但是,在数据层中的对象可以是相同的、部分类或不同的。它们可以在内部更改,包括敏感或工作流信息,但必须与API的规范兼容。我们首先从API开始,而不是数据库或ORM。
           Person  { ... }

           Address  { ... }

           ResponceDTO
           { 
             public bool success {get; set;}
             public string message {get; set;}
             public Person  person {get; set;}  
             public List<Address> addresses {get; set;}  
    //customer can use the Person & Address, which can be the same or different
   //with the ones in the data-layer. But we have defined these POCO's  in the specs.                           
            }
           RequestDTO
           { 
             public int Id {get; set;}
             public FilteByAge {get; set;}
            public FilteByZipCode {get; set;}
            }               
           UpdatePersonRequest
           { 
             public int Id {get; set;}
             public bool IsNew {get; set;}
             public Person  person {get; set;} 
             public List<Address> addresses {get; set;}   
            }                  

我们不仅向外界暴露 Request 或 Response DTOs。

Person 和 Address 是与客户协商的,并在 API 规范中引用它们。

它们可以与数据层内部实现相同、部分相同或者不同。

客户将在他们的应用程序、网站或移动设备中使用它们。

但重要的是,我们首先设计和协商 API 接口。

我们通常将 requestDTO 作为参数传递给业务层函数,

该函数返回响应对象或集合。

这样,服务代码就成为了业务层前面的一个薄包装器。

        ResponseDTO  Get(RequestDTO  request)
         {
                      return GetPersonData(request);
          }

此外,来自ServiceStack wiki的API-First development方法也提到了。

1
如果您可以公开数据对象的结构(如果这是一个公共消费的API),那么这不会成为问题。否则,Restsharp是用于与简单POCO一起使用的 :)

我同意,这是我的担忧,因为它将成为公共API。我考虑使用Request对象、Response对象(可能继承请求以返回发送请求时的属性)和数据传输对象(DTO)。这样我就可以更改数据库,重新生成DTO而不影响服务接口。 - click2install

1
我认为这完全取决于您如何使用DTO以及如何平衡代码的可重用性和可读性。如果您的请求和响应都使用大量DTO属性,那么您将获得很多可重用性,而不会降低可读性。例如,如果您的请求对象有10个属性(反之亦然),但是您的响应仅需要其中的1个,则某些人可能会认为,如果您的响应对象只有该1个属性,则更易于理解/阅读。
总之,良好的实践就是清晰的代码。您必须评估您特定的用例,以确定您的代码是否易于使用和阅读。另一种思考方式是,编写下一个将阅读代码的人的代码,即使该人是您自己。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接