Hi Demis,
On Friday, 9 December 2011 12:56:40 UTC-5, mythz wrote:
I try to promote a single message-based DTO approach to designing and invoking services.
There is major usability and re-usability advantages by having every service accept a Request DTO and conversationally returning a Response DTO (with Results and Exceptions in predictable properties).
This has major ripples through the entire use and design of your services from understanding how each service behaves, usage by your Service Clients, being able to proxy/delegate or drop your Request DTO into a sub-service or MQ for async message processing etc.
So what you're saying is that keeping each request as a single DTO simplifies the use of certain patterns on the clients, such as using a queue. Okay, I will somewhat buy that, but the client still needs to know the URL to send the DTO to..
Basically leaving everything as 'Message based' is a powerful concept that I will be keeping, as soon as you start to map Web Service Calls to an RPC method signature you are no longer 'sending a Request DTO' to a service, you're instead trying to invoke that particular method signature - it becomes much more tightly coupled and you're ability to evolve that service becomes hindered as any changes on the server effectively means the client needs to change as well and marshaling to a Method signature tends to mean you'll need to have client code-gen in order to provide a typed, succinct API. You'll also need to do this for every service client you want to support (i.e. JSON/XML/JSV/MQ/etc) unlike the elegant approach of the generic Service Clients bundled with ServiceStack.
I don't understand what you're saying here. What in the SS pattern prevents the client from needing to change if the server changes? As far as I can tell, the client still has to know the URLs, and still has to know the DTO.
If you change the URL endpoint on the server, the client has to change -- so I'm failing to see why my proposed pattern is any different. In existing SS and what I'm proposing, you can make a change to the server that requires the clients to change, and you can also do some changes that are backwards compatible.
So whether you have 0 properties, 1 property or 20 properties they all work the same way and IMO there is not much boilerplate in doing:
public class ResetMovies {} for an empty DTO.
I don't have any problem with the DTO boilerplate directly. What I find awkward is the connection between the DTOs, services, and routing. I consider the URL and HTTP verb to be effectively one and the same, so what bugs me is the fact that the URL is defined by routing, and the verb is defined by the service code implementation (and also, possibly, the routing). On top of that, the service is not connected directly to the routing -- it's connected implicitly via the DTO.
If you prefer the RPC approach for services you should easily be able to implement this in a base-class using your own attributes / conventions where you marshal the Request DTO to your sub class method calls (likely the approach you're thinking about already).
To be clear, I'm not trying to do an RPC approach. I think the difference is that I like to think of the URL first (or in other words, the API of the service). I want direct control of how the URL maps to handler code. With the current SS implementation, I feel like the URL is a by-product of everything else, because it's so convoluted to connect to the handler code.
To illustrate this, I think: "Ok, I want GET /movies to be a list of all movies, POST /movies to add a new movie, GET /movies/{id} to load a single movie, PUT /movies/{id} to edit that movie, and POST /movies/reset to reset the sample database." Arguably /movies/reset is RPC-style, but it's inevitable that you'll eventually want to tell the server to do actions, and personally I feel that is still a RESTful way to do it.
In SS now, I have to now do a second step. For each distinct request, I need to think about what the request DTO is going to be, and then I have to map that DTO to a URL. Then, I have to go back and map each request DTO to the IService that can handle it.
It's complicated to actually define how a URL maps to code
There are 2 ways to map a Route with your Request DTO it sounds like you would prefer the 2nd option, where the route is more cohesively kept with your Request DTO e.g:
[RestService("/users/{Id}")]
public class Users { ... }
Personally I think the convention of not specifying the Verb means it applies to all HTTP Verbs is an acceptable default convention that's natural to assume.
Yeah sorry, I should have pointed this out in my example since it is closer to what I was showing. I do agree, in this design, not specifying a verb having the meaning of all verbs is very natural.
There is no way (that I have seen so far) to distinguish between a parameter passed in the URL and POST/PUT'd as part of the request:
You can either have 2 Id Properties for cases when you think they will be different (personally I think this is situation should be rare):
[RestService("/users/{ForId}")]
public class Users { int ForId, int Id, ... }
Or alternatively inspect the HttpRequest to determine which Id was passed in the Request Path, i.e:
var httpReq = base.RequestContext.Get<IHttpRequest>();
httpReq.Path; //httpReq.AbsoluteUri, etc.
In my opinion, both of these methods are total hacks. In the first method, you STILL have the same problem -- what happens when a client specifies ForId as part of the request DTO and the URL? Actually that is a rhetorical question - it doesn't matter, because as a user of servicestack, there is no clear answer. I'd have to read the SS code and/or documentation, or figure it out by trial and error. I am suggesting there is a better API that removes this ambiguity, and makes a natural and supported way to get at these properties.
There's a couple of things I would do differently, here's my version:
[RestService("/users/{Id}")]
[RestService("/users/{Id}/{Method}")] /* [DataContract] not needed, Drop Request suffix */
public class Users {
public int Id { get; set; }
public string Method { get; set; }
}
public class UserService : RestServiceBase<Users> {
public MyServiceLayer Svc { get; set; }
public override object OnGet(Users request) {
if (request.Method == "load")
return Svc.LoadUser(request.Id)
...
}
}
I personally don't like this at all. This now looks like RPC: you have a verb in the URL. I know it's somewhat hypocrtical of me to say this after suggesting "/movies/reset", but there is no HTTP verb for "reset all movies", while there is a verb for load (GET).
I also really don't like having (request.Method == "load") in the service code. It feels to me like the framework only did 80% of the work it should have - it mapped the request object into service code by looking at the URL and mapping parameters, but then it left the final 20% up to the service implementation. It just seems like this is something that the framework should do for you, the actual code a user of the framework should write should be pure service implementation -- leave the mapping of the request to my code up to the framework.
I am considering this as an extension on top of SS or an alternative built-in method (depending if it gets accepted into the project), not a total replacement for the existing interface.
If you're more comfortable with this approach then I see no harm in developing this even if it's just for yourself (a lot of the features in ServiceStack is to scratch my own itch as well).
Personally I wont be using this feature since I don't think the boilerplate reduction justifies the added delegation/complexity and less debuggability but if there is a strong demand for this I can include it in the ServiceStack.ServiceInterface project.
Hope this helps clarify some of the philosophy behind ServiceStack.
It does somewhat, but I don't really get the full motivation yet. Since no matter what, you're still tied to the URL, any attempt to abstract that away actually seems counter-intuitive.
In the end, I'm just looking to simplify mapping between URLs and service code. What I was hoping to get out of this thread is an understanding of why having a DTO is beneficial, but so far I don't get it. You briefly mentioned using queues, and I think were hinting at abstracting away the implementation (which I guess means the URLs the client points at?) but I fail to understand how it's possible to have the client ignore the URL, and why knowing the URL (and even having parameters in it) cause any problems on either the client or server. Is there an example somewhere you can point me at, where it's clear that the single request DTO is beneficial, and it's not possible to do it using my proposed code?
Am I still completely missing something here?
Thanks,
Greg