Using Object Oriented Programming For Custom Programming In A SaaS Application

In the SaaS world, often customers will want their own customizations into the source code specific to how they want to do business with your software.

Here is a point where object oriented programming stands out. By crafting your programming in such a way that basic functionality is enhanced - you can use different classes specific to particular customers for enhanced implementation.

Since we are going to potentially be using one of multiple classes, we need to have an interface describing the methods available in the class.

(As a note, in OOP, you usually always want to program to the interface!)

There are two simple id number generating methods available - one for an invoice number and another for a customer number.

interface IfcGenerateID:

  method public character GenerateInvoice().
  
  method public character GenerateCustomer().

end. /* class IfcGenerateID */

Now we describe the generic class that all customers in the SaaS system will run usually.

/* Generic class to generate various IDs in the system. */

class GenerateID implements IfcGenerateID:

  /* Generate an invoice number */
  
  method public character GenerateInvoice():
  
    return string (year(today))
         + "-" 
         + string(month(today))
         + "-" 
         + string(day(today)) 
         + "-"
         + string(etime).
          
  end. /* method GenerateInfo */

  /* Generate an customer number */
  
  method public character GenerateCustomer():
  
    return string (year(today))
         + "-" 
         + string(month(today))
         + "-" 
         + string(day(today)) 
         + "-"
         + string(etime).
          
  end. /* method GenerateCustomer */
  
end. /* class GenerateID */

As you can see, this is pretty simple and straight forward in how it generates id numbers - based on the date and time.

However, now you have a client named Amduus who wants to modify how these numbers are created. They want to have an I in front of the invoice number and a C in front of the customer number. This way it is more obvious it is an invoice number or a customer number.

The usual development process is put in place and a new class based on the old class (inheriting) is created with these special behaviors specific to that customer:

/* Amduus customization class to generate various IDs in the system. */

class AmduusGenerateID inherits GenerateID implements IfcGenerateID:

  /* Generate an invoice number - precede with an I so people using */
  /* number automatically associate it as an invoice number.        */
  
  method override public character GenerateInvoice():
  
    return "I" + super:GenerateInvoice().
          
  end. /* method GenerateInfo */

  /* Generate an customer number - precede with a C so people using */
  /* number automatically associate it as a customer.               */
  
  method override public character GenerateCustomer():
  
    return "C" + super:GenerateCustomer().
    
  end. /* method GenerateCustomer */
  
end. /* class GenerateID */

Of course, now the question is - how do I tell the SaaS application which class to run for which customer?

This is the purpose of the dynamic new statement. Since we programmed to an interface, any variable defined as that interface can use any object that implements that interface.

Here we have a variable that identifies the customer as "Amduus." Any custom classes for that customer will be prefixed with Amduus*.cls so we know that code is specific to that customer.

Then it is merely a matter of searching for that specific class and instancing or defaulting to the original class:

define variable Customer as character no-undo.

define variable GenerateID as IfcGenerateID no-undo.


/* Set the Customer so we pull up the correct cls in the dynamic-new */

Customer = "Amduus".

/* Pull up the appropriate class to instance */

if search (Customer + "GenerateID.cls") <> ? then
  GenerateID = dynamic-new Customer + "GenerateID" ().
else 
  GenerateID = dynamic-new "GenerateID" ().
  
display GenerateID:GenerateInvoice() format "x(30)".

display GenerateID:GenerateCustomer() format "x(30)".

delete object GenerateID.

So you see, this is a way to enhance objects from base objects for the purpose of altering their behavior based on the customer using the program.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
tamhas's picture

One problem with this

One problem with this approach, of course, is that DYNAMIC-NEW destroys ones ability to trace the call graph of the application.

One can avoid this by having customized classes local to the customer, either just a replacement for the standard class or better to provide subclasses which are standard customization points. A customer without any customizations has a subclass which is a simple facade for its superclass while a customer with customizations has those added to their version of the subclass. This is completely traceable as long as one focuses on a customer at a time and, in fact, the overall call graph is the same for all customers, it is just that some of the nodes in that call graph have different content.

Really, there is nothing unique to SaaS or even OO about this. For eons I have been maintaining a standard code distribution at all customer sites and each customer site has a directory on the propath where they can have individual code modules which are either modified versions of standard modules or which supplement standard modules. When there is an upgrade to the base, one does have to check what has happened to any that are customized, but in many cases little or no change is required. Proper use of facades would make this easier yet since the facade in the standard implementation will never have any content change unless there are new methods or properties.


I agree, however...

The SaaS scenario being addressed is a hosted application shared by multiple customer bases for a web application.

In this model there is no such thing as a local installation.

Also, the goal is to have a single code base, so that one does not need to re-work n number of code where n is the number of customers for fundamental changes.

One can have, upon identifying the user and their organization in the hit, a reworking of the propath to have different code by the same name as you suggest - however I think that will introduce the n points of reworking code problem again.


tamhas's picture

It is all a matter of

It is all a matter of tradeoffs. Put in hooks, dynamic-new, propath alteratives, data driven runs, case statements, whatever are all ways to ***not*** have a ***completely*** common code base while retaining as common a code base as possible.

While I have not deployed a SaaS application, in many ways my approach with my customers was similar to SaaS requirements, i.e., semi-continuous incremental upgrades of a common code base, no big version shifts, and minimal site specific code needing custom attention.

The core design practice, which perhaps I should have mentioned, is to take every customer customization request and attempt to drill through the customer idiosyncratic presentation to the underlying business problem. Very often, for anything beyond obvious issues like form layout and such, it is possible to see the problem as one that might be an issue for not just the one business. Then, one designs the solution into core software and provides configuration options to select alternate behaviors. Every time one achieves that, which for me was most of the time, then there is no custom code to invoke. Moreover, with good design instincts, it also meant that there was a new feature available for someone else to use, often for a different purpose. Also, this design approach tends to lead to thinking of ways to generalize solutions instead of matching them exactly to the original request, which means they are much more likely to be able to adapt with the original requestor's changing requirements over time.

To me, anything that clouds the call graph is anathema because of the negative impact on one's ability to analyze the empirical application. If you can't analyze it, except by laborious manual tracing, then you have added a huge burden to the maintenance and evolution of the app.