Building your model/service layer like a REST API?

99 views
Skip to first unread message

Brian G

unread,
Feb 19, 2012, 2:30:10 AM2/19/12
to cfrest
I've got some greenfield development coming up shortly and am thinking
about how this API-enabled application might be built. Has anyone
tried building their model/service layer to be more REST-like? My
thought was that every method call in the service layer would return a
struct that would contain some keys like:

status
data
message (array)

Today, I have a lot of methods in my service layer that return
booleans or void or queries. In this approach, everything would
return what is effectively enough data to generate a REST response.
Internally I would use HTTP response codes to signify success/
failure. So instead of saveFoo() returning true if it succeeds, I
would return 200. But it could also return 404 or 403 or something
else with an associated message or messages (maybe pertaining to
validation problems or database errors).

The goal is not to wire the service layer directly to a REST API but
rather "think" REST all the way through the stack. I think there
could be benefits from using the same API that remote developers will
use (just not over HTTP) so that we're eating our own dog food.


Brian

Kevin J. Miller

unread,
Feb 20, 2012, 8:02:43 AM2/20/12
to cfr...@googlegroups.com
Brian,

I've always used a transferBean to return data from the service layer. Just
a bean with some minimal logic which will allow wrapping any kind of value.
Sounds very much like what you're talking about, only as an object and not a
raw struct, and since it is an object, it would allow you to keep the code
to return specific status codes in one place and have enhanced
response-shaping functionality in one place.

The only potential problem that I could think of is not having 'enough'
status codes to represent all error conditions you'd need to internally,
which would force you to create proprietary ones like a 400.12 or a 500.4,
whatever the hell those are, but I suppose you could still ultimately map
those back to a 'legitimate' set of response codes inside the transferBean.

Here's a typical call which returns a transferBean:

<cfset temp = variables.projectService.someMethod()><!--- returns a
transferBean --->
<cfif temp.hasError()>
<cfthrow type="#temp.getError().type#"
message="#temp.getError().message#"><!--- the error value is typically a
cfcatch var from the service method --->
<cfelse>
<cfset responseThing = temp.getValue()>
</cfif>

And below is the transfer object I've been using for years. I could see you
adding a REST-specific version of the getValue() function, say a
getResponse() method, which returns the struct you're talking about.
Basically, you would return the transferBean with whatever native value
you'd normally return and just add some transformation methods in the bean;
I don't know that I'd feel comfortable modifying my service layer components
so much since all the apis I've done have been integrated within larger
applications and the api and the main app share many of the same components.

<cfcomponent displayname="Transfer Object" hint="A Bean for encapsulating
data transfers between layers">

<cfset variables.instance = structnew()>
<cfset variables.instance.value = "">
<cfset variables.instance.message = "">
<cfset variables.instance.error = structnew()>

<cffunction name="init" access="public" hint="Initializes
the object" output="no" returntype="transferBean">
<cfargument name="value" required="false"
type="any">
<cfargument name="message" required="false"
type="string">
<cfargument name="error" required="false"
type="struct">

<cfif structkeyexists(arguments,"value")>
<cfset setValue(arguments.value)>
</cfif>
<cfif structkeyexists(arguments,"message")>
<cfset setMessage(arguments.message)>
</cfif>
<cfif structkeyexists(arguments,"error")>
<cfset setError(arguments.error)>
</cfif>

<cfreturn this>

</cffunction>

<cffunction name="setValue" access="private" hint="sets the
value property" output="no" returntype="void">
<cfargument name="value" required="yes" type="any">
<cfset variables.instance.value = arguments.value>
</cffunction>
<cffunction name="getValue" access="public" hint="returns
the value property" output="no" returntype="any">
<cfreturn variables.instance.value>
</cffunction>

<cffunction name="setMessage" access="private" hint="sets
the message property" output="no" returntype="void">
<cfargument name="message" required="yes"
type="string">
<cfset variables.instance.message =
arguments.message>
</cffunction>
<cffunction name="getMessage" access="public" hint="returns
the message property" output="no" returntype="string">
<cfreturn variables.instance.message>
</cffunction>

<cffunction name="setError" access="public" hint="sets the
error property" output="no" returntype="void">
<cfargument name="error" required="yes" type="any">
<cfset variables.instance.error = arguments.error>
<cfif not structisempty(arguments.error)>
<cfset request.magicErrorOverrideValue =
true>
</cfif>
</cffunction>
<cffunction name="getError" access="public" hint="returns
the error property" output="no" returntype="any">
<cfreturn variables.instance.error>
</cffunction>

<cffunction name="hasError" access="public" hint="returns
whether or not the object contains error info" output="no"
returntype="boolean">
<cfif structisempty(variables.instance.error)>
<cfreturn false>
<cfelse>
<cfreturn true>
</cfif>
</cffunction>

