Tuesday, March 22, 2011

Dynamic Application Logic: Hosting JavaScript within .NET

Having one platform transfer and execute code on another is certainly not a new concept. As you read this post, JavaScript sent by the web server is performing numerous tasks (loading and rendering content, running animations, handling UI events). Despite the ubiquity of executing application logic from external sources, there is a stigma towards developing desktop and server applications using the same pattern. Fortunately, old beliefs are giving way to a new generation of technologies, many of them "cloud"-based, founded on the concept of distributing logic amongst nodes at runtime.


Hosted logic offers an alternative to (or can supplement) declarative (XML) workflow technologies where system tasks are composed of a graph of pre-generated operations.  In the example above, the 'cloud' represents the portion of the architecture hosting the dynamic application logic.  The host sandboxes that application logic, preventing it from executing code outside the privileges granted to the process. Simply hosting an application isn't enough though.  For this to be a truly compelling solution, the hosted application needs to be able to work with the services and repositories within the architecture.  We do this by injecting the appropriate service and repository clients into the hosted application so it may perform tasks on those components.  Finally, the component references (services and repositories) could be further extracted or adapted for use by the hosted application by attaching the runtime to the bus (at some level).  More interestingly, imagine transferring application logic amongst services and repositories much like we do data today.

To demonstrate the possibilities of hosting external code and wiring it into your application at runtime, I've built a demonstration application in C# using the extremely excellent JavaScript Interpretter for .NET (Jint) [http://jint.codeplex.com/].  For the purposes of this demo, I'm going to keep things simple by only using one model class and one component to manage that model class (a repository).  Since the notion of a user task is quite simple for everyone to understand, I will start by defining our demo's notion of this construct:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace com.berico
{
  /// <summary>
  /// A simple model definition representing user tasks
  /// </summary>
  public class Task
  {
    /// <summary>
    /// Default Constructor
    /// </summary>
    public Task() { }

    /// <summary>
    /// ID of the Task
    /// </summary>
    public long ID { get; set; }

    /// <summary>
    /// User the Task is Assigned To
    /// </summary>
    public string Owner { get; set; }

    /// <summary>
    /// A Summary of what needs to be done
    /// </summary>
    public string Summary { get; set; }

    /// <summary>
    /// Description of the task
    /// </summary>
    public string Description { get; set; }

    /// <summary>
    /// The drop-dead date for task completion
    /// </summary>
    public DateTime Expiration { get; set; }

    /// <summary>
    /// An optional URL to a form that should be used to 
    /// complete the task
    /// </summary>
    public string FormUrl { get; set; }

    /// <summary>
    /// Returns the internal state of the object, formatted
    /// </summary>
    /// <returns>formatted string of the object's state</returns>
    public override string ToString()
    {
      StringBuilder sb = new StringBuilder();
      sb.AppendLine("com.berico.Task {");
      sb.AppendLine(
        string.Format("  ID: {0}", this.ID));
      sb.AppendLine(
        string.Format("  Owner: {0}", this.Owner));
      sb.AppendLine(
        string.Format("  Summary: {0}", this.Summary));
      sb.AppendLine(
        string.Format("  Description: {0}", this.Description));
      sb.AppendLine(
        string.Format("  Expiration: {0}", this.Expiration));
      sb.AppendLine(
        string.Format("  Form URL: {0}", this.FormUrl));
      sb.AppendLine("}");
      return sb.ToString();
    }

  }
}

