Saturday, August 27, 2016

SknServices: An Alternate Development Strategy for Rails Applications


SknServices Strategy

Introducing SknServices

This github hosted Rails 5 project, https://github.com/skoona/SknServices, is an small example of core authentication and  authorization security processes; and a real example of an alternate development strategy for Rails based web applications.

Rails enables an MVC-based Web Framework.  The MVC pattern is a classic and used with success, as is, for many applications.  However, there are many maintenance, scaling, and growth issues when the code set exceeds a certain size or feature mix.  We understand these growth issues and have chosen to apply a more traditional Domain Driven Design bias to implementing applications using Rails.  Bias here means to loosely align with DDD and OO principals, but we do whats needed to get the job done.

SknServices strategy involves treating all of Rails as a Web Library for our application.  Ordinarily you would not write your application by embedding it inside a library of any kind, so a rethink of the Rails Way is required.

ReThink: How would you structure your application differently if you chose not to embrace MVC?


SknServices metaphorically takes one step back from the Rails MVC Way.  Rather than add code to controllers, views, and data model we implement a classic OO application structure; right now we refer to it as a Service Strategy.  

To make the connection back to Rails: 
  1. Router/Controller entry-point methods must make one call to an application service, and that application service will return one bundle of results data packaged as a ResultsBean.  The results bundle will generally have three keys; #message, #success, and #payload.
  2. Views accept the #payload and expect to find booleans telling it which page elements have been authorized for display, and the data as basic ruby values/objects need to populate on-screen elements.
  3. Several Application Helpers have been created to bridge between the application and the controller or view methods.  This enables application services to convert named routes, etc without have direct knowledge of the controller or ActiveView.
This now means that authorization, feature logic and flow, etc has to be done is our application through one services method invocation per request.  Shouldn't be a surprise, this is where our code goes. 


To enable the controller to find our services, a #service_factory is added to the base ApplicationController, a #method_missing? implemented to handle delegation.  This ServiceFactory contains the initialization methods for all services, adapters, and provider classes in our application.  All SknServices components have access to this central ServiceFactory.  

The service method invoked from the controllers entry-method is defined in a ProcessService class.  The rethink allows us to gather page level feature methods into one class, it should be natural to combine them by the process they are related too.  AccountService, PolicyService, QuoteService, ContentService, MessageService might be names that your application would create as ProcessService class names.  These methods are required to:
  1. Parse the incoming params, collect and package the resulting data into a ResultsBean for the controller/view.
  2. Alway return a proper answer to the controller, which requires it catch any exception thrown by lower level routines
  3. Call one domain level method, through inheritance, to generate response data.
As indicated a ProcessServices class inherits from a ProcessDomain class.  Domain classes have a collection of methods that actually do the work to generate a valid and pre-authorized response for this application entry-point.  Again these methods are likely topical and related to a single business process.  If ActiveRecord/ActiveModel work is required for this process step the domain method will call the appropriate data provider through the service factory to retrieve the business object needed.  Providers return only business object; never a AR record instance, just attribute bundles.  If part of the process step work requires specialized processing, a TaskAdapter may be invoked to complete the work needed for this request.  All processed results are returned to the service methods and packaged for the view.

Role-based authorization, Profile-based content authorization, and Warden/Rack-Attach Authentication were also implemented as Rails independent components.  The example application is a minimal shell highlighting a content authorization strategy for protecting downloadable assets.  You will find that regular Rails CRUD is still used in some places, but the majority of the app uses the Services Strategy.

This is how I refactor Rails code.  If this has your interest, read the code in the github project.  It also includes a PDF with specific guidance for what to expect in the code.

I'll update this post with more details.  For now, let me know your thoughts in the comments.


James,

No comments:

Post a Comment