<cffunction name="getValueDatatype" access="public"
hint="returns the datatype of the object's value" output="no"
returntype="string">
<cfscript>
if(isArray(variables.instance.value)) {
return 'Array'; }
else if(isBinary(variables.instance.value))
{ return 'Binary'; }
else if(isNumeric(variables.instance.value))
{ return 'Numeric'; }
else if(isDate(variables.instance.value)) {
return 'Date'; }
else if(isQuery(variables.instance.value)) {
return 'Query'; }
else if(isStruct(variables.instance.value))
{ return 'Struct'; }
else if(isWDDX(variables.instance.value)) {
return 'WDDX'; }
else if(isJSON(variables.instance.value)) {
return 'JSON'; }
else if(isBoolean(variables.instance.value))
{ return 'Boolean'; }
else
if(isCustomFunction(variables.instance.value)) { return 'Function'; }
else
if(isSimpleValue(variables.instance.value)) { return 'String'; }
else { return 'Unknown'; }
</cfscript>
</cffunction>

</cfcomponent>

I'm not sure I'm 100% with you on thinking REST 'all the way through'. I
think I see what you're saying, but not having tried it, I guess I'd be
afraid that it would limit my flexibility with the service layer when what I
really want to return/am used to returning is a Boolean or a query or a
struct, or what have you. By using the transferBean, you could continue to
have service methods return whatever they 'should' and shape the
REST-focused response struct in the bean itself.

Kevin

> --
> You received this message because you are subscribed to the Google Groups
> "cfrest" group.
> To post to this group, send email to cfr...@googlegroups.com.
> To unsubscribe from this group, send email to
> cfrest+un...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/cfrest?hl=en.

Brian G

unread,
Feb 20, 2012, 12:57:44 PM2/20/12
to cfrest


On Feb 20, 5:02 am, "Kevin J. Miller" <kmil...@websolete.com> wrote:
> I'm not sure I'm 100% with you on thinking REST 'all the way through'.  I
> think I see what you're saying, but not having tried it, I guess I'd be
> afraid that it would limit my flexibility with the service layer when what I
> really want to return/am used to returning is a Boolean or a query or a
> struct, or what have you.  By using the transferBean, you could continue to
> have service methods return whatever they 'should' and shape the
> REST-focused response struct in the bean itself.

Kevin, great feedback. In my struct example, the "data" parameter
would be whatever is in your getValue() so no real difference there.
The only real difference is that you wrap it with some extra detail so
whatever your method normally responds is now always shoehorned into a
struct key "data" or a method getValue() like yours.

I am finding that as I have some client side Javascript functionality
and some server-side functionality, that I'm constantly reshaping the
service layer responses for the client side. I'm also finding that
abstraction can make it difficult to assign the error messages at the
proper level. E.g., I had a VerificationService with a method
getIsMember() that performs a remote API check to see if a memberhsip
is valid. It was returning a boolean which is nice and testable and
lets me know if the requested person has a valid membership. But, if
they aren't valid, the Service itself was unable to really determine
why. But the individual verification implementations that understand
each remote API can do a lot of contextual checks. For example, if
the member number starts with 600 for one organization, that signifies
an expired temporary membership. That's a lot better for an error
message than "your membership didn't validate". In this case, I
refactored so the individual implementations returned a struct with a
success key and a message key so the message could bubble up through
the service layer to make its way back to the end user. This pattern
is what got me thinking that maybe I should just do this across the
board. There's really no downside internally, even the most
simplistic:

<cfif myServiceMethod()>
...
</cfif>

Just becomes:

<cfif myServiceMethod().success>
...
</cfif>

Dunno, brainstorming at this point before I have to make
implementation decisions. :) My current service layer has bled over
into the controller a bit... so I'm thinking about how to push more
smarts into the service layer and programmatic messaging (via a status
code) is one of those places where I think this approach could help.


Brian


Kevin J. Miller

unread,
Feb 20, 2012, 1:53:53 PM2/20/12
to cfr...@googlegroups.com
Alright, I think I'm with you. I'm all for having a consistent, always
there 'status' struct in the response, and I do that currently as well.

In my onRequestStart method, I initialize a 'status' struct in the request
scope which my components' methods set and read during processing:

request.dc.status.success = false;
request.dc.status.haserror = false;
request.dc.status.message = "";
request.dc.status.errorfields = ""; // list of form fields which require
error highlighting, if any

Then, for remoting requests, I will always return this json-serialized
struct in the response, using the request.dc.status struct as the source for
the json response, then I don't have to putz around with crafting error and
success messages, they are already there as the result of the processing.

This status struct always exists both on the server side and in the client
side response it certainly does make things easier, knowing that you always
can rely on it to exist and contain information relevant to the process and
its result.

Here's an example of a jquery callback function I use in ajax form
processing:

