Вопрос

I have been looking at moving to message based service (ServiceStack style) and away from WCF style services (almost RPC). From having used WCF style services I see some short comings and I want to try another approach. It kills me that WCF methods can't really have overloads and I understand why and I know there are ways around it but those feel hacky to me. Where message based services acknowledges that things are over the wire.

The question I have is how to handle retrieving data. ServiceStack (and other APIs) seem to have one request object for getting either a single entity or a collection of entities. This request object has a number of optional parameters. For example

public GetInvoiceRequest
{
    public int? InvoiceId {get; set;}

    public int? AccountId {get; set;}

    public DateTime? From {get; set;}
}

public GetInvoiceResponse
{
    public List<InvoiceDto> {get;set;}

    public ResponseStatus Status {get;set;}
}

What is the standard practice for this type of thing? Do you include invoices for each optional parameter? So you get the invoice for the ID, then if the AccountId is also set add to that all the invoices for that Account and if the From is also set then add all the invoices from a specific date? Effectively performing unions. Or do you perform the intersection of these sets? Or do you simply honour the first parameter that is set. So if all the parameters are set you only return the Invoice matching InvoiceId because that is the first parameter encountered?

Это было полезно?

Решение

You have the right idea, and your approach is valid, but people tend to separate the intent of a request for a specific invoice out from a request for a list of invoices.

I tend to build a service such that my DTOs follow a C-R-U-D-L operations format i.e. Create Read Update Delete List type DTO:

So when I have a ReadInvoiceRequest rather than a GetInvoiceRequest I know will always return a single InvoiceDto object and requires the InvoiceId parameter:

[Route("/invoices/{InvoiceId}", "GET")]
[Route("/account/{AccountId}/invoices/{InvoiceId}","GET")]
public ReadInvoiceRequest : IReturn<InvoiceDto>
{
    public int InvoiceId { get; set; } // InvoiceId is always expected
    public int? AccountId { get; set; }
    public DateTime? From { get; set; }
}

And the routes specifically for listing, will always return a list of InvoiceDto:

[Route("/invoices", "GET")]
[Route("/account/{AccountId}/invoices","GET")]
public ListInvoicesRequest : IReturn<List<InvoiceDto>>
{
    public int? AccountId { get; set; }
    public DateTime? From { get; set; }
}

This prevents ambiguity over how many results would have been returned if an InvoiceId was specified and your return type was a list of results.

It's also makes it clear to the consumer that the optional parameters in the request DTOs will be treated as filters.

Your action methods in your Service would then be similar to this:

public class MyInvoiceService : Service
{
    // CREATE
    public int Post(CreateInvoiceRequest request)
    {
        // Return the InvoiceId of the created record
    }

    // READ
    public InvoiceDto Get(ReadInvoiceRequest request)
    {
        var invoice = ... // request.InvoiceId; (filter by AccountId & From if set)
        return invoice;
    }

    // UPDATE
    public void Post(UpdateInvoiceRequest request)
    {
        // Handle update
        // I don't return anything, only throw exceptions if update fails
        // Success is indicated by 200 status
    }

    // DELETE
    public void Delete(DeleteInvoiceRequest request)
    {
        // Handle delete
        // I don't return anything, only throw exceptions if delete fails
        // Success is indicated by 200 status
    }

    // LIST
    public List<InvoiceDto> Get(ListInvoicesRequest request)
    {
        var invoices = ... // (filter by AccountId & From if set)
        return invoices;
    }
}

From a service consumers point of view, I believe there is less uncertainty about the response it will get from the separated DTOs.

Другие советы

The ServiceStack API has moved away from the one Request and matching Response DTO pattern. ServiceStack now allows many different requests DTOs and the ability to explicitly designate the response object.

You should see this answer: ServiceStack Request DTO design

But generally when you are performing a Search I use optional (nullable) properties and generate the search query on the fly.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top