Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

A Programmer's Guide to Starting a Software Company and Building an Enterprise Application - Article 6

4.38/5 (8 votes)
15 Dec 2010MPL12 min read 39.7K  
Commercial Open-Source licensing and Business Rules Engine

Introduction

This is the sixth in a series of columns in which I tell you how I started SplendidCRM Software, Inc. I hope that my entrepreneurial experience inspires you. In my opinion, the creation of a company can be a wonderful adventure.

Article 6

At SplendidCRM, we are changing the license that we use for our Community Edition and this event seemed like a good time to explain our licensing strategy. First, let me make it clear that SplendidCRM Software is a business, and like most businesses, we are in business to make money. Although this statement is somewhat counter to the open-source culture, we were not the first to enter the commercial open-source world, nor will we be the last. The general problem with commercial open-source is that it violates the general business philosophy that the source code is the key intellectual property of a software business, and that a software entrepreneur should protect it at all costs.

Before I explain our new approach to licensing, it might be helpful if I provided some history. SplendidCRM started as a re-implementation of SugarCRM based on the Microsoft technology stack. We use the word "re-implementation" and not "port" because we did not use any of the source code from SugarCRM in the SplendidCRM product. Porting PHP was not practical because, at the time, PHP was not object oriented. But the real reason we re-implemented the software was that we had extensive experience using Microsoft-based enterprise-software patterns that we felt were far superior to the design used by the original SugarCRM developers. One key pattern that differentiates SplendidCRM from SugarCRM is the use of SQL objects, such as stored procedures and views, for business logic. I have discussed the use of stored procedures in a previous article, but I mention them here because they represent source code that we consider to be the foundation of our intellectual property.

