Servicestack Request Dto Design

ServiceStack Request DTO design

To give you a flavor of the differences you should think about when designing message-based services in ServiceStack I'll provide some examples comparing WCF/WebApi vs ServiceStack's approach:

WCF vs ServiceStack API Design

WCF encourages you to think of web services as normal C# method calls, e.g:

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);
}

This is what the same Service contract would look like in ServiceStack with the New API:

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

The important concept to keep in mind is that the entire query (aka Request) is captured in the Request Message (i.e. Request DTO) and not in the server method signatures. The obvious immediate benefit of adopting a message-based design is that any combination of the above RPC calls can be fulfilled in 1 remote message, by a single service implementation.

WebApi vs ServiceStack API Design

Likewise WebApi promotes a similar C#-like RPC Api that WCF does:

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 Message-Based API Design

Whilst ServiceStack encourages you to retain a Message-based Design:

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;
}
}

Again capturing the essence of the Request in the Request DTO. The message-based design is also able to condense 5 separate RPC WebAPI services into 2 message-based ServiceStack ones.

Group by Call Semantics and Response Types

It's grouped into 2 different services in this example based on Call Semantics and Response Types:

Every property in each Request DTO has the same semantics that is for FindProducts each property acts like a Filter (e.g. an AND) whilst in GetProduct it acts like a combinator (e.g. an OR). The Services also return IEnumerable<Product> and Product return types which will require different handling in the call-sites of Typed APIs.

In WCF / WebAPI (and other RPC services frameworks) whenever you have a client-specific requirement you would add a new Server signature on the controller that matches that request. In ServiceStack's message-based approach however you should always be thinking about where this feature belongs and whether you're able to enhance existing services. You should also be thinking about how you can support the client-specific requirement in a generic way so that the same service could benefit other future potential use-cases.

Re-factoring GetBooking Limits services

With the info above we can start re-factoring your services. Since you have 2 different services that return different results e.g. GetBookingLimit returns 1 item and GetBookingLimits returns many, they need to be kept in different services.

Distinguish Service Operations vs Types

You should however have a clean split between your Service Operations (e.g. Request DTO) which is unique per service and is used to capture the Services' request, and the DTO types they return. Request DTOs are usually actions so they're verbs, whilst DTO types are entities/data-containers so they're nouns.

Return generic responses

In the New API, ServiceStack responses no longer require a ResponseStatus property since if it doesn't exist the generic ErrorResponse DTO will be thrown and serialized on the client instead. This frees you from having your Responses contain ResponseStatus properties. With that said I would re-factor the contract of your new services to:

[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; }
}

For GET requests I tend to leave them out of the Route definition when they're not ambiguous since it's less code.

Keep a consistent Nomenclature

You should reserve the word Get on services which query on unique or Primary Keys fields, i.e. when a supplied value matches a field (e.g. Id) it only Gets 1 result. For search services that acts like a filter and returns multiple matching results which falls within a desired range I use either the Find or Search verbs to signal that this is the case.

Aim for self-describing Service Contracts

Also try to be descriptive with each of your field names, these properties are part of your public API and should be self-describing as to what it does. E.g. Just by looking at the Service Contract (e.g. Request DTO) we have no idea what Date does, I've assumed BookedAfter, but it could also have been BookedBefore or BookedOn if it only returned bookings made on that Day.

The benefit of this is now the call-sites of your typed .NET clients become easier to read:

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

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

Service implementation

I've removed the [Authenticate] attribute from your Request DTOs since you can instead just specify it once on the Service implementation, which now looks like:

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

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

Error Handling and Validation

For info on how to add validation you either have the option to just throw C# exceptions and apply your own customizations to them, otherwise you have the option to use the built-in Fluent Validation but you don't need to inject them into your service as you can wire them all with a single line in your AppHost, e.g:

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

Validators are no-touch and invasive free meaning you can add them using a layered approach and maintain them without modifying the service implementation or DTO classes. Since they require an extra class I would only use them on operations with side-effects (e.g. POST/PUT) as GETs' tend to have minimal validation and throwing a C# Exception requires less boiler plate. So an example of a validator you could have is when first creating a booking:

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

Depending on the use-case instead of having separate CreateBooking and UpdateBooking DTOs I would re-use the same Request DTO for both in which case I would name StoreBooking.

More ServiceStack request DTO advice


Message-based API Design

There are a few things to bear in mind when designing the ideal message-based API where your Services effectively end up serving 2 masters: a Native Client API and a REST API. Native Clients just send and receive messages in their original form so they get a natural API for free modelled using C# Request and Response DTOs to capture what information is required for the Service to perform its Operation and what it should return.

Projecting messages into the ideal HTTP API

After designing your message-based API you'll then want to focus on how best to project the messages into a REST API by annotating Request DTOs with [Route] Attributes to define the Custom endpoints for your Services.

This previous answer on Designing a REST-ful service with ServiceStack provides examples on which routes different Request DTOs map to, in general you'll want to design your APIs around Resources where each operation "acts on a Resource" which will make defining your Custom Routes easier. The ideal HTTP API for Creating and Updating a Booking Limit would look like:

