Sunday, September 17, 2006

DataObjects.Net v4.0 components: Aspects

We're going to widely use aspects \ weaved methods in DataObjects.Net v4.0. And it seems at least some of them can be developed as independent components (earlier we planned to call them Aspector Extenders, but since it looks like we'll use PostSharp, the common name of such components is the subject of question now).

At least the following functionality will be based on aspects:
- Online transactions (transactional methods)
- Offline transactions
- Undo\Redo (business methods, rollback methods)
- Tracing.

Top three features are actually quite dependent on each other, so most likely they'll be implemented altogether.

Here is example code of famous Account class and its Transfer method (rollbackable & fully transactional both in online and offline modes):

public class Account : ... 
{
  [Persistent]
  [Indexed]
  // No proxies in v4.0; property getter & setter bodies of
  // persistent properties should also be replaced by
  // Aspector \ PostSharp.
  public double Balance {get {return 0}; set {return};}

  ...
  
  [RollbackableMethod]
  // Any rollbackable method is always [Transactional] one
  public void Transfer(Account accountTo, double amount)
  {
    if (accountTo==this)
      throw new InvalidOperationException("Accounts are the same.");
    if (amount<=0)
      throw new InvalidOperationException("Illegal amount.");
    if (Balance<amount)
      throw new InvalidOperationException("Illegal amount.");
    
    RollbackServices.ActiveMethod.Description =
      String.Format("Transferring ${0} from {1} to {2}", amount, this, accountTo);
    RollbackServices.ActiveMethod.RollbackInfo.Add("accountTo", accountTo);
    RollbackServices.ActiveMethod.RollbackInfo.Add("amount",    amount);

    accountTo.Balance += amount;
    Balance -= amount;
  }

  [RollbackMethod]
  // Internal binding to Transfer method is performed by name.
  // Any rollback method is also a [Transactional] one and
  // rollbackable one.
  [RollbackableMethod(Wrapper = true)]
  // "Wrapper" means that altough method is rollbackable, it doesn't
  // "pushes" rollback data itself, but forwards this part to other
  // rollbackable methods called from it. Such method doesn't require
  // rollback method: system should just rollback all rollbackable
  // methods called from it.
  // So generally such method only sets the description of action it
  // performs; actual job is always performed by the methods it calls.
  internal void RollbackTransfer(RollbackableMethodCallDescriptor mcd)
  {
    // It shouldn't be necessary to set
    // RollbackServices.ActiveMethod.Description, since it
    // can be easily pre-set by caller of this method, i.e.
    // by RollbackableMethodCallDescriptor.Rollback(...)
    Account accountFrom = (Account)mcd.Target;
    Account accountTo   = (Account)mcd.RollbackInfo["accountTo"];
    double  amount      = (double) mcd.RollbackInfo["amount"];
    accountTo.Transfer(accountFrom, amount);
  }
}

Friday, September 15, 2006

PostSharp as Aspector?

Earlier I talked about "Aspector" - component of DataObjects.Net 4.0 that should inject proxy code directly into assemblies with persistent types. But recently we've found PostSharp - http://www.postsharp.org/projects/postsharp, and quite probably we'll use it instead of developing our own component. Currently I'm not sure if it will cover all our needs, but by the first look it covers may be 80% of them.

So what features we want to use?

Aspect code injection:

  • Possibility to apply aspects on any method type (incl. private \ internal methods, property getters \ setters, constructors, etc.)
  • Possibility to inject prologue \ epilogue as invocation of corresponding method
  • Possibility to pass complete method call information into prologue \ epilogue methods
  • Exception handling support in epilogue
Aspect binding:
  • "Apector Extender" concept: Aspector "applies" all discovered IAspectorExtenders to each class \ method in processed assembly
  • IAspectorExtender declares two key methods: ApplyAspects(SomeTypeInfp ti), ApplyAspects(SomeMethodInfp mi). These methods are invoked for each class \ method in processed assembly
  • Obviously there should be an API allowing to "apply" the aspect to any method in processed assembly. Something like Aspector.ApplyAspect(SomeMethodInfo mi; MethodInfo prologue; MethodInfo epilogue)
  • Aspector locates (loads) extenders based on:
    - Assembly-level attributes of processed assembly or on its references. E.g. [AspectorExtender(typeof(MyExtender), ExtenderBindingType.BindToDependentAssemblies)]
    - Type\method-level attributes applied to any type in processed assembly, as well as to its base types \ supported interfaces (e.g. [AspectorExtender(Type extenderType, ExtenderBindingType.BindToTarget | ExtenderBindingType.BindToDescendants)]
Other:
  • Appropriate modification .PDB files along with processed assemblies - it should be possible to debug any "aspected" assembly as before
  • Generics support - it should be possible to add aspects to generic types and methods
  • Possibility to distinguish top-level and base method calls (for virtual and overridden methods). Normally aspect code shouldn't be invoked when base method is called from overridden one, although both of them may contain the same injected code of prologue \ epilogue method invocation.
Let's look on unified (but rough) code of "aspected" method:
public SomeType M(SomeType1 P1, SomeType2 P2, ...)
{
  MethodInvocationDescriptor mid =
    new MethodInvocationDescriptor(this,
      "public SomeType M(SomeType1, SomeType2)",
      new object[] {P1, P2});
  if (!mid.IsBase)
    ISomeExtender.Prologue(mid);
  try {
    // Original method body goes here
  }
  catch (ExceptionType1 e1) {
    mid.Exception = e1;
    if (!mid.IsBase)
      ISomeExtender.Epilogue(mid);
    if (mid.Exception==null)
      return (SomeType)mid.Result;
    else
      throw;
  }
  catch (ExceptionType2 e2) {
    // Similar exception processing block goes here
  }
  finally {
    if (!mid.IsBase && !mid.IsEpilogueInvoked)
      ISomeExtender.Epilogue(mid);
  }
}
Notes:
  • Prologue and epilogue may throw an exception
  • Probably it is also desirable to "push" method return value to MethodInvocationDescriptor before invoking Epilogue, but this implies modification of original method body (replacement of all return statements).

Wednesday, September 13, 2006

DataObjects.Net v4.0 component: "Aspector"

"Aspector" is internal name of one of its key components. It replaces well-known proxy builder in previous versions of DataObjects.Net, but its possible usage area is much wider then just proxy code injection. We plan to release it as fully independent component (quite likely it will be freeware).

... Further information will be available shortly.

DataObjects.Net v4.0: development news

Being brief, we're just starting development of this branch. Until this day we were mainly focused on v3.8.X (mainly - bugfixes, tests and new upgrade feature) and v3.9 (partitioning, configuration & bugfix migration from v3.8.x) branches, and actually this was rather difficult for us. In the nearest week or two we're stopping all activities except bugfixing in v3.8.X branch, releasing v3.9 (it already behaves quite good on tests), and switching on it as on primary branch; v4.0 becomes our primary development branch after this moment.

As it was mentioned before, v4.0 won't be shipped as a single assembly - we're going to ship (and develop it) as a set of much less dependsnt components (assemblies). Further I'll try to cover the most inetresting ones of them.

P.S. Who am I? I'm the owner of Xtensive LLC, its President and CEO. Formerly I'm the architect of DataObjects.NET. I'm still playing architect role in this project, but now I'm paying attention mainly to design of its new version.