I am trying to wrap my head around ServiceStack
and utilizing it to expose RESTful services.
I am currently using a MVC/Service/Repository/UnitOfWork type pattern where a basic operation to get customers might look like this:
MVC Controller Action --> Service Method --> Repository --> SQL Server
My questions are:
I guess I am a little confused how to make all of this live side-by-side.
Domain Object
public class Customer
{
public int Id {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
}
View Model
public class CustomerViewModel
{
public int Id {get;set;}
public string FirstName {get;set;}
....
}
Controller
public class CustomersController : Controller
{
ICustomerService customerService;
public CustomersController(ICustomerService customerService)
{
this.customerService = customerService;
}
public ActionResult Search(SearchViewModel model)
{
var model = new CustomersViewModel() {
Customers = customerService.GetCustomersByLastName(model.LastName); // AutoMap these domain objects to a view model here
};
return View(model);
}
}
Service
public class CustomerService : ICustomerService
{
IRepository<Customer> customerRepo;
public CustomerService(IRepository<Customer> customerRepo)
{
this.customerRepo = customerRepo;
}
public IEnumerable<Customer> GetCustomersByLastName(string lastName)
{
return customerRepo.Query().Where(x => x.LastName.StartsWith(lastName));
}
}
First of all, and this is just a personal preference, I would get rid of your repository layer and just access/validate your data directly from the service operation(s). There's no point having all these extra layers if all you're doing is passing parameters along.
In answer to your questions:
1) Your service(s) should return DTO(s) (source), you mention you're using an MVC application so make sure you make use of the IReturn interface on your operations, this will allow you to do something like var customers = client.Get(new GetCustomers());
in your controller action, see here. How you use that DTO is upto you, you can use it as a ViewModel if you wish or create a seperate ViewModel should you require further properties from other sources.
2) Yes, ServiceStack IS the service layer in your application, typically you would have all your interaction going through this layer, there is no need for all these different layers (My first point above) for the sake of it, it seem's as if the architecture of this application is way more complicated then it needs to be..
3) I would think so yes, you seem to be over thinking your application, cut out all these layers
In terms of your example above and following the recommendation here. I would do something like this:
Project Structure (These can be folders within one project or separated into different projects dependant on how big your application is:)
Although for small projects with only a few services it's ok for everything to be in a single project and to simply grow your architecture when and as needed.
- SamProject.Web
App_Start
AppHost.cs
Controllers
CustomerController.cs
- SamProject.ServiceInterface
Services
CustomersService.cs
Translators // Mappings from Domain Object > DTO
CustomersTranslator.cs
- SamProject.Data // Assumes using EF
CustomersContext.cs
Customer.cs
- SamProject.ServiceModel
Operations
CustomersService
GetCustomers.cs
GetCustomer.cs
CreateCustomer.cs
Resources
CustomerDTO.cs
Code
DTO:
public class CustomerDTO
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Operation:
[Route("/customers/{Id}")]
public class GetCustomer : IReturn<CustomerDTO>
{
public int Id { get; set; }
}
[Route("/customers")]
public class GetCustomers : IReturn<IEnumerable<CustomerDTO>>
{
public string LastName { get; set; }
}
Service:
public class CustomersService : Service
{
private readonly CustomerContext _dbCustomerContext;
public CustomersService(CustomerContext dbCustomerContext)
{
_dbCustomerContext = dbCustomerContext;
}
public object Get(GetCustomer request)
{
return _dbCustomerContext.Customers
.FirstOrDefault(c => c.Id == request.Id)
.Select(c => c.Translate());
}
public object Get(GetCustomers request)
{
if (string.IsNullOrEmpty(request.LastName))
{
return _dbCustomerContext.Customers.ToList()
.Select(c => c.Translate());
}
return _dbCustomerContext.Customers
.Where(c => c.LastName == request.LastName).ToList()
.Select(c => c.Translate());
}
}
Controller:
public class CustomersController : Controller
{
private readonly JsonServiceClient _client;
public CustomersController(JsonServiceClient client)
{
_client = client;
}
public ActionResult Search(SearchViewModel model)
{
var customers = _client.Get(new GetCustomers
{
LastName = model.LastName
});
return View(customers);
}
}
Notes
Translate
is an extension method which is held in Translators, alternatively you could use the built in translatorServiceModel
I do have a clearly defined folder structure, however, the namespace of my service operations is just SamProject.ServiceModel
not SamProject.ServiceModel.Operations.CustomersService
. My resources, however, are in the SamProject.ServiceModel.Resources
namespace.