<font face="Default Sans Serif,Verdana,Arial,Helvetica,sans-serif" size="2"><div><br></div><div>In your previous example, you are posting to a certain URL (i.e. /keystone/{ver:=x.0}/{method:=update}).</div><div><div><client: POST /keystone/{ver:=x.0}/{method:=update}> => <middleware: just forward to clients[ver].getattr("method")(**kwargs)> => <keystone: update></div></div><div><br></div><div>Correct me if I'm wrong, but it looks like you have a unique URL for each /service/version/method.</div><div>I fail to see how that is different than what we have today? Is there a view for each service? each version?</div><div><br></div><div>Let's say for argument sake that you have a single view that takes care of all URL routing. All requests pass through this view and contain a JSON that contains instruction on which API to invoke and what parameters to pass.</div><div>And lets also say that you wrote some code that uses reflection to map the JSON to an action. What you end up with is a client-centric application, where all of the logic resides client-side. If there are things we want to accomplish server-side, it will be extremely hard to pull off. Things like caching, websocket, aggregation, batch actions, translation, etc.... What you end up with is a client with no help from the server.</div><div><br></div><div>Obviously the other extreme is what we have today, where we do everything server-side and only using client-side for binding events. I personally prefer a more balance approach where we can leverage both the server and client. There are things that client can do well, and there are things that server can do well. Going the RPC way restrict us to just client technologies and may hamper any additional future functionalities we want to bring server-side. In other words, using REST over RPC gives us the opportunity to use server-side technologies to help solve problems should the need for it arises. </div><div><br></div><div>I would also argue that the REST approach is NOT what we have today. What we have today is a static webpage that is generated server-side, where API is hidden from the client. What we end up with using the REST approach is a dynamic webpage generated client-side, two very different things. We have essentially striped out the rendering logic from Django templating and replaced it with Angular.</div><div><br></div><br><font color="#990099">-----Tihomir Trifonov <t.trifonov@gmail.com> wrote: -----</font><div class="iNotesHistory" style="padding-left:5px;"><div style="padding-right:0px;padding-left:5px;border-left:solid black 2px;">To: "OpenStack Development Mailing List (not for usage questions)" <openstack-dev@lists.openstack.org><br>From: Tihomir Trifonov <t.trifonov@gmail.com><br>Date: 12/12/2014 04:53AM<br>Subject: Re: [openstack-dev] [horizon] REST and Django<br><br><div dir="ltr"><blockquote style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex" class="gmail_quote"><span style="font-family:arial,sans-serif;font-size:12.7272720336914px">Here's an example: Admin user Joe has an Domain open and stares at it for 15 minutes while he updates the description. Admin user Bob is asked to go ahead and enable it. He opens the record, edits it, and then saves it. Joe finished perfecting the description and saves it. Doing this action would mean that the Domain is enabled and the description gets updated. Last man in still wins if he updates the same fields, but if they update different fields then both of their changes will take affect without them stomping on each other. Whether that is good or bad may depend on the situation…</span></blockquote><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif"><br></div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif"><br></div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif">That's a great example. I believe that all of the Openstack APIs support PATCH updates of arbitrary fields. This way - the frontend(AngularJS) can detect which fields are being modified, and to submit only these fields for update. If we however use a form with POST, although we will load the object before updating it, the middleware cannot find which fields are actually modified, and will update them all, which is more likely what PUT should do. Thus having full control in the frontend part, we can submit only changed fields. If however a service API doesn't support PATCH, it is actually a problem in the API and not in the client...</div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif"><br></div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif"><br></div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif"><br></div><blockquote style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex" class="gmail_quote"><span style="font-family:arial,sans-serif;font-size:12.7272720336914px">The service API documentation almost always lags (although, helped </span><span class="im" style="font-family:arial,sans-serif;font-size:12.7272720336914px">by specs now) and the service team takes on the burden of exposing a </span><span style="font-family:arial,sans-serif;font-size:12.7272720336914px">programmatic way to access the API.  This is tested and easily consumable </span><span style="font-family:arial,sans-serif;font-size:12.7272720336914px">via the python clients, which removes some guesswork from using the </span><span class="im" style="font-family:arial,sans-serif;font-size:12.7272720336914px">service.</span></blockquote><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif"><span class="im" style="font-family:arial,sans-serif;font-size:12.7272720336914px"><br></span></div>True. But what if the service team modifies a method signature from let's say:<br><br><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">def add_something(self, request,<div class="gmail_default" style="display:inline">​ field1, field2):</div><br></blockquote></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><br></div></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline">to</div></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><br></div></div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">def add_something(self, request,<div class="gmail_default" style="display:inline">​ field1, field2, field3):</div><br></blockquote></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><br></div></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline">and in the middleware we have the old signature:</div></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><br></div></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">​def add_something(self, request,<div class="gmail_default" style="display:inline">​ field1, field2):</div></blockquote><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><div class="gmail_default" style="display:inline"><br></div></div></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><div class="gmail_default" style="display:inline">we still need to modify the middleware to add the new field. If however the middleware is transparent and just passes **kwargs, it will pass through whatever the frontend sends. So we just need to update the frontend, which can be done using custom views, and not necessary going through an upstream change. My point is why do we need to hide some features of the backend service API behind a "firewall" what the middleware in fact is? </div></div></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><div class="gmail_default" style="display:inline"><br></div></div></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><br></div></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><br></div></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><br></div></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><br></div></div><div><div class="gmail_default" style="font-family:'trebuchet ms',sans-serif;display:inline"><br></div></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Fri, Dec 12, 2014 at 8:08 AM, Tripp, Travis S <span dir="ltr"><<a href="mailto:travis.tripp@hp.com" target="_blank">travis.tripp@hp.com</a>></span> wrote:<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">I just re-read and I apologize for the hastily written email I previously<br> sent. I’ll try to salvage it with a bit of a revision below (please ignore<br> the previous email).<br> <br> On 12/11/14, 7:02 PM, "Tripp, Travis S" <<a href="mailto:travis.tripp@hp.com">travis.tripp@hp.com</a>> wrote<br> (REVISED):<br> <br> >Tihomir,<br> ><br> >Your comments in the patch were very helpful for me to understand your<br> >concerns about the ease of customizing without requiring upstream<br> >changes. It also reminded me that I’ve also previously questioned the<br> >python middleman.<br> ><br> >However, here are a couple of bullet points for Devil’s Advocate<br> <span class="">>consideration.<br> ><br> ><br> >  *   Will we take on auto-discovery of API extensions in two spots<br> >(python for legacy and JS for new)?<br> </span>>  *   The Horizon team will have to keep an even closer eye on every<br> >single project and be ready to react if there are changes to the API that<br> >break things. Right now in Glance, for example, they are working on some<br> >fixes to the v2 API (soon to become v2.3) that will allow them to<br> >deprecate v1 somewhat transparently to users of the client library.<br> >  *   The service API documentation almost always lags (although, helped<br> <span class="">>by specs now) and the service team takes on the burden of exposing a<br> </span>>programmatic way to access the API.  This is tested and easily consumable<br> >via the python clients, which removes some guesswork from using the<br> <span class="">>service.<br> >  *   This is going to be an incremental approach with legacy support<br> </span>>requirements anyway.  So, incorporating python side changes won’t just go<br> >away.<br> >  *   Which approach would be better if we introduce a server side<br> <span class="">>caching mechanism or a new source of data such as elastic search to<br> </span>>improve performance? Would the client side code have to be changed<br> >dramatically to take advantage of those improvements or could it be done<br> >transparently on the server side if we own the exposed API?<br> <span class="">><br> >I’m not sure I fully understood your example about Cinder.  Was it the<br> </span>>cinder client that held up delivery of horizon support, the cinder API or<br> <span class="">>both?  If the API isn’t in, then it would hold up delivery of the feature<br> </span>>in any case. There still would be timing pressures to react and build a<br> >new view that supports it. For customization, with Richard’s approach new<br> >views could be supported by just dropping in a new REST API decorated<br> >module with the APIs you want, including direct pass through support if<br> >desired to new APIs. Downstream customizations / Upstream changes to<br> >views seem a bit like a bit of a related, but different issue to me as<br> >long as their is an easy way to drop in new API support.<br> ><br> >Finally, regarding the client making two calls to do an update:<br> <span class="">><br> >​>>Do we really need the lines:​<br> ><br> >>> project = api.keystone.tenant_get(request, id)<br> >>> kwargs = _tenant_kwargs_from_DATA(request.DATA, enabled=None)<br> >​<br> </span>>I agree that if you already have all the data it may be bad to have to do<br> <span class="">>another call. I do think there is room for discussing the reasoning,<br> >though.<br> >As far as I can tell, they do this so that if you are updating an entity,<br> >you have to be very specific about the fields you are changing. I<br> >actually see this as potentially a protectionary measure against data<br> </span>>loss and sometimes a very nice to have feature. It perhaps was intended<br> >to *help* guard against race conditions (no locking and no transactions<br> >with many users simultaneously accessing the data).<br> ><br> >Here's an example: Admin user Joe has a Domain open and stares at it for<br> >15 minutes while he updates just the description. Admin user Bob is asked<br> <span class="">>to go ahead and enable it. He opens the record, edits it, and then saves<br> </span>>it. Joe finished perfecting the description and saves it. They could in<br> >effect both edit the same domain independently. Last man in still wins if<br> <span class="">>he updates the same fields, but if they update different fields then both<br> </span>>of their changes will take affect without them stomping on each other. Or<br> >maybe it is intended to encourage client users to compare their current<br> >and previous to see if they should issue a warning if the data changed<br> >between getting and updating the data. Or maybe like you said, it is just<br> >overhead API calls.<br> <div><div class="h5"><br> <br> <br> ><br> >From: Tihomir Trifonov <<a href="mailto:t.trifonov@gmail.com">t.trifonov@gmail.com</a><mailto:<a href="mailto:t.trifonov@gmail.com">t.trifonov@gmail.com</a>>><br> >Reply-To: OpenStack List<br> ><<a href="mailto:openstack-dev@lists.openstack.org">openstack-dev@lists.openstack.org</a><mailto:<a href="mailto:openstack-dev@lists.openstack.or">openstack-dev@lists.openstack.or</a><br> >g>><br> >Date: Thursday, December 11, 2014 at 7:53 AM<br> >To: OpenStack List<br> ><<a href="mailto:openstack-dev@lists.openstack.org">openstack-dev@lists.openstack.org</a><mailto:<a href="mailto:openstack-dev@lists.openstack.or">openstack-dev@lists.openstack.or</a><br> >g>><br> >Subject: Re: [openstack-dev] [horizon] REST and Django<br> ><br> >​​<br> >Client just needs to know which URL to hit in order to invoke a certain<br> >API, and does not need to know the procedure name or parameters ordering.<br> ><br> ><br> >​That's where the difference is. I think the client has to know the<br> >procedure name and parameters. Otherwise​ we have a translation factory<br> >pattern, that converts one naming convention to another. And you won't be<br> >able to call any service API if there is no code in the middleware to<br> >translate it to the service API procedure name and parameters. To avoid<br> >this - we can use a transparent proxy model - direct mapping of a client<br> >call to service API naming, which can be done if the client invokes the<br> >methods with the names in the service API, so that the middleware will<br> >just pass parameters, and will not translate. Instead of:<br> ><br> ><br> >updating user data:<br> ><br> >    <client: POST /user/ >   =>    <middleware: convert to<br> >/keystone/update/ >   =>   <keystone: update><br> ><br> >we may use:<br> ><br> >    <client: POST /keystone/{ver:=x.0}/{method:=update} >   =><br> ><middleware: just forward to clients[ver].getattr("method")(**kwargs) ><br> >=>   <keystone: update><br> ><br> ><br> >​The idea here is that if we have keystone 4.0 client, ​we will have to<br> >just add it to the clients [] list and nothing more is required at the<br> >middleware level. Just create the frontend code to use the new Keystone<br> >4.0 methods. Otherwise we will have to add all new/different signatures<br> >of 4.0 against 2.0/3.0 in the middleware in order to use Keystone 4.0.<br> ><br> >There is also a great example of using a pluggable/new feature in<br> >Horizon. Do you remember the volume types support patch? The patch was<br> >pending in Gerrit for few months - first waiting the cinder support for<br> >volume types to go upstream, then waiting few more weeks for review. I am<br> >not sure, but as far as I remember, the Horizon patch even missed a<br> >release milestone and was introduced in the next release.<br> ><br> >If we have a transparent middleware - this will be no more an issue. As<br> >long as someone has written the frontend modules(which should be easy to<br> >add and customize), and they install the required version of the service<br> >API - they will not need updated Horizon to start using the feature.<br> >Maybe I am not the right person to give examples here, but how many of<br> >you had some kind of Horizon customization being locally merged/patched<br> >in your local distros/setups, until the patch is being pushed upstream?<br> ><br> >I will say it again. Nova, Keystone, Cinder, Glance etc. already have<br> >stable public APIs. Why do we want to add the translation middleware and<br> >to introduce another level of REST API? This layer will often hide new<br> >features, added to the service APIs and will delay their appearance in<br> >Horizon. That's simply not needed. I believe it is possible to just wrap<br> >the authentication in the middleware REST, but not to translate anything<br> >as RPC methods/parameters.<br> ><br> ><br> >​And one more example:<br> ><br> >​@rest_utils.ajax()<br> >def put(self, request, id):<br> >    """Update a single project.<br> ><br> >        The POST data should be an application/json object containing the<br> >        parameters to update: "name" (string),  "description" (string),<br> >        "domain_id" (string) and "enabled" (boolean, defaults to true).<br> >        Additional, undefined parameters may also be provided, but you'll<br> >have<br> >        to look deep into keystone to figure out what they might be.<br> ><br> >        This method returns HTTP 204 (no content) on success.<br> >        """<br> >        project = api.keystone.tenant_get(request, id)<br> >        kwargs = _tenant_kwargs_from_DATA(request.DATA, enabled=None)<br> >        api.keystone.tenant_update(request, project, **kwargs)<br> ><br> >​Do we really need the lines:​<br> ><br> >project = api.keystone.tenant_get(request, id)<br> >kwargs = _tenant_kwargs_from_DATA(request.DATA, enabled=None)<br> >​<br> >? ​Since we update the project on the client, it is obvious that we<br> >already fetched the project data. So we can simply send:<br> ><br> ><br> >POST /keystone/3.0/tenant_update<br> ><br> >Content-Type: application/json<br> ><br> >{"id": <a href="http://cached.id" target="_blank">cached.id</a><<a href="http://cached.id" target="_blank">http://cached.id</a>>, "domain_id": cached.domain_id,<br> >"name": "new name", "description": "new description", "enabled":<br> >cached.enabled}<br> ><br> >Fewer requests, faster application.<br> ><br> ><br> <br> </div></div><span class="">_______________________________________________<br> OpenStack-dev mailing list<br> <a href="mailto:OpenStack-dev@lists.openstack.org">OpenStack-dev@lists.openstack.org</a><br> </span><a href="http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev" target="_blank">http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev</a><br> </blockquote></div><br clear="all"><div><br></div>-- <br><div class="gmail_signature">Regards,<br>Tihomir Trifonov</div> </div> <div><font face="Courier New,Courier,monospace" size="3">_______________________________________________<br>OpenStack-dev mailing list<br>OpenStack-dev@lists.openstack.org<br><a href="http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev">http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev</a><br></font></div></div></div></font>