Here is the contract for our repository. I'm going to use some of C#'s more advanced functional features to reduce the amount of code necessary for interacting with the Repository. More importantly, you will find that we can actually use anonymous JavaScript functions to fulfill the runtime requirements of these delegates/predicates used by the Repo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace com.berico
{
  public interface ITaskRepository
  {
    /// <summary>
    /// Create a new task
    /// </summary>
    /// <param name="owner">Person the task 
    ///    is assigned to</param>
    /// <param name="summary">Summary of the Task</param>
    /// <param name="description">Verbose 
    ///    description of the task</param>
    /// <param name="expiration">Expiration time of the task</param>
    /// <param name="formUrl">Location of a form 
    ///    for completing the task</param>
    /// <returns>A new Task</returns>
    Task Create(string owner, string summary, string description, 
              DateTime expiration, string formUrl);

    /// <summary>
    /// Get a Task by ID
    /// </summary>
    /// <param name="id">ID of the task</param>
    /// <returns>A Task with the submitted ID or Null</returns>
    Task Get(long id);

    /// <summary>
    /// Get all tasks that match the provided filter
    /// </summary>
    /// <param name="filter">Filter method</param>
    /// <returns>List of tasks that the filter applies to</returns>
    List<Task> Get(Func<Task, bool> filter);

    /// <summary>
    /// Update a task in the repository (Task must exist in the
    /// repository)
    /// </summary>
    /// <param name="task">Updated Task</param>
    void Update(Task task);

    /// <summary>
    /// Update many tasks using an update predicate
    /// </summary>
    /// <param name="updatePredicate">Predicate that will update
    /// tasks (completely dependent on your own logic.</param>
    void Update(Action<Task> updatePredicate);

    /// <summary>
    /// Delete a task having the supplied id
    /// </summary>
    /// <param name="id">ID of the task</param>
    void Delete(long id);

    /// <summary>
    /// Delete all tasks that match the supplied filter
    /// </summary>
    /// <param name="filter">Predicate</param>
    void Delete(Func<Task, bool> filter);

    /// <summary>
    /// Find the Aggregate value of something against tasks in the
    /// Repo
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="map">Function for selecting 
    ///   or calculating</param>
    /// <param name="reduce">Function for 
    ///   reducing selected values</param>
    /// <returns>Aggregate Value</returns>
    T Aggregate<T>(Func<Task, T> map, Func<IList<T>, T> reduce);
  }
}

