Freigeben über


Aspect Oriented Programming with using WinJS

JavaScript metadata descriptors

Metadata programming is very handful in many scenarios, e.g. in Aspect Oriented Programming (AOP). Typical scenarios are

  • tracing
  • value change notifications
  • security checks
  • etc.

Basically all scenarios where it's necessary to write boilerplate code over methods or properties. The problem is that this boilerplate code is mixed with the business logic and it decreases the readability of the code. It can be solved by applying AOP. AOP leverages can leverage the metadata defined around method and properties. Let's look how it could be applied in context of Javascrip and WinJS library.

ECMA script 5 support metadata in form of descriptors. Basically, method Object.getOwnPropertyDescriptor(object, property_name) returns the descriptor object, aka metadata of the property or method. It's very similar to System.Reflection known form .NET.

 

 Object.defineProperty(instance, "foo", {
    firstName: {
        value: "bar",
        configurable: true,
        enumerable: true,
    }
});

Object.defineProperty(instance, "foo", {
    firstName: {
        get: function () { return this._foo; }, set: function (value) { this._foo = value; },
        configurable: true,
        enumerable: true,
    }
});

As you can see, there are 2 possible descriptor definitions:

  1. value descriptors
  2. get/set descriptors

The difference is that the first has property "value" which refers to the function (in case of method) or value (in case of property). The latter has "get" and/or "set" properties referring to the property get/set methods. The rest are same:

  • enumerable
  • writable

That's all what can be defined on JavaScript descriptors and that's the standard property definition via Object.defineProperty mechanism.

It would be nice to use property descriptors for AOP or other advanced scenarios, as shown in the following code: 

             var instance = function() { };

            Object.defineProperty(instance, "name", {
                firstName: {
                    value: "frantisek",
                    configurable: true,
                    enumerable: true,
                    observable: true // custom metadata
                }
            });

            var descriptor = Object.getOwnPropertyDescriptor(instance, "name");
            WinJS.log("Metadata for name: \n" + JSON.stringify(descriptor));

 

Unfortunately, JavaScript implementation of descriptors is not extensible.  So that Object.getOwnPropertyDescriptor() drops all non-ECMA5 descriptor properties. 

 

The above code returns ECMA5 property descriptor, skipping our custom observable metadata information.

 "Metadata for name: \n{\"writable\":false,\"enumerable\":false,\"configurable\":false}"

 

WinJS and metadata

But we can overcome this issue with WinJS. Anyway, on WWA platform, we use WinJS to define classes which under the hood calls Object.defineProperty(). We could replace-via-extend WinJS.Class.define or just define new helper method for defining the class with the possibilities to use pre/post processing.

The whole idea is that we can do any pre/post processing when defining the class via WinJS, we could hook into the process of class definition and do the custom steps. I already used it in my previous post, but lets see it on the sample example.

Let's say we want to define a class and we want to add tracing over the selected properties.

             var Klass = defineClass(function() {
            }, {
                firstName: {
                    value: "frantisek",
                    trace: true,
                    writable: true,
                    enumerable: true
                }
            });

            instance = new Klass();
            instance.firstName = "fero";

 

The goal is to get the tracing automatically available over firstName property. 

 

Step #1: custom class define function

             var aopFilters = [];
            function defineClass(ctor, instanceMembers, staticMembers) {
                var base;
                aopFilters.forEach(function (aopFilter) {
                    base = aopFilter(instanceMembers, base); // apply filter
                });

                return WinJS.Class.derive(base, function () {
                    base.call(this);
                    ctor.apply(this, arguments);
                }, instanceMembers, staticMembers);
            }

The above code defines a list of AOP filters so that we can add there our AOP filters, in this example it'll be the tracing filter. When defining the class, all AOP filters will be applied and then the final class will be defined. 

Step #2: tracing filter

             function traceFilter(instanceMembers, baseClass) {
                expandTraceProperties(instanceMembers);
                return baseClass;
            }

            function expandTraceProperties(shape) {
                var props = {};
                Object.getOwnPropertyNames(shape).forEach(function (name) {
                    var descr = shape[name];
                if (descr.trace) {
                        var defaultValue = shape[name].value;
                        var field = "_" + name;
                        shape[name] = {
                            get: function() {
                               WinJS.log("getting " + name);

                                if (Object.hasOwnProperty(this, field)) {
                                    return this[field];
                                } else {
                                    return defaultValue;
                                }
                            },
                            set: function(value) {
                                this[field] = value;
                                WinJS.log("setting " + name + " to " + value);
                            }
                        };
                    }
                });
                return props;
            };

 

The code above defines the tracing filter which uses WinJS.logging capabilities. The above code is just a demo code. It's not production ready code. Generally, AOP filter is a function which gets instance members and a reference to the base class. In general, it could be used in the scenarios where AOP filter requires a specific base class. In our case we don't need it, so we return what we get passed. 

The main logic is in the expandTraceProperties method. This metod filters only trace-able properties and replaces their value with custom get/set method. 

 

Step #3: final example

 Let's use it and run the example again:  

 var Klass = defineClass(function() {
            }, {
                firstName: {
                    value: "frantisek",
                    trace: true,
                    writable: true,
                    enumerable: true
                }
            });

            instance = new Klass();
            instance.firstName = "fero"; // outputs setting firstName to fero

 

The above described approach gives the developer possibility to mark-up (attribute) the properties with the metadata and let AOP filter to be applied. 

 

What do you think about this approach? Any other ideas how to overcome that metadata constraint?

Metadata.zip