POST /bookinglimits       (Create Booking Limit)
PUT /bookinglimits/{id} (Update Booking Limit)

General recommendations on good API Design

Whilst not specifically about Web Services this article on Ten Rules for Good API Design provides good recommendations on general (Code or Services) API design. As API Consumers are the intended audience of your APIs who'll primarily be deriving the most value from them, their design should be optimized so that they're self-descriptive, use consistent naming, are intuitive to use and can be evolved without breaking existing clients. Messages are naturally suited to versioning but you still need to be mindful when making changes to existing published APIs that any additional properties are optional with default fallback behavior if required.

For this reason whilst you can save some code by returning a naked BookingLimit, my preference is to instead return a specific Response DTO for each Service which allows the Service to return additional metadata without breaking existing clients whilst maintaining a consistent Request/Response pattern for all Services. Although this is just my preference - returning naked types is also fine.

ServiceStack Implementation

To implement this in ServiceStack I wouldn't use the same Request DTO to support multiple verbs. Since the Request DTO is called Create* that conveys that users should only send this Request DTO to Create Booking limits which is typically done using a POST request, e.g:

[Route("/bookinglimits", "POST")]
public class CreateBookingLimit : IReturn<CreateBookingLimitResponse>, IPost
{
public int ShiftId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int Limit { get; set; }
}