My naive implementation of the Task Repository backed by a Dictionary (obviously nothing is going to persist after the program terminates).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace com.berico
{
  /// <summary>
  /// A little in-memory Repository of Tasks backed by a Dictionary
  /// </summary>
  public class VolatileTaskRepository : ITaskRepository
  {
    //Auto incrementing strategy.
    private static long id = 0;

    //Our task repository is backed by a dictionary
    private Dictionary<long, Task> 
      taskstore = new Dictionary<long, Task>();

    /// <summary>
    /// Create a new task
    /// </summary>
    /// <param name="owner">Person the task is 
    ///   assigned to</param>
    /// <param name="summary">Summary of the Task</param>
    /// <param name="description">Verbose description 
    ///   of the task</param>
    /// <param name="expiration">Expiration time of 
    ///   the task</param>
    /// <param name="formUrl">Location of a form for 
    ///   completing the task</param>
    /// <returns>A new Task</returns>
    public Task Create(string owner, string summary, 
                       string description, DateTime expiration, 
                       string formUrl)
    {
      Task task = new Task()
      {
        ID = ++id,
        Owner = owner,
        Summary = summary,
        Description = description,
        Expiration = expiration,
        FormUrl = formUrl
      };

      taskstore.Add(task.ID, task);

      return task;
    }

    /// <summary>
    /// Get a Task by ID
    /// </summary>
    /// <param name="id">ID of the task</param>
    /// <returns>A Task with the submitted ID or Null</returns>
    public Task Get(long id)
    {
      return taskstore[id];
    }

    /// <summary>
    /// Get all tasks that match the provided filter
    /// </summary>
    /// <param name="filter">Filter method</param>
    /// <returns>List of tasks that the filter applies to</returns>
    public List<Task> Get(Func<Task, bool> meetsFilter)
    {
      List<Task> filteredTasks = new List<Task>();

      foreach(Task task in taskstore.Values)
      {
        if (meetsFilter(task))
        {
          filteredTasks.Add(task);
        }
      }
    
      return filteredTasks;
    }

    /// <summary>
    /// Update a task in the repository (Task must exist in the
    /// repository)
    /// </summary>
    /// <param name="task">Updated Task</param>
    public void Update(Task task)
    {
      if (taskstore.ContainsKey(task.ID))
      {
        taskstore[task.ID] = task;
      }
    }

    /// <summary>
    /// Update many tasks using an update predicate
    /// </summary>
    /// <param name="updatePredicate">Predicate that will update
    /// tasks (completely dependent on your own logic.</param>
    public void Update(Action<Task> updatePredicate)
    {
      foreach (Task task in taskstore.Values)
      {
        updatePredicate(task);
      }
    }

    /// <summary>
    /// Delete a task having the supplied id
    /// </summary>
    /// <param name="id">ID of the task</param>
    public void Delete(long id)
    {
      if (taskstore.ContainsKey(id))
      {
        taskstore.Remove(id);
      }
    }

    /// <summary>
    /// Delete all tasks that match the supplied filter
    /// </summary>
    /// <param name="filter">Predicate</param>
    public void Delete(Func<Task, bool> meetsFilter)
    {
      List<long> idsToDelete = new List<long>();
      foreach (Task task in taskstore.Values)
      {
        if (meetsFilter(task))
        {
          idsToDelete.Add(task.ID);
        }
      }

      foreach (long id in idsToDelete)
      {
        taskstore.Remove(id);
      }

    }

    /// <summary>
    /// Find the Aggregate value of "whatever" against 
    /// tasks stored in the Repo
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="map">Function for selecting 
    ///   or calculating</param>
    /// <param name="reduce">Function for reducing 
    ///   selected values</param>
    /// <returns>Aggregate Value</returns>
    public T Aggregate<T>(Func<Task, T> map, 
                Func<IList<T>, T> reduce)
    {
      IList<T> items = new List<T>();

      foreach (Task task in taskstore.Values)
      {
        items.Add(map(task));
      }

      return reduce(items);
    }

  }
}

Now let's setup the runtime environment. The Jint API is extremely simple, to run a script, you only need to construct the object and call the "Run" method: new JintEngine().Run(script);. By default, the interpreter will not expose any objects in your environment to the script, and will ensure the hosted application is executed at the Lowest .NET Trust Level. You can even isolate the script host from using the .NET Framework entirely, but this would prevent us from exposing our services to the script. In our case, we not only want to expose the Task Repository, but also the Console.WriteLine and String.Format functions to the script so we can see the script's output.

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using Jint;

namespace com.berico
{
  public class JintTestDrive
  {
    private JintEngine jsEngine;

    public JintTestDrive()
    {
      //Instantiate the Engine
      this.jsEngine = new JintEngine();

      //Register the print and format functions, 
      //and the task repository
      this.jsEngine.SetFunction("print", 
        new Action<object>(Console.WriteLine));

      this.jsEngine.SetFunction("format", 
        new Func<string, object, string>(String.Format));

      this.jsEngine.SetParameter("taskRepo", 
        new VolatileTaskRepository());
    }

    static void Main(string[] args)
    {

      ///////////////////////////////////////////////////////
      //   Repository Usage                //
      ///////////////////////////////////////////////////////

      Console.WriteLine("Simple Repository Usage.\n\n");

      string simpleRepoUsageJavaScript = 
        File.ReadAllText("SimpleRepoUsage.js", Encoding.UTF8);

      new JintTestDrive().Run(simpleRepoUsageJavaScript);

      Console.WriteLine("Press Any Key to Continue...");
      Console.ReadKey();

      ///////////////////////////////////////////////////////
      //   Aggregate Example                 //
      ///////////////////////////////////////////////////////

      Console.WriteLine("Aggregate Usage.\n\n");

      string aggregateExampleJavaScript = 
        File.ReadAllText("AggregateExample.js", Encoding.UTF8);

      new JintTestDrive().Run(aggregateExampleJavaScript);

      Console.WriteLine("Press Any Key to Continue...");
      Console.ReadKey();
    }