function(data) {
var success = false;
var haserror = false;
var message = "";
var errorfields = "";
var j;
try {
j = $.parseJSON(data);
success = j.SUCCESS;
haserror = j.HASERROR;
message = j.MESSAGE;
errorfields = j.ERRORFIELDS;
}
catch(e){
haserror = true;
message = "An unexpected error occurred.";
}
renderAjaxMessage(success,haserror,message,errorfields); //
this just throws up a div to display success or error messaging
}

FWIW, I always force uppercase my json serialized from cf to remove any
question as to case. Anyway, if you're saying that the same struct should
exist from service layer (and other) calls as well as at the final response
to the client, I totally agree with that. I do something similar with my
api responses as well so that there is actionable information to go off of,
and it's also based upon that same basic status struct.

I was looking here http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html to
see if I could better understand what you're saying about using the status
codes at the service layer to denote success/error conditions, and while I
certainly see some alignment between HTTP status codes and common responses
from service calls, I think I'm still not seeing the light on how that move
would be a good thing within the app itself, rather than just when preparing
the response to the client. For example, what would you use for 'required
parameter missing', or the like? Is this even what you're talking about,
using the standard spectrum of HTTP status codes internally to denote the
state of processing at the cf level?

Kevin

.


Kevin J. Miller

unread,
Feb 20, 2012, 2:40:16 PM2/20/12
to cfr...@googlegroups.com
Sorry, let me clarify something:

I return transferBeans not 'status' structs from my service layer to my
handlers (controllers); rather, the handlers make calls to the service
components, get the results of those calls, and update the status struct as
required. My service layer doesn't deal directly with responses to anything
other than other components, so it was misleading to imply that they did,
below.

When I was talking about having a struct start at the server and exist
through to the client response, I was referring to it existing at the
controller level, not service.

Kevin

> -----Original Message-----
> From: cfr...@googlegroups.com [mailto:cfr...@googlegroups.com] On

Brian G

unread,
Feb 21, 2012, 8:37:28 PM2/21/12
to cfrest

On Feb 20, 10:53 am, "Kevin J. Miller" <kmil...@websolete.com> wrote:
> I was looking herehttp://www.w3.org/Protocols/rfc2616/rfc2616-sec10.htmlto
> see if I could better understand what you're saying about using the status
> codes at the service layer to denote success/error conditions, and while I
> certainly see some alignment between HTTP status codes and common responses
> from service calls, I think I'm still not seeing the light on how that move
> would be a good thing within the app itself, rather than just when preparing
> the response to the client.  For example, what would you use for 'required
> parameter missing', or the like?  Is this even what you're talking about,
> using the standard spectrum of HTTP status codes internally to denote the
> state of processing at the cf level?

My thought was that in a service layer you might do something like:

if NOT fooExists(foo)
saveFoo(foo)
return true
else
return false
/if

So instead of returning a boolean, if it were feasible to select an
HTTP response code that made sense in these cases, you could have a
single place that sets the status like:

var response = {message = "", success = true, status = 200, error =
[]}
if NOT fooExists(foo)
saveFoo(foo)
response.status = 201
return response
else
response.status = 409
response.success = false
return response
/if

From a model-glue, coldbox or other normal CF controller, you could
use the res.success or a if (res.status NEQ 201) to check for success
just like using a boolean response. But when it comes to returning
data over REST API, this would eliminate the potentially long and
complicated if-else that appears in every REST controller which
decides what status code to set.

What I like about this is it brings the result of the attempted action
closer to where it actually takes place making it more accurate.
This would permit some very contextual error messages and status codes
that could then be evaluated by a higher-level i18n/l10n service if
needed.

In my membership verification service example, the calls look like
this:

controller -> VerificationService.verifyMember(member) ->
VerifySpecificClub.verifyMember(member)

Back at the controller level, all I was getting was a boolean yes/no,
but when I refactored to return a struct, my organization-specific
implementation was able to do this:

if (left(member_id, 4) EQ 6000)
response.message = 'you are using an expired temporary membership';
elseif (NOT isNumeric(member_id))
response.message = 'your member number for XYZ must be numeric';
elseif (len(member_id) < 4 OR len(member_id) > 8)
response.message = 'your member number is either too short or too
long to be valid';
/if

That's infinitely more useful than the raw true/false, it brings the
context closer to where the decision was made but permits it to bubble
up through the stack. If called from a REST API controller like
PowerNap or Taffy, that might be inserted into an X-Error header and
would be an order of magnitude more useful than "the membership did
not verify".

And if that struct contained responses like 400 (member id could not
be understood), 404 (member not found), whatever might be
appropriate. You are correct in that you may need to create your own
response codes, which I believe is OK and where you would document it
in your API. I don't know that you would need to make them decimals
as there are a lot of unused numbers in the integer space.

Thanks for your thoughts,

Brian
Reply all
Reply to author
Forward
0 new messages