exedore

1.0.0 • Public • Published

Exedore

Exedore is a JavaScript library for aspect-oriented programming (AoP) in ES6 and newer versions of JavaScript.

It is based on Aop.js by Fredrik Appelberg and Dave Clayton, and on the unit tests in Chapter 2 of Reliable JavaScript: How to Code Safely in the World's Most Dangerous Language by Seth Richards and Lawrence Spencer (ISBN 9781119028727). From this foundation, Exedore was extended to interoperate with ES6 classes, as easily as Aop.js works with functions defined on plain objects.

Understanding Aspect-Oriented Programming (AOP)

Aspect-Oriented Programming (AOP) complements Object-Oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns such as transaction management that cut across multiple types and objects. (Such concerns are often termed crosscutting concerns in AOP literature.) -- Spring Framework documentation

The first paragraph of the Wikipedia entry on Aspect-Oriented Programming also has a good definition, which I won't bother to repeat here.

We don't deal with a lot of transaction management in the JavaScript world, so think about logging instead. You don't want to write a bunch of logging code in all of your functions. AOP allows you to write generic logging functions and then insert them before or after the specific functions that you want to log. You can see this in example 1.

In the jargon of AOP, an aspect is a modularization of a cross-cutting concern. An advice is the action that the aspect takes at any given point. In other words, an advice is the function that wraps around a target function, and an aspect is a library or class that contains one or more advices.

Basic Usage


IMPORTANT

When defining an advice function, do NOT use arrow functions (aka "fat arrows"). Use the old school function declaration:

const advice = function( target, args ) { /* do stuff */ }

This is because arrow functions lexically bind the value of this to the context where they are defined. In AOP, we want to be able to control the context of the advice. We move advice functions to many different contexts, so we don't want them to be bound to a particular context. Don't use arrow functions.


Start, of course, by importing Exedore into your file:

import Exedore from 'exedore';

Executing Advice Before the Target

Suppose that you want to monitor the usage of certain functions. This is a perfect use-case for AOP! First, we want to know when a function is called and what arguments it is called with. Here are our functions, as properties on a plain object. We are also creating an empty array to store the log messages.

const log = [ ];
const math = {
    add: function( a, b ) { return a + b; },
    multiply: function( a, b ) { return a * b; }
};

Create the Advice Function

An advice function recieves two arguments: the original or target function, and the arguments to the target function. Our logger function will push information about the function call onto the log array

const loggerBefore = function( target, args )  {
    const beforeMessage = `Function '${target.name}' called with ${args.toString()}`;
    log.push( beforeMessage );
}

Using the Advice

To use the advice function, we wrap the function(s) that we want to log. The before function takes as its arguments (1) the object that contains the target function, (2) the name of the target function as a string, and (3) the advice function.

Exedore.before( math, 'add', loggerBefore );
Exedore.before( math, 'multiply', loggerBefore );

Then we use the functions and view the log.

math.multiply( 3, 9 );
math.add( 1, 1 );
math.multiply( 4, 4 );
math.add( 2, 2 );
 
console.log( `The log has ${log.length} entries` );
log.forEach( entry => console.log( entry ) );

All of this code comes from example 1, so you can see it all together in that file. To run the examples, clone this repository, install the dependencies with npm install, and execute npm run example:01 (or the number of the example you want to see).

↓ TODO: Start here ↓

Executing Advice After the Target

Adding Multiple Advice Functions

Advanced Usage

Wrap

To execute the original function, in the body of the advice, call Exedore.next. Pass it the current context this, the target function, and the args to the target function.

The next function will return the result of calling the target function with the provided arguments. This means that Exedore will let you modify the arguments before passing them to the target!

const result = Exedore.next( this, target, args );

You can also modify the result of the target function before returning it.

Putting it all together, a "logger" advice function might look like this:

const logger = function( target, args ) {
    const beforeMessage = `Function '${target.name}' called with ${args.toString()}`;
    log.push( beforeMessage );
 
    const result = Exedore.next( this, target, args );
 
    const afterMessage = `Function '${target.name}' returned ${result}`;
    log.push( afterMessage );
 
    return result;
};

Use the Advice

Before we can use the logger advice function, we need some things to use it with. In the example, these all come before the advice function is definied.

const log = [ ];
const math = {
    add: function( a, b ) { return a + b; },
    multiply: function( a, b ) { return a * b; }
};

Next, we wrap the function(s) that we want to log. The wrap functions takes as its arguments (1) the object that contains the target function, (2) the name of the target function as a string, and (3) the advice function.

Exedore.wrap( math, 'add', logger );
Exedore.wrap( math, 'multiply', logger );

Then we use the functions and view the log.

math.multiply( 3, 9 );
math.add( 1, 1 );
math.multiply( 4, 4 );
math.add( 2, 2 );
 
console.log( `The log has ${log.length} entries` );
log.forEach( entry => console.log( entry ) );

All of this code comes from the example, so you can see it all together in that file. To run the examples, clone this repository, install the dependencies with npm install, and execute npm run example:01 (or the number of the example you want to see).

Exedore and Classes

TODO

Recommendations and Precautions

  • recommendations and precautions for using with module loaders and npm modules
    • only modify prototypes that you own
    • only wrap instances of modules objects
    • use custom factory or class if you need to wrap every instance (show example)

Dependents (1)

Package Sidebar

Install

npm i exedore

Weekly Downloads

3

Version

1.0.0

License

MIT

Unpacked Size

14.7 kB

Total Files

8

Last publish

Collaborators

  • philgs