    public void Run(string logic)
    {
      var returnData = this.jsEngine.Run(logic);
      Console.WriteLine(returnData);
    }
  }
}

You will notice that there are two different script demonstrations executed in the Main method. The first demonstrates the use of standard repository functions, though it does show you how to pass an anonymous function to the repository's Get(), Update() and Delete() methods. The final demonstration is a little more advanced, showing how a little functional programming can result in powerful capabilities for your script (in this case, I show you how to implement MapReduce using a blend of JavaScript and C# [inspired by CouchDB]).

I have to credit my wife who magically solved a coding issue of mine. The funny thing is that she doesn't even know why it worked!

EXAMPLE 1: SimpleRepoUsage.js

/**
 * Dump all tasks in the List to the Console.
 * @param tasks Task objects that should be dumped to
 *  the console.
 */
function printTasks(tasks) {
  for (var task in tasks) {
    print(task);
  }
}

/**
 * Get all tasks from the Task Repo using
 * the a predicate that selects every item
 * @return a list of all the tasks in the Repo
 */
function getAllTasks() {
  return taskRepo.Get(
    function (taskItem) {
      return true;
    });
}

print("\n\nCREATING TASK");

// Create a new Task using the Task Repository 
// injected into the Session
var task1 = taskRepo.Create(
      "Richard", "Do this",
      "This needs to get done now",
      System.DateTime.Now.AddHours(4), 
      "http://www.bericotechnologies.com/form?name=dothis");

// The "format" function is a binding to the 
// .NET String.Format method.
print(
  format(
    "\n\nWE SHOULD HAVE A TASK ID NOW: {0}", task1.ID));

print("\n\nUPDATING TASK");

// Set the owner of the task to "Bob"
task1.Owner = "Bob";

// Update the Task in the Repository
taskRepo.Update(task1);

print("\n\nGETTING THE FIRST TASK AFTER AN UPDATE");

// Get the updated task from the Repo (we want to ensure both
// the repo's Update and Get methods worked correctly)
var updatedTask = taskRepo.Get(task1.ID);

print(updatedTask);

// Create another task
var task2 = taskRepo.Create(
      "Richard", "Do this other thing",
      "You can have this done within 2 days",
      System.DateTime.Now.AddDays(2),
      "http://www.bericotechnologies.com/form?name=dothis2");

// Create a third task
var task3 = taskRepo.Create(
      "Richard", "Do you ever get anything done?",
      "Hurry up on this one!",
      System.DateTime.Now.AddMinutes(1),
      "http://www.bericotechnologies.com/form?name=dothisNOW");

print("\n\nGETTING ALL TASKS");

// Show all the tasks in the repo (verify the
// new tasks are there)
printTasks(getAllTasks());

print("\n\nGETTING ALL OF RICHARD'S TASKS");

// Do a predicate search against the repository
// looking for all tasks assigned to Richard
var richardsTasks = taskRepo.Get(
    function (taskItem) {
      if (taskItem.Owner == "Richard") {
        return true;
      }
      return false;
    });

// Dump Richard's Tasks to the Console
printTasks(richardsTasks);

print("\n\nDELETING THE FIRST TASK");

// Delete the first task (assigned to Bob)
// from the repo.
taskRepo.Delete(task1.ID);

print("\n\nGETTING ALL TASKS AFTER A DELETE");

// Verify the task was deleted
printTasks(getAllTasks());

// Use a predicate to perform a conditional update on Tasks
// in the repository
taskRepo.Update(
  function (task) {
    if (task.Owner == "Richard") {
      task.Summary = "Postpone a month!";
      task.Description = "I'll get to it.";
      task.Expiration = System.DateTime.Now.AddMonths(1);
    }
  });

