Skip to content

Commit

Permalink
Kickstart the 4.1 docs
Browse files Browse the repository at this point in the history
  • Loading branch information
seancorfield committed Sep 17, 2016
1 parent 7bd789c commit 26b4e77
Show file tree
Hide file tree
Showing 9 changed files with 4,173 additions and 0 deletions.
1,525 changes: 1,525 additions & 0 deletions source/documentation/4.1/cfml-and-clojure.markdown

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions source/documentation/4.1/changes.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
layout: page
title: "Change Log for FW/1 and Friends"
date: 2016-09-16 20:00
comments: false
sharing: false
footer: true
---
_This is documentation for the upcoming 4.1 release. For the current release, see [this documentation](/documentation/)._

The following changes are part of FW/1 4.1, DI/1 1.3.0, and cfmljure 1.2.0.

Summary
---
The 4.1 release is intended to be a minor maintenance release over 4.0.

Breaking Changes
---

* None so far.

Enhancements
---

* None so far.

Bug Fixes
---

* None so far.
941 changes: 941 additions & 0 deletions source/documentation/4.1/developing-applications.markdown

Large diffs are not rendered by default.

236 changes: 236 additions & 0 deletions source/documentation/4.1/index.markdown

Large diffs are not rendered by default.

573 changes: 573 additions & 0 deletions source/documentation/4.1/reference-manual.markdown

Large diffs are not rendered by default.

61 changes: 61 additions & 0 deletions source/documentation/4.1/roadmap.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
layout: page
title: "FW/1 Roadmap"
date: 2016-09-16 20:00
comments: false
sharing: false
footer: true
---
_This is documentation for the upcoming 4.1 release. For the current release, see [this documentation](/documentation/)._

