Monday, April 18, 2011

Berico Technologies “Micro Effects .NET” Preview

//@Inject("eventPublisher")
  var eventer;

(function () { 
   eventer.publish("Engineers", "Micro Effects has arrived.");
})();
I’m proud to introduce a preview of a very special project I’ve been working on for Berico called “Micro Effects,” an Enterprise Scripting framework for .NET. The goal of Micro Effects is to “dynamicize” business logic in your applications by loading and hosting scripts, replacing classically hardcoded domain logic. Our first supported scripting language is JavaScript, powered by the awesome JINT Engine (JavaScript Interpretter for .NET) [http://jint.codeplex.com/]. Micro Effects, however, is not just a wrapper for a 3rd party engine. At its core, Micro Effects brings a number of capabilities need to make Enterprise Scripting possible:

  • Opinionated framework for managing and reusing scripts.
  • Support for configuring the Script Engine via an IOC/DI container.
  • Dependency and Script management.
  • Script loading and repository functionality.
  • Script “Hot Swapping” support.
  • Numerous examples demonstrating how JavaScript can fit into your architecture.
  • Clean abstraction, allowing different scripting languages and/or engines to exist on the same platform.

Don’t worry Java Engineers, a port is in the works, and will be released with Berico’s forthcoming Next Generation Enterprise Stack (more to come later). We are considering porting the JINT Engine to Java, which will allow the features to remain relatively consistent (half the work is done for us since JINT uses ANTLR). We may also utilize and existing JavaScript parser/hosting environment like Mozilla Rhino.

Why Micro Effects?


Scripting languages typically get a bad rap. They are considered security risks, typically have poor tooling, and often require a large commitment in learning with little gain. In many cases this may be true. I would argue, however, that you probably already use a scripting language of some sort in many different areas of development. Consider web frameworks, like JSP, ASP.NET, and PHP. Perhaps you’re a Ruby or Python engineer. I doubt many would argue these platforms lack their usefulness.

Consider the following problems or needs a scripting language could address:

  • Need to recompile code or restart a server every time application logic changes.
  • Requirement to add extension points (plugin support) into an application.
  • Governance/Repository for application logic; this could allow a change to application logic in a central location to propagate across an entire architecture.
  • The ability to send more than just data across the wire. Consider the possibility of sending a “predicate” that can be evaluated at an Authorization service or Data Source.

For these reasons alone, we have decided to build a scripting platform which can be used across our platform for handling all sorts of things: responding to events, predicate-based security, making decisions within a domain, routing messages in an ESB.

Ok, no more talking; here’s the demo.

Before I start, let me mention that there are only two objects that exist by default in the Micro Effects environment, the “console” object and the “context” object. The Console is simply a Firebug-like logger exposed to the Scripting API. The Console has multiple implementations in Micro Effects, including a DebugConsoleService which writes all information to the Debug output (using the System.Diagnostics.Debug class), a Log4netConsoleService which adapts the Log4NET framework to the console API, and finally, a CompositeConsoleService which sends the console output to a chain of handlers implementing the IConsoleService interface.

The “context” object, on the other hand, is a .NET Dictionary<string, object> containing contextual information about the environment that may enter the API from various points (metadata within scripts, or submitted by a user). The context, however, is not a generic bag of variables in which users should store information. In fact, the Micro Effects engine allows users to set variables, functions and parameters on the engine, and even react to when those properties change.

I will start by demonstrating a number of key pieces of functionality in order of ascending complexity.

Basic Micro Effects

Create an engine with the default environment, register the Log4NET console service, and execute an adhoc script.

//Start with a new Environment
MfxEngine engine = MfxEnvironment.Default
  //Register the Log4Net console service
  .RegisterConsole(typeof(Log4netConsoleService))
  //Get a new (or pooled) engine from the environment
  .GetEngine()
  //Create an adhoc script and load it into the engine
  .Load(new Script()
  {
    //create a ScriptInfo object 
    //(metadata for the script)
    Info = ScriptInfo.Bldr().ID("TestScript").Build(),
    //define the script body
    Body = "console.log('Hello World.');"
  })
  //Run the script
  .Run();

Console Output

2011-04-18T21:22:48.0210000-04:00 [AdHoc] INFO - Hello World.

Now, let’s load a Script from the file system and execute it.

//Get the default environment
MfxEngine engine = MfxEnvironment.Default
 //Register the Log4Net console service
 .RegisterConsole(typeof(Log4netConsoleService))
 //Get a new (or pooled) engine from the environment
 .GetEngine()
 //Load a new script from the file system
 .Load(new FileInfo("JsCapabilitiesTest.js"))
 //Execute the script
 .Run();

External Script [JsCapabilitiesTest.js]

//Simple for loop
for (var i = 0; i < 10; i++) {
    console.log("Hello iteration {0}", i);
}

//Anonymous function
(function () { 
    console.error("Defined an anonymous function and called it!");
})();

//Define a named JavaScript function.
//By the way, this can be called from C#!
function myfunction(){
    console.log("In function");
}

//Call the previously defined function
myfunction();

//Create an anonymous object
var someObject = {
    name: "Bob",
    phone: "1234567890",
    age: 25
};

//Call properties of the anonymous object
console.log("{0}, {1}, {2}", 
    someObject.name, someObject.phone, someObject.age);

Console Output

2011-04-18T21:32:07.2780000-04:00 [AdHoc] INFO - Hello iteration 1
2011-04-18T21:32:07.2790000-04:00 [AdHoc] INFO - Hello iteration 2
2011-04-18T21:32:07.2800000-04:00 [AdHoc] INFO - Hello iteration 3
2011-04-18T21:32:07.2810000-04:00 [AdHoc] INFO - Hello iteration 4
2011-04-18T21:32:07.2820000-04:00 [AdHoc] INFO - Hello iteration 5
2011-04-18T21:32:07.2830000-04:00 [AdHoc] INFO - Hello iteration 6
2011-04-18T21:32:07.2840000-04:00 [AdHoc] INFO - Hello iteration 7
2011-04-18T21:32:07.2840000-04:00 [AdHoc] INFO - Hello iteration 8
2011-04-18T21:32:07.2850000-04:00 [AdHoc] INFO - Hello iteration 9
2011-04-18T21:32:07.2870000-04:00 [AdHoc] ERROR - Defined an anonymous function and called it!
2011-04-18T21:32:07.2900000-04:00 [AdHoc] INFO - In function
2011-04-18T21:32:07.2940000-04:00 [AdHoc] INFO - Bob, 1234567890, 25

Now, let’s load a directory of scripts, in this case there is only one, and we’ll watch the directory for changes. Every time the script is changed on the file system, we will re-run the engine (by registering on the Engine’s StateDirtied event).

//Start with a new Environment
MfxEngine engine = MfxEnvironment.Default
 //Register a new console
 .RegisterConsole(typeof(Log4netConsoleService))
 //Get a new engine instance
 .GetEngine()
 //Load all scripts in this directory and watch for changes
 .LoadAndWatch(new DirectoryInfo("C:\\Scripts"))
 .Run();

//Every time the state is dirtied, let's rerun the example.
engine.StateDirtied += new EventHandler<MfxEngineEventArgs>(
 (sender, eventArgs) => { eventArgs.Engine.Run(); });

//Run forever
while (true) ;

External Script [from C:\Scripts]

console.log("Wow haas!");

console.log("Change");

console.error("Ooops!");

console.fatal("Huge error");

Video Demonstrating dynamic reloading of the script.



Using an IOC Container to define the Environment

//Grab the Spring.NET Application Context
IApplicationContext context = 
  new XmlApplicationContext("Config/MfxEnvironmentConfig.xml");
//Pull the Micro Effects Environment from the context
MfxEnvironment environment = context.GetObject<MfxEnvironment>();
//Nothing new here.
environment.GetEngine()
  .Load(new FileInfo("JsCapabilitiesTest.js"))
  .Run();

Spring Application Context

The Spring Context is rather verbose.  Most of this configuration is handled programatically in the static property MfxEnvironment.Default, but if you want to extend the environment, you will probably want to use an IOC to configure those capabilities.  This example config is a pretty good start if you choose to extend the Micro Effects API.

<?xml version="1.0" encoding="UTF-8"?>
<objects xmlns="http://www.springframework.net"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.net 
                      http://www.springframework.net/xsd/spring-objects.xsd">

  <!-- Define a MicroEffects Environment -->
  <object id="mfxEnvironment" type="com.berico.mfx.MfxEnvironment">
    <!-- This is the object responsible for loading external script dependencies -->
    <property name="ScriptLoader" ref="scriptLoader" />
    <!-- This is the object responsible for loading external service dependencies -->
    <property name="ServiceRegistry">
      <object type="com.berico.mfx.ext.spring.SpringServiceRegistry, com.berico.mfx.ext.spring" />
    </property>
    <!-- Here is a list of classes that implement the IConsoleService (where console
         output from within the script will be directed). -->
    <property name="ConsoleClassNames">
      <list element-type="System.String">
        <!-- This is a facade for System.Diagnostics.Debug -->
        <value>com.berico.mfx.services.DebugConsoleService</value>
        <!-- Log4net Facade -->
        <value>com.berico.mfx.services.Log4netConsoleService</value>
      </list>
    </property>
  </object>

  <!-- Configure the ScriptParserFactory (the guy who delegates parsing to the correct Parser) -->
  <object id="scriptParserFactory" type="com.berico.mfx.parsers.ScriptParserFactory">
    <!-- Set the object responsible for detecting the Script type -->
    <property name="ScriptDetector">
      <object type="com.berico.mfx.parsers.DelegatingScriptTypeDetector">
        <property name="Detectors">
          <list>
            <!-- We only support JavaScript at the moment, so we will register the
                 JavaSciptDetector.  Keep in mind, you don't need to use the 
                 DelegatingScriptTypeDetector; you could simply use this guy. -->
            <object type="com.berico.mfx.parsers.js.JavaScriptDetector" />
          </list>
        </property>
      </object>
    </property>
    <!-- Register Script metadata parsers -->
    <property name="Parsers">
      <list>
        <object type="com.berico.mfx.parsers.js.JavaScriptMetadataParser" />
      </list>
    </property>
  </object>

  <!-- Configure Script Loaders to use the ScriptParserFactory -->
  <object id="stringLoader" type="com.berico.mfx.loaders.StringLoader">
    <property name="SetScriptParserFactory" ref="scriptParserFactory" />
  </object>

  <object id="httpLoader" type="com.berico.mfx.loaders.HttpLoader">
    <property name="SetScriptParserFactory" ref="scriptParserFactory" />
  </object>

  <object id="fileLoader" type="com.berico.mfx.loaders.FileLoader">
    <property name="SetScriptParserFactory" ref="scriptParserFactory" />
  </object>
  
  <!-- Create a Composite Script Loader for the Environment.  These are the actual
       mechanisms used to retrieve external files.                                 -->
  <object id="scriptLoader" 
          type="com.berico.mfx.loaders.CompositeScriptLoader">
    <property name="Loaders">
      <dictionary key-type="System.String" 
                  value-type="com.berico.mfx.IScriptDependencyLoader">
        <entry key="http" value-ref="httpLoader" />
        <entry key="https" value-ref="httpLoader" />
        <entry key="file" value-ref="fileLoader" />
      </dictionary>
    </property>
  </object>
  
</objects>

Using the Spring.NET Factory Method to create new engines

This is probably the best way to use an IOC with Micro Effects.

IApplicationContext context = 
  new XmlApplicationContext("Config/MfxEngineFactory.xml");
//Pull the engine from the context and run it.
context.GetObject<MfxEngine>().Run();

Spring Application Context

We do a couple of fancier things in this context file. First, we reuse the environment from the previous example. First, and probably my most favorite thing about this demo, we define a script within the Spring context! We also create a Service Dependency within the script. A Service Dependency is a requirement the script imposes on the engine, that basically enforces the existence of a variable in the script environment meeting a specific contract (think interface). The engine looks to the environment, specifically the environment's IServiceRegistry to fulfill that requirement. In this context file, we utilize Micro Effect's Spring.NET support to adapt the IApplicationContext to the IServiceRegistry interface (to be honest, Spring was the inspiration for IServiceRegistry). The engine pulls the dependency from the environment (via Spring) and injects it into the script.

You also see the use of the context object. Variables defined in a Script's ScriptInfo object are placed into the context for use by the Script. In this case, we iterate over the context printing the key-value-pair to the console.

<?xml version="1.0" encoding="UTF-8"?>
<objects xmlns="http://www.springframework.net"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.net 
                      http://www.springframework.net/xsd/spring-objects.xsd">

  <import resource="MfxEnvironmentConfig.xml"/>

  <object id="specificEngine" type="com.berico.mfx.MfxEngine"
          factory-object="mfxEnvironment" factory-method="GetEngine">

    <property name="RegisteredScripts">
      <list element-type="com.berico.mfx.Script">
        <object type="com.berico.mfx.Script">
          <property name="Info">
            <object type="com.berico.mfx.ScriptInfo">
              <property name="ID" 
              value="Example of using a factory to create an engine and register a script" />
              <property name="Application" value="MicroEffects Examples" />
              <property name="Component" value="Factory Demo" />
              <property name="Version" value="1.0.0.0" />
              <property name="Properties">
                <dictionary key-type="System.String" value-type="System.Object">
                  <entry key="Property 1" value="Value 1" />
                  <entry key="Property 2" value="Value 2" />
                  <entry key="Property 3">
                    <value>12</value>
                  </entry>
                </dictionary>
              </property>
              <property name="ServiceDependencies">
                <list element-type="com.berico.mfx.ServiceDependency">
                  <object type="com.berico.mfx.ServiceDependency">
                    <property name="Variable" value="calculator" />
                    <property name="Contract" 
            value="com.berico.mfx.examples.services.CalculatorService, com.berico.mfx.examples" />
                  </object>
                </list>
              </property>
            </object>
          </property>
          <property name="Body">
            <!-- Using a CDATA element, we can write scripts in the Spring Config without
                 worrying about escaping characters. -->
            <value>
              <![CDATA[
              
              //Look, we are actually defining a script right here!
              for(var item in context){
                console.log("{0} => {1}", item.Key, item.Value);
              }
              
              console.log("2 + 3 = {0}", calculator.Add(2, 3));
              
              ]]>>
            </value>
          </property>
        </object>
      </list>
    </property>
  </object>

  <object id="calculatorService" 
    type="com.berico.mfx.examples.services.CalculatorService" />
  
  
</objects>

Console Output

2011-04-18T22:26:41.4205000-04:00 [AdHoc] INFO - Property 3 => 12
2011-04-18T22:26:41.5335000-04:00 [AdHoc] INFO - Property 1 => Value 1
2011-04-18T22:26:41.5345000-04:00 [AdHoc] INFO - Property 2 => Value 2
2011-04-18T22:26:41.5395000-04:00 [AdHoc] INFO - 2 + 3 = 5

Aspect-Oriented Micro Effects

In our final demonstration, I wanted to show a more practical application of using Micro Effects. Here we have defined a pointcut on all methods of the IClassBeingAdvised interface. We apply some "Before Advice" on each method, passing the context of the method invocation to the Micro Effects engine. This is performed from within C#, by calling a specific JavaScript method (method name defined in the Spring context).

IApplicationContext context = 
new XmlApplicationContext("Config/AopExample.xml");

IClassBeingAdvised obj = 
  context.GetObject<IClassBeingAdvised>("proxiedTargetObject");

//Call some methods on the object
obj.Initialize();
obj.PrintTheFollowingStatement("Hello Dynamic AOP Handling!");

The class being advised

public class ClassBeingAdvised : IClassBeingAdvised {

  public void Initialize() {
    Debug.WriteLine("Doing some initialization.");
  }

  public void PrintTheFollowingStatement(string statement) {
    Debug.WriteLine(statement);
  }
}

Spring Application Context

We will reuse the Environment configuration and define the script within the body of the application context.

<?xml version="1.0" encoding="UTF-8"?>
<objects xmlns="http://www.springframework.net"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.net/aop"
 xsi:schemaLocation="http://www.springframework.net 
                      http://www.springframework.net/xsd/spring-objects.xsd
                      http://www.springframework.net/aop
                      http://www.springframework.net/xsd/spring-aop.xsd">

  <import resource="MfxEnvironmentConfig.xml"/>

  <!-- The object that will be Proxied with Advice -->
  <object id="targetObject" 
          type="com.berico.mfx.examples.ClassBeingAdvised" />

  <!-- The proxy of the target object -->
  <object id="proxiedTargetObject" 
          type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
    <property name="proxyInterfaces" 
              value="com.berico.mfx.examples.IClassBeingAdvised"/>
    <property name="target" ref="targetObject" />
    <property name="interceptorNames">
      <list>
        <value>beforeAdvisor</value>
      </list>
    </property>
  </object>
  
  <!-- The advising class (we inject the MfxEngine into the advisor) -->
  <object id="beforeAdvisor" 
          type="com.berico.mfx.ext.spring.aop.MfxEngineBeforeAdvice">
    <!-- This is not necessary; the default name 
         method name is "runBeforeAdvice" -->
    <property name="AdvisingMethod" value="runBeforeAdvice" />
    <property name="Engine" ref="aopEngine" />
  </object>

  <!-- The Micro Effects Engine pulled from 
       our previously configured Environment -->
  <object id="aopEngine" type="com.berico.mfx.MfxEngine"
        factory-object="mfxEnvironment" factory-method="GetEngine"
        init-method="Prepare">
    <property name="RegisteredScripts">
      <list element-type="com.berico.mfx.Script">
        <!-- Create a Script to handle the Advice -->
        <object type="com.berico.mfx.Script">
          <property name="Info">
            <object type="com.berico.mfx.ScriptInfo">
              <property name="ID" value="Before Advisor" />
              <property name="Application" value="MicroEffects Examples" />
              <property name="Component" value="AOP Demo" />
              <property name="Version" value="1.0.0.0" />
            </object>
          </property>
          <property name="Body">
            <value>
              <![CDATA[
              
              console.log("Script Initialized");
              
              // This is called when the method is intercepted
              function runBeforeAdvice(method, argsArray, target){
              
                console.log("Target: {0}", target);
                
                //If we have arguments, lets inspect the first
                if(argsArray.Length > 0){
                  console.log("First Argument = '{0}'", argsArray[0]);
                }
                
              }
              
              ]]>>
            </value>
          </property>
        </object>
      </list>
    </property>
  </object>
</objects>

Console Output

2011-04-18T22:42:02.7245000-04:00 [AdHoc] INFO - Target: com.berico.mfx.examples.ClassBeingAdvised
Doing some initialization.
2011-04-18T22:42:02.7435000-04:00 [AdHoc] INFO - Target: com.berico.mfx.examples.ClassBeingAdvised
2011-04-18T22:42:02.7495000-04:00 [AdHoc] INFO - First Argument = 'Hello Dynamic AOP Handling!'
Hello Dynamic AOP Handling!

I hope this preview gives you an idea of the power scripting offers in Enterprise Components and what Berico Micro Effects has to offer. If you are interested in Micro Effects, please leave me a message and I will get in touch with you soon. We plan on releasing Micro Effects as an open source project, but there are still a couple of features I want to implement before a release.

Richard.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.