print("\n\nGETTING ALL TASKS AFTER AN UPDATE");

// Verify the task was deleted
printTasks(getAllTasks());

// Let's delete tasks using a predicate to
// select those that should be deleted
taskRepo.Delete(
  function (task) {
    return task.Owner == "Richard";
  });

print("\n\nGETTING ALL TASKS AFTER A FILTERED DELETE");

// Ensure there are no tasks left and that the previous delete
// operation was correct
printTasks(getAllTasks());

The output for the standard repository operations.

CREATING TASK

WE SHOULD HAVE A TASK ID NOW: 1

UPDATING TASK

GETTING THE FIRST TASK AFTER AN UPDATE
com.berico.Task {
  ID: 1
  Owner: Bob
  Summary: Do this
  Description: This needs to get done now
  Expiration: 3/23/2011 1:55:29 AM
  Form URL: http://www.bericotechnologies.com/form?name=dothis
}

GETTING ALL TASKS
com.berico.Task {
  ID: 1
  Owner: Bob
  Summary: Do this
  Description: This needs to get done now
  Expiration: 3/23/2011 1:55:29 AM
  Form URL: http://www.bericotechnologies.com/form?name=dothis
}

com.berico.Task {
  ID: 2
  Owner: Richard
  Summary: Do this other thing
  Description: You can have this done within 2 days
  Expiration: 3/24/2011 9:55:29 PM
  Form URL: http://www.bericotechnologies.com/form?name=dothis2
}

com.berico.Task {
  ID: 3
  Owner: Richard
  Summary: Do you ever get anything done?
  Description: Hurry up on this one!
  Expiration: 3/22/2011 9:56:29 PM
  Form URL: http://www.bericotechnologies.com/form?name=dothisNOW
}

GETTING ALL OF RICHARD'S TASKS
com.berico.Task {
  ID: 2
  Owner: Richard
  Summary: Do this other thing
  Description: You can have this done within 2 days
  Expiration: 3/24/2011 9:55:29 PM
  Form URL: http://www.bericotechnologies.com/form?name=dothis2
}

com.berico.Task {
  ID: 3
  Owner: Richard
  Summary: Do you ever get anything done?
  Description: Hurry up on this one!
  Expiration: 3/22/2011 9:56:29 PM
  Form URL: http://www.bericotechnologies.com/form?name=dothisNOW
}

DELETING THE FIRST TASK

GETTING ALL TASKS AFTER A DELETE
com.berico.Task {
  ID: 2
  Owner: Richard
  Summary: Do this other thing
  Description: You can have this done within 2 days
  Expiration: 3/24/2011 9:55:29 PM
  Form URL: http://www.bericotechnologies.com/form?name=dothis2
}

com.berico.Task {
  ID: 3
  Owner: Richard
  Summary: Do you ever get anything done?
  Description: Hurry up on this one!
  Expiration: 3/22/2011 9:56:29 PM
  Form URL: http://www.bericotechnologies.com/form?name=dothisNOW
}

GETTING ALL TASKS AFTER AN UPDATE
com.berico.Task {
  ID: 2
  Owner: Richard
  Summary: Postpone a month!
  Description: I'll get to it.
  Expiration: 4/22/2011 9:55:29 PM
  Form URL: http://www.bericotechnologies.com/form?name=dothis2
}

com.berico.Task {
  ID: 3
  Owner: Richard
  Summary: Postpone a month!
  Description: I'll get to it.
  Expiration: 4/22/2011 9:55:29 PM
  Form URL: http://www.bericotechnologies.com/form?name=dothisNOW
}

GETTING ALL TASKS AFTER A FILTERED DELETE