Whilst you can read the [FW/1 issues list](https://github.com/framework-one/fw1/issues) to see what's on the cards for future releases, several people have asked that I document things at a higher level so they can get a better sense of what's coming.

5.x Lifecycle Restructuring
---
FW/1 5.0 will move the request lifecycle methods out of `Application.cfc` into a separate CFC. In addition, subsystems will also get a CFC that controls their lifecycle methods (`subsystem.cfc`). Since this will be a breaking change, there will be a 4.5 release to pave the way with deprecation warnings and exceptions, in the same way that the 2.5 release paved the way between 2.2 and 3.0.

4.x REST and Modernization
---
FW/1 4.0 is the current stable release and focus on improved REST support and Dependency Injection. The major version number change indicates that support for ACF9.0.2 was dropped to allow for the use of closures in the code as this provided some nicer syntax for some framework features. The core code was also modernized a little to take advantage of features in ACF10+ and Lucee 4.5+ in this release.

FW/1 4.1 will be a maintenance release, followed by FW/1 4.5 which will be a migration release for the breaking features of 5.0 (see above).

3.x Integrations
---
FW/1 3.5 added integraton of Clojure controllers and autowired Clojure services. It does this by bundling cfmljure and providing an extension to DI/1, as well as a controller adapter. It also provides direct support for the Lucee language (a new dialect of CFML, introduced in Lucee 5.0), as well as a new, improved way to add subsystems to an existing application. In addition, WireBox is better support as a DI option, and the folder naming conventions can be configured for the first time. It is a major release but also remains compatible with earlier 3.x releases.

FW/1 3.1 is the previous stable release, and it was primarily a maintenance release for 3.0, but it also bundled AOP/1 for the first time. It builds on core 3.0 features:

* Using DI/1 to manage services and beans
* Explicitly calling services in the `item()` controller methods

FW/1 3.0 bundles DI/1 and automatically creates bean factories following the convention of a `model` folder containing `services` and `beans` subfolders. Subsystems also follow this convention and use the main application's bean factory as a parent to enable sharing of common services across subsystems.

FW/1 3.0 also slims down the framework by removing all of the service queue machinery and the start/end variants of controller methods, as well as coalescing all the framework components in a single folder `framework` (with `org.corfield.framework` replaced by `framework.one`).

2.5 Deprecations
---
FW/1 2.5 represented a large shift from 2.2.1 and deprecated a number of long-standing features that were removed in 3.0:

* Service queue - implicit service calls were disabled in 2.x but still supported for backward compatibility with 1.x, but in 2.5 the whole service queue machinery is deprecated, including the `service()` call, and will be removed in 3.0.
* `startItem()` / `endItem()` controller methods - these were originally introduced to encourage a clean separation of controller logic from (queued) service logic. With the service queue going away, they make no sense, so they are deprecated in 2.5 and will be removed in 3.0.
* Global access to the `rc` variable - this was never really intended to be supported but some users found it convenient and started to rely on it. Due to edge case bugs, and the fact that there are better ways to manage the `rc` in the lifecycle, global access is deprecated in 2.5 and will be removed in 3.0. This will mostly affect users who update the `rc` in `setupRequest()` - logic which should generally be moved to `Application.cfc` in the global `before()` controller method.

2.1/2.2 Release Stream
---
This is the previous stable stream. FW/1 2.5 is the last release in that stream. The 2.x versions support Adobe ColdFusion 9.0.1 and later, Lucee 4.5.0 and later, and Railo 3.2.2 and later.

Changes between 1.x and 2.x include:

* No more implicit service calls
* Custom route support
* Property-based injection in controllers
* Better lifecycle control: layout and view control, abort controllers
* Better REST support: render data instead of views, resource packs and regular expressions in routes
* Environment control
* Application tracing
* Automated test suite

1.x Release Stream
---
This is the legacy stream. FW/1 1.3 is the most recent legacy version, provided to support older CFML engines (Adobe ColdFusion 9.0.0 and earlier, Railo 3.1.x, Open BlueDragon). FW/1 2.x and 3.x will not run on these older CFML engines. Only critical fixes will be made to the 1.x release stream. Currently no new 1.x releases are planned.
187 changes: 187 additions & 0 deletions source/documentation/4.1/using-aop-one.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
---
layout: page
title: "Using AOP/1"
date: 2016-09-16 20:00
comments: false
sharing: false
footer: true
---
_This is documentation for the upcoming 4.1 release. For the current release, see [this documentation](/documentation/)._

AOP/1 is a simple Aspect Oriented Programming extension for [DI/1 (a.k.a Inject One)](using-di-one.html) which allows you to define interceptors for your beans.

These interceptors can run code before or after a method is called on a bean without the need for you to alter the code in your bean. This allows you to create generic services (such as a logger service) that is coded and configured to operate completely separate from your other services and beans. What this means is you no longer need to mix unrelated service code together by using dedicated interceptors.

_The information below assumes that you already have a good working knowledge of DI/1._

* TOC
{:toc}

# Getting Started with AOP/1

Create an instance of the AOP/1 extended DI/1 bean factory and specify the folder(s) you want it to search for beans.

var beanFactory = new framework.aop("/model");

So far nothing difficult since this is what we would typically see from [DI/1](using-di-one.html). Now, if we want to intercept method calls to an object, we need to declare the interceptors and the objects that should be intercepted.

var beanFactory = new framework.aop("/model");

beanFactory.intercept("pdfService", "beforeInterceptor");
beanFactory.intercept("pdfService", "afterInterceptor", "createDocument");

var ps = beanFactory.getBean("pdfService");

var document = ps.createDocument("http://seancorfield.github.io");
var pages = ps.splitPages(document);

In this example the `beforeInterceptor` will intercept every call to the `pdfService()`, but the `afterInterceptor` will only intercept calls to the `createDocument()` method. Due to AOP/1 creating intercept points on the bean being intercepted, it is generally recommended to list the methods to be intercepted when declaring the interceptors so there are not unnecessary calls made on other methods.

## Creating Interceptors

A common practice for DI/1 is to place beans and services within a model folder like so:

* `/model/beans/`
* `/model/services/`

Interceptors can follow this pattern in order to make it simple for the factory to locate the interceptors with the rest of the model.

* `/model/interceptors/`

### Before Interceptors

Before interceptors will intercept method calls _before_ they are executed. They cannot affect the result of a method call, but they can be used to alter the arguments going to the method call and they can perform operation that you wish to be performed before the method call. In order for an interceptor to operate as a before interceptor, it only needs the `before()` method to be defined.

component {
function before(targetBean, methodName, args) {
arguments.args.input = "before" & arguments.args.input;
}
}

Because the interceptor is like any other bean handled by DI/1, dependencies can be intjected into the interceptor and used by the interceptor.

component {
property logService;

function before(targetBean, methodName, args) {
getLogService().logMethodCall(arguments.methodName, arguments.args);
}
}

### After Interceptors

Just like the name implies, after interceptors will intercept method calls _after_ they are executed. They cannot affect the arguments going to the method call, but they can monitor or alter the result of the method call. In order for an interceptor to operate as an after interceptor, it only needs the `after()` method to be defined.

component {
property logService;

function after(targetBean, methodName, args, result) {
if (structKeyExists(arguments, "result) && !isNull(arguments.result) {
getLogService().logMethodCallResult(arguments.methodName, arguments.args, arguments.result);
}
}
}

Should you wish to alter the result being returned, all that is needed is to return something from the `after()` method.

component {
function after(targetBean, methodName, args, result) {
if (structKeyExists(arguments, "result) && !isNull(arguments.result) {
return arguments.result & "After";
}
}
}

### onError Interceptors

onError interceptors allow errors that occur during the execution of intercepted method calls to be handled outside the normal flow of model execution. This can be used for situations where the normal error handling of your application will not produce the desired result.

component {
property logService;

function onError(targetBean, methodName, args, exception) {
getLogService().logException(arguments.methodName, arguments.args, arguments.exception);
}
}

### Around Interceptors

Around interceptors are an interesting interceptor, because unlike other interceptor types, an around interceptor can actually stop execution of an intercepted method. This accomplished because, unlike before, after, and onError interceptors which are called externally in the order they are defined in their stacks, the around interceptors always call the next interceptor in their stack.

Calling the next interceptor in the stack for around interceptors is accomplished by calling the `proceed()` method. The `proceed()` method is automatically added to any interceptor that has an `around()` method. An around interceptor can stop the execution chain by simply not calling the `proceed()` method. The around interceptors can preform the actions of both before and after interceptors as well.

component {
property logService;
property userService;

function around(targetBean, methodName, args) {
// Perform 'before' arguments manipulation.
arguments.args.name = getUserService().getCurrentUser().getName();

if (getUserService().getCurrentUser().hasPermission("administrator")) {
var result = proceed(arguments.targetBean, arguments.methodName, arguments.args);

if (!isNull(result))
{
getLogService().logMethodCallResult(arguments.methodName, arguments.args, result);

return result;
}
}
}
}

Since an around interceptor may intercept multiple methods, the method must be able to handle any type of result being returned (including void/null). The example above demonstrates handling when a result is present and demonstrates how the execution chain can be stopped by not calling the `proceed()` method if the current user is not an 'administrator'.

# Advanced Usage & Understanding

The following section explains additional features and concepts that may prove useful when implementing AOP/1.

## Loading Interceptors Via Configuration

AOP/1 extends DI/1 so it has access to the `config` parameter of the constructor.

var interceptors = [{beanName = "stringUtilityService", interceptorName = "afterInterceptor"}];
var factory = new framework.aop(folders, {interceptors = interceptors});

The `interceptors` configuration is just an array of structures that define the interceptors to be loaded like so:

var interceptors =
[
{beanName = "stringUtilityService", interceptorName = "beforeInterceptor", methods = "forward,reverse,split"},
{beanName = "stringUtilityService", interceptorName = "afterInterceptor"},
{beanName = "stringUtilityService", interceptorName = "afterInterceptor2", methods = ""},
{beanName = "stringUtilityService", interceptorName = "afterInterceptor3", methods = "*"},
{beanName = "stringUtilityService", interceptorName = "aroundInterceptor", methods = "reverse"}
];

When the `methods` key is missing from the interceptor definition or it contains an empty value or asterisk, then AOP/1 assumes that all methods on the bean should be intercepted.

## Helper Methods

`isLast()`
This method is automatically added to any **around** interceptor and will tell you if the interceptor is the last in the execution chain.

`translateArgs(any targetBean, string methodName, struct args, boolean replace)`
This method is automatically added to any interceptor and will attempt translate position based arguments into name based arguments. This method has a `replace` argument that when set to `true` will replace the `args` with a copy of named arguments.

## Intercepting Cross Object Calls & Private Methods

Unlike some other AOP frameworks, AOP/1 has the ability to intercept cross object calls. What this means is, if you are intercepting methods (`method1()`, `method2()`, `method3()`) on `myService` and `method2()` actually makes a call to `method3()`, then AOP/1 will intercept the call from `method2()` to `method3()` in addition to original call to `method2`.

In addition to intercepting cross object method calls, AOP/1 can also intercept calls to private methods.

## Multiple Interceptor

You may find yourself creating an interceptor that performs multiple similar tasks and it is logical to group multiple different interceptor types together. This can be accomplished by simply creating the correct methods in the same component. For instance, if you have an interceptor that you want to perform both before and after interceptions, then you simply add both the `before()` and `after()` methods to the component. AOP/1 will place an interceptor in multiple execution stacks if it has more than one interceptor type method present.

## Stack Execution

Stacks are executed in the following order.

* **before**
* **around**
* **after**
* **onError**

All the stacks will only execute if there is an interceptor of their type present. If the stack is emtpy, nothing is executed. The **onError** stack only executes if there is an error in the execution of the other stacks. The **before** and **after** stacks execute like a queue and will execute from start to finish regardless of changes to the arguments or result, skipping any interceptors that do not match the currently intercepted bean method. The **around** stack executes more like a chain. The chain execution can be stopped by not calling the `proceed()` method.
Loading

0 comments on commit 26b4e77

Please sign in to comment.