For the last 5 years, we have released 4 major versions of SplendidCRM. Much of our ASP.NET source code has been released under the SugarCRM Public License (http://www.sugarcrm.com/crm/SPL), but we have been using a dual license approach, which is common among commercial open-source vendors. The SugarCRM Public License was derived from the Mozilla Public License, with the primary difference being that the word Mozilla was replaced by the word SugarCRM. At this point, you may wonder why we did not create a SplendidCRM Public License by following the same approach. There were two reasons. The first was that we used the original SugarCRM images, icons and style sheets so we were legally bound to include the SugarCRM Public License in conjunction with these files. The second reason was purely for marketing purposes. While the SugarCRM Public License does not grant permission to use the SugarCRM trademarks, we felt that we were able to use their trademark by always using it in the context of license attribution. SugarCRM effectively forced us to use their trademark by creating the SugarCRM Public License.

It is important to note that, until now, we have not released any of our SQL source code in the Community Edition. We have always distributed encrypted versions of the SQL code and we have kept the SQL objects in the database in encrypted form. We did this for two reasons: the first was so that we had a business case for our customers to license the Professional Edition, which included all of the SQL source code. The second reason was to prevent SugarCRM from taking our key intellectual property and using it in their product. Now that 5 years have passed, it seems clear that the dynamic SQL design of SugarCRM works well for them and that the SQL object design of SplendidCRM works well for us. We are therefore no longer concerned that SugarCRM would have any interest in our SQL code, though they probably could learn a few things from it, like how to properly upgrade a database from a previous version of the app.

In July 2007, SugarCRM released their Community Edition using the GPLv3 license. On the surface, this seemed like a magnanimous gift, but we take a more business-centric view that sees it as a form of intellectual property protection. The release of SugarCRM on GPL effectively stopped companies like SplendidCRM Software from using any files from the software, even images or icons. The problem with the GPL license, paraphrasing Steve Ballmer, is that it acts as a virus infecting any software that uses any part of the package. So, whereas we were free to use any single file licensed under the SugarCRM Public License, if we used anything under a GPL license, we would immediately be required to release all the source code, including the SQL objects, under the GPL license.

It is our belief that SugarCRM moved to the GPLv3 license to prevent competitors from using any of their code. Our theory is that no for-profit business would want to create a derived product that includes the requirement that all new intellectual property must also be made public. At this point, you may be thinking that SugarCRM, Inc. is in the same boat, for example, they now need to release to the public all their Professional and Enterprise source code. But here is the beauty of being a software vendor: you have the right to release your source code under multiple licenses, without the concern of one license affecting the other. Whereas the Open Software Foundation encourages all vendors to release all their code under a public license, they have no problem with a company releasing the same source code under multiple licenses or dual-licenses.

In April 2010, SugarCRM released their 6.0 Community Edition on the Affero GNU Public License v3, AGPLv3 for short. This move to AGPLv3 further protects their code against competitors who would customize the Community Edition but not redistribute. You see, the key difference between GPLv3 and AGPLv3 is that a vendor does not need to share derived works of a GPLv3 project so long as the vendor does not "distribute" the changes. So while the top brass at SugarCRM would have you believe that they simply want to ensure that the Community Edition source code is shared by all, we believe that their goal is to prevent all forms of competition.

At this point, you may be thinking that we are just a disgruntled competitor complaining that we can no longer steal from a primary competitor, but nothing can be farther from the truth. We actually have great respect for the management team at SugarCRM. We have been following them closely for 5 years and we can see how successful their business practices have been. For obvious reasons, we try to adopt those same business practices with the hopes of achieving the same success. The main difference is that our target audience consists of companies that prefer Microsoft software and the SugarCRM target audience consists of companies that do not like Microsoft software.

Now that you know the history of software licenses for SplendidCRM and SugarCRM, it will come as no surprise that we are releasing SplendidCRM 5.0 Community Edition under the AGPLv3 license. We do this with the full knowledge that we will likely prevent any commercial venture from using the SplendidCRM 5.0 Community Edition as the basis of their services. At this point, it is important to note that we have a partner program that uses an entirely different software license and allows our partners to create custom services without the requirement to share their intellectual property.

You may wonder why we bother to release any source code if we are going to work so hard to prevent competitors from using it. First, the source code acts as a marketing tool for those enterprises that need a powerful CRM that they can customize. As these enterprises do generally need to customize the CRM, they typically want to see the source code so that they can determine if it has the right level of quality and if the design patterns will fit within their existing environment. The second reason for releasing the source code is to share our experiences with other developers. We cannot share everything, because there would be no reason for enterprises to buy, but we can share some of our key design patterns and cool implementations.

Speaking of cool stuff, with SplendidCRM 5.0, we have integrated Microsoft's Business Rules Engine with all of our editions, including our Community Edition. Business Rules are something we would normally only include with our Professional Edition or Enterprise Edition, but the solution was so simple and elegant, that we could not resist sharing it with the world.

Business Rules Engine

What are business rules? In the context of a CRM, a “business rule” is a way for a non-programmer to define a condition and apply some logic. It is probably easier to explain by way of an example. Let us say you are re-arranging your sales regions and that you need to reassign all Accounts in California from Region 1 to Region 2. You would create a rule that uses the condition (STATE = 'California' or STATE = 'CA') and that applies the logic (REGION = 'Region 2'). Scott Allen has a good article on Rules and Conditions at http://odetocode.com/Articles/458.aspx.

Rules are pretty cool and incredibly powerful, but, as a programmer, how do you allow an end-user to apply this logic? How do you allow this logic while not violating the security model of the application? This is where the Microsoft Rules Engine comes into the picture. Based on the dearth of hits from Google, it seems clear that the Microsoft Rules Engine is not well used. That is a shame because it is very powerful and very easy to use. The Microsoft Rules Engine came with the Windows Workflow Foundation but whereas the Workflow Engine is complicated, the Microsoft Rules Engine is simple.

SplendidCRM uses the rules engine in four areas. The first area is called the Rules Wizard and it provides the end-user with a way to apply rules to all records of a specific module. The second area is called Business Rules and it is an admin-only feature that allows rules to be applied to an EditView, DetailView or GridView so that rules can be applied to the user interface. The third area is the Reporting Engine, where rules can be applied to the result set prior to rendering the report. The fourth area is the Workflow Engine, where fields can include calculations. The Rules Wizard is the easiest of the areas to describe, so this article will focus on the Rules Wizard. The Rules Wizard applies rules to a set of records, so the first step is to create a data table and fill it with records. The following code is typical within SplendidCRM. It is probably a bit more complicated than you are used to because we use the common ADODB.NET interfaces so that the same code will work across multiple database providers.

C#
DbProviderFactory dbf = DbProviderFactories.GetFactory();
using ( IDbConnection con = dbf.CreateConnection() )
{
    string sSQL;
    sSQL = "select * vwACCOUNTS";
    using ( IDbCommand cmd = con.CreateCommand() )
    {
        cmd.CommandText = sSQL;
        using ( DbDataAdapter da = dbf.CreateDataAdapter() )
        {
            ((IDbDataAdapter)da).SelectCommand = cmd;
            using ( DataTable dtCurrent = new DataTable() )
            {
                da.Fill(dtCurrent);
            }
        }
    }
}

I am going to jump to the end and show you the loop where we process the rules for each row in the data table. The most important thing to notice about the following code is the use of a special object called SplendidWizardThis (an object that we will shall call the This object). The rules need to be applied to something, and our special object is the something that the rules engine will modify. The rules engine simply applies to the rules to the This object and it is up to you to do whatever you want with the results. Behind the scenes, I am sure that the rules engine uses reflection to determine what it can or cannot do with the This object. The use of the This object is also important because it establishes a sandbox that will contain the rules engine. Having a sandbox is important because you do not want to provide a hacker with the ability to corrupt the database.

C#
RuleValidation validation = 
	new RuleValidation(typeof(SplendidWizardThis), null);
RuleSet rules = RulesUtil.BuildRuleSet(dtRules, validation);
foreach ( DataRow row in dt.Rows )
{
    SplendidWizardThis swThis = new SplendidWizardThis(row);
    RuleExecution exec = new RuleExecution(validation, swThis);
    rules.Execute(exec);
}

The following code is a simplified version of our special This object. The actual SplendidCRM version has additional properties that are used to manage field-level security, but that is an advanced topic that you can research later. For now, you will notice that our special This object simply wraps a DataRow object and provides a single accessor. This simple object effectively creates a sandbox that only allows the rules engine to get and set values in a single data row.

C#
public class SplendidWizardThis
{
    private DataRow Row;
    
    public SplendidWizardThis(DataRow Row)
    {
        this.Row = Row;
    }
    
    public object this[string columnName]
    {
        get { return Row[columnName]; }
        set { Row[columnName] = value; }
    }
}

At this point, I am going to take a major shortcut. I am going to assume that you can create your own entry screens for the rules so that I can jump to the part where a rule set is created. The BuildRuleSet help function takes a data table that contains a collection of rules and turns it into a rule set that can be executed. I have just a few comments. First, you will want to make sure that the Rule Names are unique (because an exception will be thrown if they are not). Second, set the priority based on the order that you want the rules to be run and make sure that the rule is active. Third, you will almost always want to set the Reevaluation Behavior to Never. This is a subtle setting in that a value of Always could lead to an endless loop. It is simply better to use Never.

C#
public static RuleSet BuildRuleSet(DataTable dtRules, RuleValidation validation)
{
    RuleSet     rules  = new RuleSet("RuleSet 1");
    RulesParser parser = new RulesParser(validation);
    foreach ( DataRow row in dtRules )
    {
        string sRULE_NAME    = Guid.NewGuid().ToString();
        string sCONDITION    = row["CONDITION"   ] as string;
        string sTHEN_ACTIONS = row["THEN_ACTIONS"] as string;
        string sELSE_ACTIONS = row["ELSE_ACTIONS"] as string;
        
        RuleExpressionCondition condition      = 
        	parser.ParseCondition    (sCONDITION   );
        List<ruleaction>        lstThenActions = 
        	parser.ParseStatementList(sTHEN_ACTIONS);
        List<ruleaction>        lstElseActions = 
        	parser.ParseStatementList(sELSE_ACTIONS);
        System.Workflow.Activities.Rules.Rule r = 
        	new System.Workflow.Activities.Rules.Rule
        		(sRULE_NAME, condition, lstThenActions, lstElseActions);
        r.Priority = 0;
        r.Active   = true;
        r.ReevaluationBehavior = RuleReevaluationBehavior.Never;
        rules.Rules.Add(r);
    }
    return rules;
}

The real heart of BuildRuleSet are the values for the Condition, Then and Else actions. This is where things become less obvious. It turns out that Microsoft did not make it easy to parse statements, but some clever folks used reflection and figured out how to use the Rules Parser outside of a workflow. My special thanks goes to Beau Crawford for posting an article on the subject; please see URL http://beaucrawford.net/post/Hacking-into-the-Windows-Workflow-Rules-Engine.aspx.

C#
public class RulesParser
{
    private static ConstructorInfo m_ctorParser               = null;
    private static MethodInfo      m_methParseCondition       = null;
    private static MethodInfo      m_methParseStatementList   = null;
    private static MethodInfo      m_methParseSingleStatement = null;
    private object objParser = null; 
    static RulesParser()
    {
        Assembly asmActivities = Assembly.GetAssembly
        	(typeof(System.Workflow.Activities.Rules.RuleValidation));
        Type     typParser     = asmActivities.GetType
        	("System.Workflow.Activities.Rules.Parser");
        m_ctorParser               = typParser.GetConstructor
        	(BindingFlags.Instance | BindingFlags.NonPublic, null, 
        	new Type[] { typeof(System.Workflow.Activities.Rules.RuleValidation) }, null);
        m_methParseCondition       = typParser.GetMethod
        ("ParseCondition"      , BindingFlags.Instance | BindingFlags.NonPublic);
        m_methParseStatementList   = typParser.GetMethod
        ("ParseStatementList"  , BindingFlags.Instance | BindingFlags.NonPublic);
        m_methParseSingleStatement = typParser.GetMethod
        ("ParseSingleStatement", BindingFlags.Instance | BindingFlags.NonPublic);
    }
    public RulesParser(RuleValidation validation)
    {
        objParser = m_ctorParser.Invoke(new object[] { validation } );
    }
    public RuleExpressionCondition ParseCondition(string expressionString)
    {
        try
        {
            return m_methParseCondition.Invoke(objParser, new object[] 
            	{ expressionString } ) as RuleExpressionCondition;
        }
        catch(TargetInvocationException ex)
        {
            // Instead of displaying "Exception has been thrown by the 
            // target of an invocation.", 
            // catch the error and return the more useful inner exception. 
            throw ex.InnerException;
        }
    }
    public List<ruleaction> ParseStatementList(string statementString)
    {
        try
        {
            return m_methParseStatementList.Invoke(objParser, new object[] 
            	{ statementString } ) as List<ruleaction>;
        }
        catch(TargetInvocationException ex)
        {
            throw ex.InnerException;
        }
    }
    public RuleAction ParseSingleStatement(string statementString)
    {
        try
        {
            return m_methParseSingleStatement.Invoke
            (objParser, new object[] { statementString } ) as RuleAction;
        }
        catch(TargetInvocationException ex)
        {
            throw ex.InnerException;
        }
    }
}

Now that we have all the components in place, we can define a run and run it against a data table. For example, the following rule will set the country to USA if it is not defined:

C#
Condition: this["COUNTRY"] == DBNull.Value 
Then: this["COUNTRY"] = "USA" 

Notice the use of this. The this in the code refers to the SplendidWizardThis. I hope that you have enjoyed our sixth article in the series. For a complete demo of SplendidCRM with the Rules Engine, please visit http://demo.splendidcrm.com. The login is will/will.

License

This article, along with any associated source code and files, is licensed under The Mozilla Public License 1.1 (MPL 1.1)