The next example demonstrates two different MapReduce operations, exposed by the Aggregate() function of the Task Repository. You will probably notice the funky brackets in between the function name and the braces "()". Jint forces you to declare the value of the Generic type, and does this through a nonstandard (ECMAScript standard) use of brackets "{}" with the specified fulfilling type. For instance, taskRepo.Aggregate{com.berico.Task} will return a Task object back at the end of the Aggregate operation.

EXAMPLE 2: AggregateExample.js

// Create some tasks
print("CREATING SOME TASKS.\n\n");

taskRepo.Create(
      "Richard", "Schedule Vacation",
      "You need one, and soon.",
      System.DateTime.Now.AddHours(8),
      "http://www.bericotechnologies.com/form?name=dothis2");

taskRepo.Create(
      "Richard", "Where did all the love go?",
      "Buy flowers and candy for the wife.",
      System.DateTime.Now.AddMonths(5),
      "http://www.bericotechnologies.com/form?name=dothisNOW");

taskRepo.Create(
      "Bob", "Need this now.",
      "TPS Coversheets are mandatory Bob!",
      System.DateTime.Now.AddMinutes(5),
      "http://www.bericotechnologies.com/form?name=dothisNOW");

taskRepo.Create(
      "Richard", "Immediate Tasker",
      "Figure out cloud strategy.  Do we really need SOA?",
      System.DateTime.Now.AddMinutes(1),
      "http://www.bericotechnologies.com/form?name=dothisNOW");

taskRepo.Create(
      "Bob", "Buy Plane Tickets",
      "Richard needs a vacation, get him tickets to Australia.",
      System.DateTime.Now.AddDays(2),
      "http://www.bericotechnologies.com/form?name=vacation");

// Let's count all tasks for Richard in the Repository
var numOfTasksForRichard =
  taskRepo.Aggregate{System.Int32}(

    function (task) {
      return (task.Owner == "Richard") ? 1 : 0;
    },

    function (markers) {
      var total = 0;
      for (var mark in markers) {
        if (mark == 1) {
          total++;
        }
      }
      return total;
    });

//Display the number of tasks for Rich
print(format("Number of tasks for Richard = {0}\n\n", 
    numOfTasksForRichard));

// Find the Earliest Task in the Repository
var earliestTask = 
    taskRepo.Aggregate{com.berico.Task}(
    
    function (task){
      return task;
    },

    function (tasks){
      var earliest = tasks[0];
      for(var task in tasks){
        if(task.Expiration < earliest.Expiration){
          earliest = task;
        }
      }
      return earliest;
    });


//Dump the task
print(earliestTask);
The output from our aggregate operations.
CREATING SOME TASKS.

Number of tasks for Richard = 3

com.berico.Task {
  ID: 7
  Owner: Richard
  Summary: Immediate Tasker
  Description: Figure out cloud strategy.  Do we really need SOA?
  Expiration: 3/22/2011 9:56:33 PM
  Form URL: http://www.bericotechnologies.com/form?name=dothisNOW
}
I hope you found this post interesting. I really do believe this sort of hosted application interpretation and executing is the future of distributed computing, but also have their place in API's that have a need for scripting or easy to implement extension points. I also want to thank the creators of Jint. I evaluated a number of JavaScript interpreters for the purpose of this post, and Jint was the only one mature enough to support the functionality I need; in fact, I was shocked at some of the features it did have (allowing me to pass anonymous functions in JavaScript as .NET delegates was a major surprise!).
Happy coding,
Richard

3 comments:

  1. amazing demo, and a bunch of thanks to Jint!
    I'm using Jint in a project, where I write the configurable rule-like logics in javascript files, and evaluate them exactly the same way you demoed in this post.
    Thanks for sharing!

    ReplyDelete
  2. Pink, thank you for your comments.

    ReplyDelete
  3. TT’s Autospreader is one of the most widely used spread tools largely because it delivers sophisticated functionality and extreme performance in a very user-friendly package. Hosted Lync Providers

    ReplyDelete

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