public class CreateBookingLimitResponse
{
public BookingLimit Result { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}

The IPut, IPost are Verb interface markers which lets both the User and Service Client know which Verb this message should be sent with which makes it possible to have all messages sent in a single Service Gateway method.

If your Service also supports updating a Booking Limit then I'd create a separate Service for it which would look like:

[Route("/bookinglimits/{Id}", "PUT")]
public class UpdateBookingLimit : IReturn<UpdateBookingLimitResponse>, IPut
{
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 class UpdateBookingLimitResponse
{
public BookingLimit Result { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}

By using separate Operations you can ensure Request DTOs contains only the properties relevant to that operation, reducing the confusion for API consumers.

If it makes sense for your Service, e.g. the schemas for both operations remains the same I'll merge both Create/Update operations into a single Operation. When you do this you should use a consistent Verb that indicates when an operation does both, e.g. Store* or CreateOrUpdate*:

[Route("/bookinglimits", "POST")]
public class StoreBookingLimit : IReturn<StoreBookingLimitResponse>, IPost
{
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 class StoreBookingLimitResponse
{
public BookingLimit Result { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}

In most cases where the Server generates the Id for the Resource you should use POST, in the rare case where the client specifies the Id, e.g. Slug or Guid you can use PUT which roughly translates to "PUT this resource at this location" which is possible when the client knows the url for the resource.

Message based API examples

Most of the time what messages should contain will be obvious based on the Service requirements and becomes intuitive and natural to create over time. For examples on a comprehensive message-based API you can have a look AWS Web Services who've effectively servicified their Web Services behind a message-based design that uses Service Clients to send messages to access all their APIs, e.g. AWS DynamoDB API Reference lists each Actions that's available as well as other DTO Types that the Services return, e.g here are DynamoDB APIs they have around Creating / Modifying and Querying Items:

Actions

  • BatchGetItem
  • BatchWriteItem
  • DeleteItem
  • GetItem
  • PutItem
  • Query
  • Scan
  • UpdateItem

Data Types

  • AttributeDefinition
  • AttributeValue
  • AttributeValueUpdate
  • Condition
  • ...

In ServiceStack Actions are called Operations and what you'll use Request DTOs to define, whilst AWS Data Types are just called DTOs which I keep in a Types namespace to differentiate from Operations.

DynamoDb.ServiceModel (project)

/GetItem
/PutItem
/UpdateItem
/DeleteItem
/Query
/Scan

/Types
/AttributeDefinition
/AttributeValue
/AttributeValueUpdate

You typically wouldn't need additional explicit Services for Batch Requests as you can get that for free using ServiceStack's Auto Batched Requests. ServiceStack also includes a number of other benefits where it's able to generate richer DTOs containing Custom Attributes and interfaces in the Source DTOs to enable a richer and succinct end-to-end typed API requiring less boilerplate and generated code that lets you use the same Generic Service Client to call any ServiceStack Service offering both Sync and idiomatic Async APIs. The additional metadata also enables seamless higher-level functionality like Encrypted Messaging, Cache Aware Clients, Multiple Formats, Service Gateway, HTTP Verb Interface Markers, etc.

Otherwise AWS follows a very similar approach to ServiceStack for designing message-based APIs using generic Service Clients to send DTOs native in each language.

servicestack - make request dto parameters REQUIRED

You can declare required fields using ServiceStack's built-in ValidationFeature, e.g:

Enable in AppHost with:

Plugins.Add(new ValidationFeature());

//Register All Validators in Assembly
container.RegisterValidators(typeof(MyValidator).Assembly);

Configure with:

public class ResetPasswordValidator : AbstractValidator<ResetPassword>
{
public ResetPasswordValidator()
{
RuleFor(x => x.UserId).NotEmpty();
RuleFor(x => x.OldPassword).NotEmpty();
RuleFor(x => x.NewPassword).NotEmpty();
}
}

Designing Message-based and HTTP APIs

Note some serializers require all DTO's to have a parameterless constructor. Also Resetting a Users Password isn't a valid PUT Operation which is expected to be idempotent and roughly translates to "PUT this resource at this location", it's more appropriately sent as a POST.

You can find some tips on designing message-based and HTTP APIs in ServiceStack in this previous answer where I would rewrite this Request DTO as:

[Route("/users/{UserId}/reset-password", "POST")]
public class ResetPassword : IReturn<ResetPasswordResponse>
{
public Guid UserId { get; set; }
public string OldPassword { get; set; }
public string NewPassword { get; set; }
}

Although if this Service doesn't need to return a response it can also just return void, e.g:

public class ResetPassword : IReturnVoid { ... }

And implemented in your Service with:

public void Any(ResetPassword request) { ... }

Request DTO with private setters

Yes DTOs (Data Transfer Objects) by design are meant to have serializable properties with public getters/setters and a parameterless constructor.

ServiceStack zero dependency Request-Response DTOs

The only dependency DTO's should have is the impl-free ServiceStack.Interfaces.dll which as it's a Portable Class Library (PCL) supports almost every mobile or Desktop platform that .NET runs on. ServiceStack's Interfaces .dll is required in order to be able to cleanly describe your complete Services contract in a single, benign .dll.

For example. the [Route] metadata attribute captures the Custom Routes where the remote Services are hosted which is required info about your Service that clients need to know in order to be able to call services via their published Custom Routes. Likewise the IReturn<T> interface marker provides a strong-typed contract on what your Service returns which is what enables ServiceStack succinct end-to-end Typed API. Essentially ServiceStack.Interfaces is a required extension to be able to capture your entire Service Contract in your Services DTO's.

ServiceStack.Interfaces can be used outside of ServiceStack

Even if you don't use ServiceStack, you can still use the benign ServiceStack.Interfaces.dll which the clients can introspect to find out more information about your DTO's and the remote Service Contract. Whilst I'm not seeing any reason to, if you want to decouple the ServiceStack.Interfaces on your project you can just copy the attributes you're using in your DTO .dll freeing it from any external dependencies. But this would impact your ability to have a generic Service Client since these embedded interfaces and attributes are unknown to your client library, limiting its ability to enable rich generic functionality using it.

Service Contract Interfaces and Attributes in other Languages

To support non .NET languages like TypeScript, ServiceStack emits these interfaces in the generated DTO's so they don't require any dependencies.

Likewise in Add ServiceStack Reference support of Swift 2.0 or Java and Android these additional contracts are emitted idiomatically referencing a Swift IReturn protocol or IReturn<T> interface in the Java android client package which is also what enables the succinct Typed API's ServiceStack enables on both iOS and Android.

Service Design

Something you should keep in mind when designing your API's is that your Service Layer is your most important contract. i.e. Your API exists to allow consumers access to your remote Servers capabilities, so your internal logic should be a hidden impl-detail, not something that should impact the external surface area of your API.

The Request DTO defines your Service Contract where I find using a Request suffix is an ugly construct that negatively affects the readability of your external API, e.g. Here's a typical example of what a noun with a *Request suffix would look like:

var response = client.Get(new CustomerRequest { ... });

Compared with using a Verb where the Request DTO is indicative and provides better readability of what the Service does:

var response = client.Get(new FindCustomers { ... });

Your Request DTO should ideally be a verb that's grouped by call semantics and Response Type. Having a *Dto suffix is an indication that your internal implementation is leaking and affecting the ideal Service Contract your external API Consumers will bind to (and should never change). Keep in mind the objective of your Service is to provide re-usable functionality to your consumers so your impl should realize your published contract, not the other way around where its implementation dictates what the contract should be.

With that in mind I would rewrite your ServiceStack Examples to look like:

public class Authenticate : IReturn<AuthenticateResponse>
{
public string UserName { get; set; }
public string Password { get; set; }
}

public class AuthenticateResponse
{
public AuthenticationResult Result { get; set; }
public UserInfo UserInfo { get; set; }
}

Which ends up being similar to ServiceStack's built-in Authenticate and AuthenticateResponse Request and Response DTOs.

I also recommend reading this earlier answer to understand the importance of DTO's and how it relates to the goals of a Service.

servicestack GlobalRequestFilters request Dto coming null

The requestDto is passed in the filter itself, i.e:

GlobalRequestFilters.Add((req, res, requestDto) => {
var authDto = requestDto as Authenticate;
if (authDto != null)
{
//...
}
});

An alternative approach to the above is to use a Typed Request Filter, e.g:

RegisterTypedRequestFilter<Authenticate>((req, res, authDto) => {
//...
});


Related Topics



Leave a reply



Submit