Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied! <p>To give you a flavor of the differences you should think about when designing message-based services in <a href="http://www.servicestack.net" rel="noreferrer">ServiceStack</a> I'll provide some examples comparing WCF/WebApi vs ServiceStack's approach:</p> <h2><a href="https://github.com/ServiceStack/ServiceStack/wiki/Why-Servicestack#difference-between-an-rpc-chatty-and-message-based-api" rel="noreferrer">WCF vs ServiceStack API Design</a></h2> <p>WCF encourages you to think of web services as normal C# method calls, e.g:</p> <pre class="lang-cs prettyprint-override"><code>public interface IWcfCustomerService { Customer GetCustomerById(int id); List&lt;Customer&gt; GetCustomerByIds(int[] id); Customer GetCustomerByUserName(string userName); List&lt;Customer&gt; GetCustomerByUserNames(string[] userNames); Customer GetCustomerByEmail(string email); List&lt;Customer&gt; GetCustomerByEmails(string[] emails); } </code></pre> <p>This is what the same Service contract would look like in ServiceStack with the <a href="https://github.com/ServiceStack/ServiceStack/wiki/New-API" rel="noreferrer">New API</a>:</p> <pre class="lang-cs prettyprint-override"><code>public class Customers : IReturn&lt;List&lt;Customer&gt;&gt; { public int[] Ids { get; set; } public string[] UserNames { get; set; } public string[] Emails { get; set; } } </code></pre> <p>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.</p> <h2><a href="https://github.com/ServiceStack/ServiceStack.UseCases/tree/master/WebApi.ProductsExample" rel="noreferrer">WebApi vs ServiceStack API Design</a></h2> <p>Likewise WebApi promotes a similar C#-like RPC Api that WCF does:</p> <pre class="lang-cs prettyprint-override"><code>public class ProductsController : ApiController { public IEnumerable&lt;Product&gt; GetAllProducts() { return products; } public Product GetProductById(int id) { var product = products.FirstOrDefault((p) =&gt; p.Id == id); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return product; } public Product GetProductByName(string categoryName) { var product = products.FirstOrDefault((p) =&gt; p.Name == categoryName); if (product == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } return product; } public IEnumerable&lt;Product&gt; GetProductsByCategory(string category) { return products.Where(p =&gt; string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase)); } public IEnumerable&lt;Product&gt; GetProductsByPriceGreaterThan(decimal price) { return products.Where((p) =&gt; p.Price &gt; price); } } </code></pre> <h3>ServiceStack Message-Based API Design</h3> <p>Whilst ServiceStack encourages you to retain a Message-based Design:</p> <pre class="lang-cs prettyprint-override"><code>public class FindProducts : IReturn&lt;List&lt;Product&gt;&gt; { public string Category { get; set; } public decimal? PriceGreaterThan { get; set; } } public class GetProduct : IReturn&lt;Product&gt; { 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 =&gt; x.Category == request.Category); if (request.PriceGreaterThan.HasValue) ret = ret.Where(x =&gt; x.Price &gt; request.PriceGreaterThan.Value); return ret; } public Product Get(GetProduct request) { var product = request.Id.HasValue ? products.FirstOrDefault(x =&gt; x.Id == request.Id.Value) : products.FirstOrDefault(x =&gt; x.Name == request.Name); if (product == null) throw new HttpError(HttpStatusCode.NotFound, "Product does not exist"); return product; } } </code></pre> <p>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. </p> <h2>Group by Call Semantics and Response Types</h2> <p>It's grouped into 2 different services in this example based on <strong>Call Semantics</strong> and <strong>Response Types</strong>:</p> <p>Every property in each Request DTO has the same semantics that is for <code>FindProducts</code> each property acts like a Filter (e.g. an AND) whilst in <code>GetProduct</code> it acts like a combinator (e.g. an OR). The Services also return <code>IEnumerable&lt;Product&gt;</code> and <code>Product</code> return types which will require different handling in the call-sites of Typed APIs.</p> <p>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 <strong>generic way</strong> so that the same service could benefit other future potential use-cases.</p> <h1>Re-factoring GetBooking Limits services</h1> <p>With the info above we can start re-factoring your services. Since you have 2 different services that return different results e.g. <code>GetBookingLimit</code> returns 1 item and <code>GetBookingLimits</code> returns many, they need to be kept in different services.</p> <h3>Distinguish Service Operations vs Types</h3> <p>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. </p> <h3>Return generic responses</h3> <p>In the New API, ServiceStack responses <a href="https://github.com/ServiceStack/ServiceStack/wiki/New-Api#structured-error-handling" rel="noreferrer">no longer require a ResponseStatus</a> property since if it doesn't exist the generic <code>ErrorResponse</code> DTO will be thrown and serialized on the client instead. This frees you from having your Responses contain <code>ResponseStatus</code> properties. With that said I would re-factor the contract of your new services to:</p> <pre class="lang-cs prettyprint-override"><code>[Route("/bookinglimits/{Id}")] public class GetBookingLimit : IReturn&lt;BookingLimit&gt; { 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&lt;List&lt;BookingLimit&gt;&gt; { public DateTime BookedAfter { get; set; } } </code></pre> <p>For GET requests I tend to leave them out of the Route definition when they're not ambiguous since it's less code.</p> <h3>Keep a consistent Nomenclature</h3> <p>You should reserve the word <strong>Get</strong> on services which query on unique or Primary Keys fields, i.e. when a supplied value matches a field (e.g. Id) it only <strong>Gets</strong> 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 <strong>Find</strong> or <strong>Search</strong> verbs to signal that this is the case.</p> <h3>Aim for self-describing Service Contracts</h3> <p>Also try to be descriptive with each of your field names, these properties are part of your <strong>public API</strong> 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 <strong>Date</strong> does, I've assumed <strong>BookedAfter</strong>, but it could also have been <strong>BookedBefore</strong> or <strong>BookedOn</strong> if it only returned bookings made on that Day. </p> <p>The benefit of this is now the call-sites of your <a href="https://github.com/ServiceStack/ServiceStack/wiki/C#-client" rel="noreferrer">typed .NET clients</a> become easier to read:</p> <pre class="lang-cs prettyprint-override"><code>Product product = client.Get(new GetProduct { Id = 1 }); List&lt;Product&gt; results = client.Get( new FindBookingLimits { BookedAfter = DateTime.Today }); </code></pre> <h2>Service implementation</h2> <p>I've removed the <code>[Authenticate]</code> attribute from your Request DTOs since you can instead just specify it once on the Service implementation, which now looks like:</p> <pre class="lang-cs prettyprint-override"><code>[Authenticate] public class BookingLimitService : AppServiceBase { public BookingLimit Get(GetBookingLimit request) { ... } public List&lt;BookingLimit&gt; Get(FindBookingLimits request) { ... } } </code></pre> <h2>Error Handling and Validation</h2> <p>For info on how to add validation you either have the option to just <a href="https://github.com/ServiceStack/ServiceStack/wiki/Error-Handling" rel="noreferrer">throw C# exceptions</a> and apply your own customizations to them, otherwise you have the option to use the built-in <a href="https://github.com/ServiceStack/ServiceStack/wiki/Validation" rel="noreferrer">Fluent Validation</a> 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:</p> <pre class="lang-cs prettyprint-override"><code>container.RegisterValidators(typeof(CreateBookingValidator).Assembly); </code></pre> <p>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:</p> <pre class="lang-cs prettyprint-override"><code>public class CreateBookingValidator : AbstractValidator&lt;CreateBooking&gt; { public CreateBookingValidator() { RuleFor(r =&gt; r.StartDate).NotEmpty(); RuleFor(r =&gt; r.ShiftId).GreaterThan(0); RuleFor(r =&gt; r.Limit).GreaterThan(0); } } </code></pre> <p>Depending on the use-case instead of having separate <code>CreateBooking</code> and <code>UpdateBooking</code> DTOs I would re-use the same Request DTO for both in which case I would name <code>StoreBooking</code>.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload