Building RESTful Service Layers Presentation with Expanded Answers to Questions
I'd like to publicly thank Doug Smith at DaveRamsey.com for making such a great presentation on RESTful service layers at the CFMeetup. If you haven't heard Doug talk, you should put this presentation on your list to watch in the next couple of week especially if you might build REST APIs (over SOAP) in the future.
DescriptionREST-based service oriented architectures have become a popular pattern for offering services that can be consumed by diverse web clients. Using the architecture of the web itself, RESTful services provide a high performance, robust solution without the unnecessary complexities of SOAP and its related technologies.
In this session, Doug will provide a brief introduction to REST as it relates to web-enabled services, demonstrate a service currently used at daveramsey.com to support their 10M+ pageview per month site, and walk through the code required to implement a RESTful service using ColdFusion 9, Mach-II v1.9, and the new REST Endpoints (created by Doug in partnership with Team Mach-II).
You can watch the recording presentation here (special thanks to Charlie Arehart and the CFMeetup for hosting this event):
Where do you do your authentication/authorization? In the endpoint, or in another layer?
I would typically perform authentication in your REST endpoint. This is because your authentication process is going to be specific to your REST endpoint implementation and not to your service layer directly. It's not really the concern of your service layer to authenticate calls to it but really the responsibility of your REST endpoint to make sure the client is authenticated correctly before calling your service layer. This implementation strategy makes your service layer more flexible as it's not tied to a specific authentication schema and allows you to swap in a new authentication layer in your REST endpoint in the future.
So, for authentication would you call authenticate method from all the secure methods?
Doug touched on the request lifecycle methods available in his presentation a bit, but you could easily do this in the "preProcess" method if you wanted to apply this at a global level. By default, the base REST endpoint takes care of certain things in the "preProcess" like parsing the path info, deciding on the method to call, the type of response format, etc.
Because it's relatively new feature in the BER, Doug didn't mention that Mach-II 1.9 is shipping with a basic HTTP access authentication module. By default, it reads an Apache authentication file for all credentials but this module is easily extensible and you can swap in other credential checking schemas like a database by extending this CFC.
Here is an example of adding basic HTTP access authorization to a REST endpoint. In the "configure" method, you can see we are instantiating a basic auth module and passing in the "realm" (part of the basic authentication standard) and the path to the Apache-style credential file (the documentation explain more about this). Later on in the code, we override the "preProcess" point in the endpoint request lifecycle. We need the code in the base endpoint to run first so we call super.preProcess(arguments.event) before we try to authenticate the request. In this case, if the authentication fails we include an exception template and abort the request. The basic HTTP access authentication module takes care of checking the headers, decoding the auth, checking the credential file and ultimately setting other header if the authentication request fails. This module returns a boolean so you can customize the exception output if the authentication fails.
<!---
INITIALIZATION / CONFIGURATION
--->
<cffunction name="configure" access="public" returntype="void" output="false"
hint="Configures the API endpoint.">
<cfset variables.authentication = CreateObject("component", "MachII.security.http.basic.Authentication").init("Dashboard API", getParameter("apiCredentialFilePath")) />
<cfset super.configure() />
</cffunction>
<!---
PUBLIC METHODS - REQUEST
--->
<cffunction name="preProcess" access="public" returntype="void" output="true"
hint="Runs when an endpoint request begins.">
<cfargument name="event" type="MachII.framework.Event" required="true" />
<cfset super.preProcess(arguments.event) />
<!--- Authenticate the request via HTTP basic authentication --->
<cfif NOT variables.authentication.authenticate(getHTTPRequestData().headers)>
<cfoutput><cfinclude template="/MachII/dashboard/endpoints/Unauthorized.cfm" /></cfoutput>
<!--- This is the one time we don't want the endpoint exception handling to process --->
<cfabort>
</cfif>
</cffunction>
So you mention it is scalable, but how do you make it scale?
Just like any other web application. It is proper form that REST calls are stateless. This means that all calls should sent authentication credentials with each request (if required) and that the server does not maintain a "state" (like a CFML session scope) for the client. This allows your REST endpoint to reside in a cluster of servers without having to tie calls from clients to specific servers.
Doug mentioned that they have rolled out a REST API at DaveRamsey.com using the Mach-II 1.9 M2 release (BER). I believe he said they are handling about 800k requests per day with it.
Do you know how the Mach-II Dashboard handles reloading of endpoints in a clustered environment?
Currently the Dashboard handles applications on a per server basis. For those who don't use Mach-II, the dashboard is a development tool build as a Mach-II module that can help you develop and manage your application. I'll leave the laundry list of features out of this blog post, but check out the Dashboard documentation with screen shots for more information. Getting back to the question. We're build a REST API for the Dashboard so I expect in the future we'll see some awesome cluster support where you can send messages to all your clustered servers.
When structuring xml responses, any tips on avoiding 'bloat' in your service layer?
Try to think of formatting your response in a way that is useful to the client instead of scratching your own desires in how you feel data should be organized. Don't be tied to your database model as they may not be useful to your client. Doug explained they have had great luck using JSON instead of XML because it has less "bloat" to in the response (i.e. smaller). It's not uncommon to offer responses in multiple response formats (XML, JSON, plain text, etc.) as a client that consumes your REST service may be able to work with XML over JSON easier or vice-versa.
Was the response type jason/html handled by base RESTAPI component or you had that in your service?
Yes, the base endpoint looks for certain "file extensions" and automatically sets the correct mime type in the response to the client. By default, can set the default response type if the "file extension" is left off the URI. The type of the response is in the event.getArg('format') event arg.
Have you ever done REST as a wrapper for SOAP in cases where you want to offer the API in both flavors to the world?
Doug answered that they haven't done that, but they have thought about creating REST wrappers for common SOAP services they have to interact with on a daily basis to make their lives better.
There is no WSDL equivilant for REST is there?
Yes, there it's called WADL and it sounds like Jason York at DaveRamsey.com is already looking into creating a documentation engine. Jason, if you're listening... Let's get a WADL documentation engine built into the Dashboard.
