ServiceStack请求DTO设计(ServiceStack Request DTO desig

2019-08-21 07:49发布

我用来开发微软技术的Web应用程序.NET开发。 我想教育自己,以了解Web服务REST方法。 到目前为止,我很热爱ServiceStack框架。

但有时我发现自己在我习惯与WCF的方式来写服务。 所以我有一个问题,它的错误我。

我有2个要求DTO是如此喜欢这两个服务:

[Route("/bookinglimit", "GET")]
[Authenticate]
public class GetBookingLimit : IReturn<GetBookingLimitResponse>
{
    public int Id { get; set; }
}
public class GetBookingLimitResponse
{
    public int Id { get; set; }
    public int ShiftId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public int Limit { get; set; }

    public ResponseStatus ResponseStatus { get; set; }
}

[Route("/bookinglimits", "GET")]
[Authenticate]
public class GetBookingLimits : IReturn<GetBookingLimitsResponse>
{      
    public DateTime Date { get; set; }
}
public class GetBookingLimitsResponse
{
    public List<GetBookingLimitResponse> BookingLimits { get; set; }
    public ResponseStatus ResponseStatus { get; set; }
}

作为对这些请求DTO的看到我有类似的要求DTO几乎是每一个服务,这似乎是不干燥。

我试图用GetBookingLimitResponse类在列表里面GetBookingLimitsResponse出于这个原因ResponseStatusGetBookingLimitResponse的情况下,我有一个错误类dublicated GetBookingLimits服务。

我也有这些要求,是服务实现:

public class BookingLimitService : AppServiceBase
{
    public IValidator<AddBookingLimit> AddBookingLimitValidator { get; set; }

    public GetBookingLimitResponse Get(GetBookingLimit request)
    {
        BookingLimit bookingLimit = new BookingLimitRepository().Get(request.Id);
        return new GetBookingLimitResponse
        {
            Id = bookingLimit.Id,
            ShiftId = bookingLimit.ShiftId,
            Limit = bookingLimit.Limit,
            StartDate = bookingLimit.StartDate,
            EndDate = bookingLimit.EndDate,
        };
    }

    public GetBookingLimitsResponse Get(GetBookingLimits request)
    {
        List<BookingLimit> bookingLimits = new BookingLimitRepository().GetByRestaurantId(base.UserSession.RestaurantId);
        List<GetBookingLimitResponse> listResponse = new List<GetBookingLimitResponse>();

        foreach (BookingLimit bookingLimit in bookingLimits)
        {
            listResponse.Add(new GetBookingLimitResponse
                {
                    Id = bookingLimit.Id,
                    ShiftId = bookingLimit.ShiftId,
                    Limit = bookingLimit.Limit,
                    StartDate = bookingLimit.StartDate,
                    EndDate = bookingLimit.EndDate
                });
        }


        return new GetBookingLimitsResponse
        {
            BookingLimits = listResponse.Where(l => l.EndDate.ToShortDateString() == request.Date.ToShortDateString() && l.StartDate.ToShortDateString() == request.Date.ToShortDateString()).ToList()
        };
    }
}

正如你看到的我也想在这里使用验证功能,所以我必须写验证类为每个请求DTO我有。 所以,我有我应该让我的服务号码由低分组类似服务为一体的服务的感觉。

但在这里,在我的脑海里弹出的疑问,我应该比客户端需要这样发送请求的更多信息?

我觉得我的思维方式应该改变,因为我不开心的与我写的思维就像一个WCF家伙当前的代码。

可有人告诉我正确的方向可循。

Answer 1:

为了让你的差异的味道,你应该想想,当在设计基于消息的服务ServiceStack我将提供一些例子比较WCF / VS的WebAPI ServiceStack的做法:

WCF VS ServiceStack API设计

WCF鼓励你去思考的Web服务作为普通的C#方法调用,如:

public interface IWcfCustomerService
{
    Customer GetCustomerById(int id);
    List<Customer> GetCustomerByIds(int[] id);
    Customer GetCustomerByUserName(string userName);
    List<Customer> GetCustomerByUserNames(string[] userNames);
    Customer GetCustomerByEmail(string email);
    List<Customer> GetCustomerByEmails(string[] emails);
}

这是相同的服务合同是什么样子与ServiceStack 新的API :

public class Customers : IReturn<List<Customer>> 
{
   public int[] Ids { get; set; }
   public string[] UserNames { get; set; }
   public string[] Emails { get; set; }
}

要记住的重要概念是整个查询(又名请求)的请求消息来捕获(即请求DTO),而不是在服务器方法签名。 采用基于消息的设计的明显直接的好处是,上面的RPC调用的任何组合可以在1条远程消息得到满足,由单个服务实现。

的WebAPI VS ServiceStack API设计

同样的WebAPI促进了WCF做了类似的类似C#的RPC API:

public class ProductsController : ApiController 
{
    public IEnumerable<Product> GetAllProducts() {
        return products;
    }

    public Product GetProductById(int id) {
        var product = products.FirstOrDefault((p) => p.Id == id);
        if (product == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
        return product;
    }

    public Product GetProductByName(string categoryName) {
        var product = products.FirstOrDefault((p) => p.Name == categoryName);
        if (product == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
        return product;
    }

    public IEnumerable<Product> GetProductsByCategory(string category) {
        return products.Where(p => string.Equals(p.Category, category,
                StringComparison.OrdinalIgnoreCase));
    }

    public IEnumerable<Product> GetProductsByPriceGreaterThan(decimal price) {
        return products.Where((p) => p.Price > price);
    }
}

ServiceStack基于消息的API设计

虽然ServiceStack鼓励你保留一个基于消息的设计:

public class FindProducts : IReturn<List<Product>> {
    public string Category { get; set; }
    public decimal? PriceGreaterThan { get; set; }
}

public class GetProduct : IReturn<Product> {
    public int? Id { get; set; }
    public string Name { get; set; }
}

public class ProductsService : Service 
{
    public object Get(FindProducts request) {
        var ret = products.AsQueryable();
        if (request.Category != null)
            ret = ret.Where(x => x.Category == request.Category);
        if (request.PriceGreaterThan.HasValue)
            ret = ret.Where(x => x.Price > request.PriceGreaterThan.Value);            
        return ret;
    }

    public Product Get(GetProduct request) {
        var product = request.Id.HasValue
            ? products.FirstOrDefault(x => x.Id == request.Id.Value)
            : products.FirstOrDefault(x => x.Name == request.Name);

        if (product == null)
            throw new HttpError(HttpStatusCode.NotFound, "Product does not exist");

        return product;
    }
}

再次捕获请求DTO请求的实质。 基于消息的设计也能够凝结5个独立的RPC的WebAPI服务到2基于消息的ServiceStack的。

集团通过调用语义和响应类型

它分为2个不同的服务,基于呼叫语义响应类型的例子:

在每个请求DTO每个属性都有相同的语义是FindProducts每个属性的作用就像一个过滤器(例如AND)而在GetProduct它就像一个组合子(例如OR)。 该服务也返回IEnumerable<Product>Product的返回类型,这将需要在类型化的API的调用点不同的处理。

在WCF /的WebAPI(和其他RPC服务框架),只要你有一个特定的客户要求,你会认为该请求匹配的控制器上添加新的服务器签名。 在ServiceStack的基于消息的方法,但是你应该总是想着这个功能属于哪里,以及是否你能够增强现有的服务。 你也应该考虑如何支持在一个通用的方法客户特定的要求,使同样的服务可有益于其它潜在的未来使用情况。

重新分解GetBooking限制服务

随着信息上面我们可以开始重新分解你的服务。 既然你有一个返回不同的结果2种不同的服务,例如GetBookingLimit返回1项和GetBookingLimits返回不少,他们需要被保存在不同的服务。

区分服务运营VS类型

然而,你应该有你的服务操作(例如,请求DTO),这是每个服务唯一的,用于捕获服务的要求,以及它们返回的DTO类型之间有一个清晰分裂。 请求DTO的通常操作,以便他们是动词,而DTO类型的实体/数据容器所以他们的名词。

返回通用的反应

在新的API,ServiceStack响应不再需要ResponseStatus性质,因为如果它不存在一般是ErrorResponse DTO会抛出和系列化的客户端来代替。 这使你不必你的回应包含ResponseStatus性能。 随着中说我会重新因素你的新服务合同:

[Route("/bookinglimits/{Id}")]
public class GetBookingLimit : IReturn<BookingLimit>
{
    public int Id { get; set; }
}

public class BookingLimit
{
    public int Id { get; set; }
    public int ShiftId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public int Limit { get; set; }
}

[Route("/bookinglimits/search")]
public class FindBookingLimits : IReturn<List<BookingLimit>>
{      
    public DateTime BookedAfter { get; set; }
}

对于GET请求我倾向于给他们留下了路线定义的时候他们不是模糊的,因为它是更少的代码。

保持一致的命名

你应该保留搭话上唯一或主键字段,即,查询服务时所提供的值的字段匹配(例如:内径)只获取 1分的结果。 对于搜索服务,就像一个过滤器,并返回多个匹配的结果我请使用查找搜索动词的信号,这是情况下所需的范围内,其下降。

瞄准自我描述的服务合同

也尽量描述与每个字段名,这些属性是你的公共API的一部分,应该是自描述为它做什么。 例如,只要看一眼的服务合同(例如,请求DTO)我们不知道呢,我认为BookedAfter什么日期 ,但它也可能是BookedBeforeBookedOn如果只返回那一天进行预订。

这样做的好处是,现在您的调用点类型的.NET客户端变得更容易阅读:

Product product = client.Get(new GetProduct { Id = 1 });

List<Product> results = client.Get(
    new FindBookingLimits { BookedAfter = DateTime.Today });

服务实现

我已经删除了[Authenticate]从您的要求DTO的属性,因为你可以而不是只指定了一次服务实现,它现在的样子:

[Authenticate]
public class BookingLimitService : AppServiceBase 
{ 
    public BookingLimit Get(GetBookingLimit request) { ... }

    public List<BookingLimit> Get(FindBookingLimits request) { ... }
}

错误处理和验证

有关如何添加验证你要么只选择信息抛出C#的异常并应用自己的自定义它们,否则你必须使用该选项的内置流利的验证 ,但你并不需要将其注入到你的服务如你就可以将它们全部用在你的APPHOST,例如一行:

container.RegisterValidators(typeof(CreateBookingValidator).Assembly);

验证器是无接触和无侵入性的意义,你可以使用一个分层的方法添加它们,并保持它们而不需要修改服务实现或DTO类。 因为它们需要一个额外的类我只会用他们与副作用(如POST / PUT)为GET的操作往往有最小的验证和抛出C#异常需要较少的锅炉板。 所以,你可以有一个验证的一个例子是首先建立在预订时:

public class CreateBookingValidator : AbstractValidator<CreateBooking>
{
    public CreateBookingValidator()
    {
        RuleFor(r => r.StartDate).NotEmpty();
        RuleFor(r => r.ShiftId).GreaterThan(0);
        RuleFor(r => r.Limit).GreaterThan(0);
    }
}

根据不同的使用情况,而不必单独CreateBookingUpdateBooking DTO的我将重新使用相同的请求DTO两个在这种情况下,我会说出StoreBooking



Answer 2:

该“效应初探DTO的”似乎是不必要的,因为ResponseStatus属性不再需要。 。 虽然,我认为你可能还需要一个匹配响应类,如果你使用SOAP。 如果删除响应的DTO你不再需要推BookLimit到响应的对象。 此外,ServiceStack的TranslateTo()可以很好的帮助。

下面是我怎么会尽量简化你贴什么...因人而异。

作出BookingLimit一个DTO - 这将是BookingLimit的所有其他系的代表性。

public class BookingLimitDto
{
    public int Id { get; set; }
    public int ShiftId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public int Limit { get; set; }
}

请求和DTO的是很重要的

[Route("/bookinglimit", "GET")]
[Authenticate]
public class GetBookingLimit : IReturn<BookingLimitDto>
{
    public int Id { get; set; }
}

[Route("/bookinglimits", "GET")]
[Authenticate]
public class GetBookingLimits : IReturn<List<BookingLimitDto>>
{
    public DateTime Date { get; set; }
}

不再返回效应初探对象......只是BookingLimitDto

public class BookingLimitService : AppServiceBase 
{ 
    public IValidator AddBookingLimitValidator { get; set; }

    public BookingLimitDto Get(GetBookingLimit request)
    {
        BookingLimitDto bookingLimit = new BookingLimitRepository().Get(request.Id);
        //May need to bookingLimit.TranslateTo<BookingLimitDto>() if BookingLimitRepository can't return BookingLimitDto

        return bookingLimit; 
    }

    public List<BookingLimitDto> Get(GetBookingLimits request)
    {
        List<BookingLimitDto> bookingLimits = new BookingLimitRepository().GetByRestaurantId(base.UserSession.RestaurantId);
        return
            bookingLimits.Where(
                l =>
                l.EndDate.ToShortDateString() == request.Date.ToShortDateString() &&
                l.StartDate.ToShortDateString() == request.Date.ToShortDateString()).ToList();
    }
} 


文章来源: ServiceStack Request DTO design