【问题标题】:CRUD and Query with ServiceStack - Need to get rid of some confusion使用 ServiceStack 进行 CRUD 和查询 - 需要摆脱一些混乱
【发布时间】:2016-09-29 10:24:29
【问题描述】:

我对 ServiceStack 的“旧”和“新”API 有点困惑,需要一些说明和最佳实践,尤其是请求/响应 DTO 和路由。我在 Pluralsight 上观看了一些课程,并在我的电子书架上列出了 servicestack.net 上的前三本书。

我喜欢对使用 DDD 模式 构建的现有应用程序进行“重构”,这意味着我具有高级别的抽象。客户端是 WPF,遵循 MVVM 模式。我有“客户端服务”、“服务器端服务”和存储库类(还有一些聚合)。我使用 NHibernate 4(具有流畅的 API 和代码优先方法)作为 ORM。只有我的存储库类知道 ORM。我的所有实体对象都有 DTO,在我的 WPF 客户端中,我只使用 ViewModel 类中的那些 DTO。我大量使用 AutoMapper 将实体对象“传输”到我的 DTO,反之亦然。

我的困惑正是从这些 DTO 和 ServiceStack 中使用的请求/响应 DTO 开始的。这是一个非常简化的地址实体示例,它说明了问题:

我所有的实体对象都派生自 EntityBase,它包含所有实体中使用的基本属性:

public abstract class EntityBase : IEntity
{
    public virtual Guid Id { get; protected set; }
    public virtual DateTime CDate { get; set; } //creation date
    public virtual string CUser { get; set; }  //creation user
    public virtual DateTime MDate { get; set; }  //last modification date
    public virtual string MUser { get; set; } //last modification user
    //
    // some operators and helper methods irrelevant for the question
    // ....
}

public class Address : EntityBase
{
    public string Street { get; private set; } 
    public string AdrInfo1 { get; private set; }
    public string AdrInfo2 { get; private set; }
    public string ZipCode { get; private set; }
    public string City { get; private set; }
    public string Country { get; private set; }
}

当然,这里忽略了相关对象的集合和引用以及数据库映射器、命名约定等。我拥有的 DTO 如下所示:

public class AddressDto
{
    public Guid Id { get; set; }  // NHibernate GUID.comb, NO autoincrement ints!!
    public DateTime CDate { get; set; }
    public string CUser { get; set; }
    public DateTime MDate { get; set; }
    public string MUser { get; set; }
    public string Street { get; private set; } 
    public string AdrInfo1 { get; private set; }
    public string AdrInfo2 { get; private set; }
    public string ZipCode { get; private set; }
    public string City { get; private set; }
    public string Country { get; private set; }
}

要将它与 ServiceStack 一起使用,我需要支持以下内容:

  1. CRUD 功能
  2. 过滤/搜索功能

所以我的“地址服务”应该有以下方法:

  • GetAddresses(ALL、ById、ByZip、ByCountry、ByCity)
  • AddAddress(没有 Id.CDate、CUser 的完整 AddressDTO 会自动填写,无需用户输入)
  • UpdateAddress(无需用户输入即可自动填写无 CUser 和 CDate、MDate 和 MUser 的完整 AddressDTO)
  • 删除地址(仅 ID)

对我来说很清楚,所有请求都返回单个 AddressDtoList<AddressDto> 作为 ResponseDTO,但删除应该只返回一个状态对象。

但是如何定义所有的 RequestDTO 呢?我真的必须为每个场景定义一个 DTO 吗?? 在书中我只看到了以下示例:

[Route("/addresses", "GET")]
public class GetAddresses : IReturn<AddressesResponse> { }

[Route("/addresses/{Id}", "GET")]
public class GetAddressById : IReturn<AddressResponse>
{
    public Guid Id { get; set; }
}

[Route("/addresses/{City}", "GET")]
public class GetAddressByCity : IReturn<AddressResponse>
{
    public string City { get; set; }
}

// .... etc.

这是很多样板代码,让我想起了我在 C++ 和 CORBA 中使用的很多旧 IDL 编译器.....

特别是对于创建和更新,我应该能够“共享”一个 DTO,甚至更好地重用我现有的 DTO...对于删除,可能没有太多选择......

然后是过滤器。我还有其他具有更多属性的 DTO。像在 WCF、RPC 等中使用的函数方法是地狱编码...... 在我的存储库中,我传递了一个完整的 DTO 并使用了一个谓词构建器类,该类根据填充的属性组成 LINQ where 子句。这看起来像这样:

List<AddressDto> addresses;

Expression<Func<Address, bool>> filter = PredicateBuilder.True<Address>();
if (!string.IsNullOrEmpty(address.Zip))
    filter = filter.And(s => s.Zip == address.Zip);
// .... etc check all properties and dynamically build the filter

addresses = NhSession.Query<Address>()
                .Where(filter)
                .Select(a => new AddressDto
                {
                    Id = a.Id,
                    CDate = a.CDate,
                    //.... etc
                }).ToList();

我可以用我的 RequestDTO 做任何类似的事情吗?应该如何定义路由?

【问题讨论】:

    标签: servicestack


    【解决方案1】:

    这里提出的许多问题已在下面现有的链接答案中得到解决。请求/响应 DTO 是您对 define your Service Contract 使用的,即您的服务接受(请求 DTO)并返回(响应 DTO)的 using RPC method signatures, you define your contract with messages。前面的示例也遍历了guidelines on designing HTTP APIs with ServicesStack

    well-defined DTOs have a very important role in Services的使用:

    您希望确保您的服务返回的所有类型都在 DTO 中,因为这与您的服务托管位置的基本 URL 一起是所有需要让您的服务消费者知道才能使用您的服务。他们可以将其与任何 .NET 服务客户端一起使用,以无需代码生成、工具或任何其他人工机制即可获得端到端的类型化 API。

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

    自动查询服务

    如果您正在执行大量数据驱动的服务,我建议您查看 AutoQuery,它可以让您定义完全可查询的服务,而无需使用您的服务请求 DTO 定义来实现。

    【讨论】:

    • 感谢 Demis,AutoQuery 看起来很有趣。我认为它可以帮助我很多。我读了所有这些文章。问题是,我无法改变我已经拥有的一切。我正在评估 Dapper,但 NHibernate 有许多 MicroORM 都没有提供的东西,并且性能比 MS 的 EF 好得多。 GUID 只是一个示例,如果您曾经合并两个使用自动增量整数作为 PK 的数据库,您就会知道为什么不应该这样做!但我会找到一个适合我设计“好的”RequestDTO 需求的解决方案。
    • @ThommyB 是的,Micro ORM 也比 NHibernate 快。 Guid 对于手动同步断开连接的数据集很有用,但是您需要 NHibernate 提供哪些 Guid 功能?例如OrmLite 让您可以像使用任何数据类型一样使用 Guid 作为 PK。
    • Guid 是在 DB 上创建的(如果 DB 支持的话),因此您将 Id 初始化为 Guid.Empty 并从 DB 中取回生成的 ID。它也有Guid.comb。我还大量使用了ConventionsEvents,它们可以让你实现类似触发器的东西。我也喜欢 NHibernate 的可控事务处理(与 EF 相反)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多