28
votes

In the examples for ServiceStack I don't see a single application that is ASP.NET MVC website first and then made ServiceStack service second.

Let's take a very simple ASP.NET MVC web application that renders products through Views. It uses controllers, views, models and viewmodels.

Let's say we have a model of Product which gets persisted into a document DB. Let's assume we have a viewmodel of ProductViewModel which gets mapped from Product and display within MVC Razor View/PartialView.

so this is a web side of things..now let's assume we want to add a service returning products to various clients like the Windows 8 applications.

Should the request/response classes be completely disconnected from what we already have? Our ProductViewModel might already contain everything we want to return from the service.

Since we already have Product (model class) we can't have another Product class in the API namespace..well we could but that makes things unclear and I'd like to avoid that.

So, should we introduce standalone ProductRequest class and ProductRequestResponse (inherits ProductViewModel) class in the API namespace?

Like so ProductRequestResponse : ProductViewModel?

What i'm saying is, we already have the Model and ViewModel classes and to construct Request and Response classes for the SS service we would have to create another two files, mostly by copying everything from the classes we already have. This doesn't look DRY to me, it might follow the separation of concerns guidelines but DRY is important too, actually more than separating everything (separating everything leads to duplication of code).

What I would like to see is a case where a web application has already been made, it currently features Models and ViewModels and returns the appropriate Views for display on the Web but can be extended into a fully functional service to support programmatic clients? Like AJAX clients etc...with what we already have.

Another thing:

If you take a look at this example https://github.com/ServiceStack/ServiceStack.Examples/blob/master/src/ServiceStack.MovieRest/MovieService.cs

you will see there is Movie Request class and Movies Request class (one for single movie request, the other one for a list of movies). As such, there are also two services, MovieService and MoviesService, one dealing with requests for a single movie, the other one for a genre of movies.

Now, while I like SS approach to services and I think it is the right one, I don't like this sort of separation merely because of the type of request. What if I wanted movies by director? Would I be inventing yet another request class having a Director property and yet another service (MoviesByDirector) for it?

I think the samples should be oriented towards one service. Everything that has to deal with movies need to be under one roof. How does one achieve that with ServiceStack?

public class ProductsService : Service
{
    private readonly IDocumentSession _session;
    private readonly ProductsHelperService _productsHelperService;
    private readonly ProductCategorizationHelperService _productCategorization;

    public class ProductRequest : IReturn<ProductRequestResponse>
    {
        public int Id { get; set; }
    }

    // Does this make sense? 
    // Please note, we use ProductViewModel in our Views and it holds everything we'd want in service response also
    public class ProductRequestResponse : ProductViewModel
    {
    }

    public ProductRequestResponse GetProducts(ProductRequest request)
    {
        ProductRequestResponse response = null;
        if (request.Id >= 0)
        {
            var product = _session.Load<Product>(request.Id);
            response.InjectFrom(product);
        }
        return response;
    }
}
2

2 Answers

69
votes

The Service Layer is your most important Contract

The most important interface that you can ever create in your entire system is your external facing service contract, this is what consumers of your service or application will bind to, i.e. the existing call-sites that often won't get updated along with your code-base - every other model is secondary.

DTOs are Best practices for remote services

In following of Martin Fowler's recommendation for using DTOs (Data Transfer Objects) for remote services (MSDN), ServiceStack encourages the use of clean, untainted POCOs to define a well-defined contract with that should kept in a largely implementation and dependency-free .dll. The benefits of this allows you to be able to re-use typed DTOs used to define your services with, as-is, in your C#/.NET clients - providing an end-to-end typed API without the use of any code-gen or other artificial machinery.

DRY vs Intent

Keeping things DRY should not be confused with clearly stating of intent, which you should avoid trying to DRY or hide behind inheritance, magic properties or any other mechanism. Having clean, well-defined DTOs provides a single source of reference that anyone can look at to see what each service accepts and returns, it allows your client and server developers to start their work straight away and bind to the external service models without the implementation having been written.

Keeping the DTOs separated also gives you the freedom to re-factor the implementation from within without breaking external clients, i.e. your service starts to cache responses or leverages a NoSQL solution to populate your responses with.

It's also provides the authoritative source (that's not leaked or coupled inside your app logic) that's used to create the auto-generated metadata pages, example responses, Swagger support, XSDs, WSDLs, etc.

Using ServiceStack's Built-in auto-mapping

Whilst we encourage keeping separate DTO models, you don't need to maintain your own manual mapping as you can use a mapper like AutoMapper or using ServiceStack's built-in Auto Mapping support, e.g:

Create a new DTO instance, populated with matching properties on viewModel:

var dto = viewModel.ConvertTo<MyDto>();

Initialize DTO and populate it with matching properties on a view model:

var dto = new MyDto { A = 1, B = 2 }.PopulateWith(viewModel);

Initialize DTO and populate it with non-default matching properties on a view model:

var dto = new MyDto { A = 1, B = 2 }.PopulateWithNonDefaultValues(viewModel);

Initialize DTO and populate it with matching properties that are annotated with the Attr Attribute on a view model:

var dto = new MyDto { A=1 }.PopulateFromPropertiesWithAttribute<Attr>(viewModel);

When mapping logic becomes more complicated we like to use extension methods to keep code DRY and maintain the mapping in one place that's easily consumable from within your application, e.g:

public static class MappingExtensions
{
    public static MyDto ToDto(this MyViewModel viewModel)
    {
        var dto = viewModel.ConvertTo<MyDto>();
        dto.Items = viewModel.Items.ConvertAll(x => x.ToDto());
        dto.CalculatedProperty = Calculate(viewModel.Seed);
        return dto;
    }
}

Which is now easily consumable with just:

var dto = viewModel.ToDto();
2
votes

If you are not tied specifically to ServiceStack and just want "fully functional service to support programmatic clients ... with what we already have", you could try the following: Have your controllers return either a ViewResult or a JsonResult based on the request's accept header - Request.AcceptTypes.Contains("text/html") or Request.AcceptTypes.Contains("application/json").

Both ViewResult and JsonResult are ActionResult, so the signature of actions remains the same, and both View() and Json() accept a ViewModel. Furthermore, if you have a ControllerBase you can make a base method (for example protected ActionResult RespondWith(Object viewModel)) which calls either View() or Json() so the change to existing code is minimal.

Of course, if your ViewModels are not pure (i.e. have some html-specific stuff or you rely on some ViewBag magic) then it's a little more work. And you won't get SOAP or other binding types provided by ServiceStack, but if your goal is to support a JSON data interface with minimal code changes to the existing MVC app then this could be a solution.

Lp