Child pages
  • FlexJS "Pay as You Go" (PAYG)
Skip to end of metadata
Go to start of metadata
Status: DRAFT, under review (initial content is subjective interpretation of list discussions by author, for review and contributions by others)
Intended Audience:Apache Flex FlexJS SDK/Compiler developers (a separate document could be created for users of FlexJS SDK releases, based on relevant content)

Review time frame:

annual

 

PAYG - "Pay as you Go"

Pay as you go (PAYG) is a general principle for FlexJS whereby the SDK framework code (the actionscript codebase used to create FlexJS SDK library swcs) supports incremental functionality in components. The goal is to provide the possibility for a lower level of performance or memory impact of applications built using the framework (compared to not applying PAYG to the framework code). PAYG may imply a small performance or memory cost in the framework to support its implementation, but that is recouped in its use.  Some PAYG aspects may also be provided by or supported by compiler options which may not have any corresponding framework overhead.

Philosophy

To understand PAYG, it's very helpful to understand some background. The opposite of PAYG is "Just in Case" code. Classic Flex architecture took this "Just in Case" approach and it resulted in a bloated framework. Just in Case relies very heavily on class inheritance and has a lot of code that you might need for certain components. A perfect example of this is UIComponent which is close to 15,000 lines of code with hundreds of methods that are often not needed at all. Being that UIComponent is the base class for every UI component in Flex, every application is carrying around this code whether it needs it or not. Additionally, much of this code is being run at runtime whether or not it's needed for the specific use case. This is wasteful in terms of disk space, network resources, memory and runtime performance overhead.

PAYG takes the opposite approach. Classes only contain the code necessary for the minimum useful implementation to function. Instead of heavy reliance on inheritance, PAYG prefers composition and dependency injection. Instead of additional features being baked in, they are composed and functionality injected when needed. The theory is that this results in smaller, leaner applications. Results seem to back up this theory. One application which was ported from Flex4 to FlexJS was reduced from a total size of 1.8MB down to less than 500KB of Javascript gzipped. There is still potential in the FlexJS compiler to reduce the size even further. The Flash version of this application seems to be even smaller, but exact numbers are hard to determine because not all features are implemented in Flash.

What is not PAYG?

todo: list any possible common misconceptions (or remove this topic)

What is 'Paying' in PAYG

'Paying' is defined as conscious decisions by developers to include functionality that has a measurable and substantial performance or memory impact. The ‘as you go’ implies ‘incremental functionality’. In simple terms, it means that you do not include performance and memory ‘cost’ for functionality you don’t use. -Harbs I'm not sure this is the best way to put it. Below is my attempt to clarify this. (not sure I did the best job with the "clarifying" part...)

The word 'Paying' can be replaced by the word 'Code'. Pay as You Go is synonymous with 'Code as You Go'. That is (in general), any code which is not essential for the functioning of the basic component should be composed to be used "as you go" elsewhere. This elsewhere can be a subset, an alternate set, a composable bead etc. The point is that "just in case" code should not be included in the basic minimal useful implementation. It should be included only when you want it, or opt in to "just in case" code.

The ‘paying’ aspect of PAYG does not include 'developer effort' as ‘payment’ or ‘cost’. This means that for some projects using a full PAYG component set, additional developer effort will be required, however the FlexJS project team is working to reduce this by composing sets of general purpose components that include ‘general purpose’ functionality (See Doesn’t supporting PAYG have its own cost? below).

How is ‘measurable and substantial’ defined to determine thresholds for default functionality?

Determining whether something has merit for inclusion in default functionality or is categorised into ‘as you go’ is something that has generated discussions in the flex lists and has resulted in disparate views. Perhaps part of the reason for this is a lack of clarity and/or consensus as to whether ‘paying’ includes developer effort or not (see What is ‘Paying’ above).

Discussion areas:

How do we decide what is default functionality and what is ‘as you go’?

What does ‘substantial’ mean? And in what context?

Are there exceptions or inconsistencies that are justifiable (and under what conditions)?

Why PAYG?

To target javascript, the FlexJS SDK and components needed to be substantially different to the legacy Flex 4 SDK which is SWF-only (no need for details why here, but maybe link to other content?). Given this need to make large changes, the opportunity was also taken to review general improvements to the SDK component architecture to address some common criticisms of the Flex 4 component set. One of those criticisms (the size of compiled applications) relates to the size of some of the Flex 4 base classes which, in simple terms, is the opposite of PAYG. Implementing PAYG in FlexJS has the goal of maintaining the possibility of a full range of functionality, but making it more incremental/opt-in.

Doesn’t supporting PAYG have its own cost?

Performance

Performance cuts both ways. PAYG may contribute to performance costs, because there is some overhead in supporting a runtime based composition model. On the other hand, "Just in Case" code is not being run, so that more than likely offsets the overhead. Moreover, the general principle is that establishing the ability to compose functionality (especially via mxml) is worth the overhead. For features that are opt-in via the compiler, these features may not have an extra framework cost.

Developer Effort

Because there can be extra effort to compose incremental functionality for common use cases, it would likely result in extra developer effort being required if the developer was using a full PAYG approach to development on every project. For this reason, the ‘Express’ component set exists which has pre-composed components that feature combinations of functionality to suit more general use cases. The intention behind this is to reduce the time and effort required in the use of common components by including the more commonly needed functionality. The vision is that there should be a collection of component sets which compose the functionality required for specific types of applications. This can help balance ease of development against PAYG benefits.

PAYG vs DRY

Another software design concept is DRY (Don't Repeat Yourself). PAYG can sometimes be at odds with DRY. Considering that additional functionality would need to be in a different class, this can lead to code repeat and multiple classes which have a lot of the same logic.

Despite this, we feel that the benefits of PAYG out way the costs of sometimes failing to adhere to DRY.

However, we don't want to ignore DRY and it's worth noting that it's not always an issue, and the cost can be mitigated. Many of these cases are simply alternate implementations of the same type of component. When picking the component set, the minimal usable implementation should be selected. If the larger implementation is needed, and is used consistently across the application, this code duplication might never make it into the end application. Additionally, use of subclasses in extended components can result in less code duplication.

An additional technique which is used frequently in framework code is the use of utility classes or top level functions (note: top level functions should be used more than it currently is) to implement commonly used code that is needed across different components. This utility code will only be included if it's actually used, and only be included once no matter how many times it's used. Utility code should be small and serve a single purpose.

How do we decide if a piece of functionality should be default/baseline functionality or ‘extra’/optional functionality?

todo: this needs to provide an actual guide for decision making. Currently it has questions that might help to arrive at such a guide

Suggested goal for this topic area: a decision framework with weighting on areas of importance when considering various factors.

The aim is to remove any lack of clarity and reduce any future debate around specific topics to a discussion around relevance to documented (consensus-based) criteria.

Current status: There is no definitive guide, suggested discussion topics 

Discussion areas (What factors could be considered for this decision process, and what is their relative weighting in decisions).

1. What is ‘minimum’ vs. pareto-based functionality?

-Is the effort of avoiding functionality worth more than the cost of including it (is there a practical value in avoiding it - will any developer actually prefer to use the 'minimum' or is it to remain 'true' to PAYG principles, for example) .

-There appears to be general agreement that the Flex 4 sdk had ‘too much’ swiss-army-knife code, attempting to cover too much functionality in base classes and therefore had extra un-needed ‘weight’ or baggage in most applications. This has been often used as the justification for a PAYG approach, what is the right balance of functionality for the Express package.

Are there downsides to aiming too much in the opposite direction, or is it always justified? For FlexJS, what is the 20% of functionality/features that address 80% of needs (assuming pareto principles apply – 80/20 is only indicative for discussion here, not literal values). Is this always ‘express’ vs. ‘baseline’ or not? If baseline is not in line with pareto principles, what percentage of developers would find it useful to work with directly (or is it mainly intended for support of ‘express’ and other modules)?

2. How much ‘weight’ does an optional piece of functionality add? Is there a way to specify a threshold for what is considered baseline/default and what (for example) would be included in express component set?

3. Do additional compilation steps automatically eliminate un-used functionality, and how does that relate to PAYG?

-If yes, this could favour inclusion of greater functionality by default, because the ‘payment’ for unused functionality is essentially zero if it is optimised away by (for example, via Google Closure Compiler) when it is not used. Counter: what disadvantages are there with relying on an external process to do this?

4. Are all targets considered equal?

Discussions on the list have included suggestions that we favour the javascript target over SWF in terms of performance as a priority, with SWF target aiming more for consistency in results with the javascript target. If this is true then it might mean that the external compiler optimizations (GCC) play a greater role in determining what default implementations for ‘PAYG’ should contain (if unused functionality is optimized out of the production code). How does that fit in to PAYG?

5. What other factors might be important in the future?

-General trends continue to be increasingly faster processing, and greater memory capacity across device targets. Decisions have already been made about ES5 compliance as minimum target browser specification for javascript. Do we need to anticipate the target context (e.g. es6?) 2-4 years out when FlexJS has an (anticipated) healthy user base of developers?

The sands keep shifting, the idea here is to draw a line in the sand, at a point that anticipates the shape of the dunes in the relevant future. We have done this before.

As an example, here is a guiding statement that was documented 4 years ago which was true then, but has been addressed in ES6 (WeakMap) and is now supported by current browsers (but notably, not IE 9 -10, for example, which are still considered current FlexJS targets).

“Also, since Dictionary and weak references don't really exist on HTML/JS, the new framework will not use them on AS and you may have to explicitly call release() or some other API on some objects to make them available for garbage collection.“

 At the outset FlexJS included IE8 as a target browser and that is no longer the case. The example above is intended to be illustrative of the changing ‘landscape’ only. The simple question here is whether, considering what we expect even only one year from now, external changes may impact decisions on any PAYG aspects now. This could include javascript language itself or browser performance.

-Does what we decide now make sense for possible future compiler targets (i.e. other than swf or javascript) (note: there are no established plans for other targets, but the possibility of considering them might factor in, although probably/perhaps this is a low-weight factor). 

 

How does PAYG get implemented?

todo: Discuss and populate

Framework

1. Strands and Beads

The ‘strands and beads’ concept is a metaphor for adding extra functionality (beads) onto a component (strand). Beads represent the modular units of functionality that can be selected and composed together to configure functionality added to the base component (strand). Component sets such as ‘Express’ include more general functionality which may into reduce developer effort for the general case of application development.

Questions

Where do beads with additional functionality go? What determines inclusion in the sdk and which module (e.g. would some beads only be part of Express or are they central and Express is only a set of components composed of commonly used beads)? What criteria would we potentially have to suggest them being excluded from FlexJS (i.e. ‘that belongs more in an external library’ if that is ever a need).

 

General discussions to derive guidance:

Null Checks

todo: discussion When to include, when to exclude

Other ‘safety’ aspects

-Automatic removal of listeners/general ‘cleanup’ when removing functionality

Initial content: This should be done (in a bead) when a bead is intended to be removable, which is unlikely for the most basic implementation of that 'type' of bead. Therefore it should not be in the base class if the incremental functionality for that bead is inheritance based.

 

Approaches to Incremental functionality via beads

todo: discussion (resolve varied views)

Is there one preferred approach to coding beads (or?) ?

-New (isolated) Beads (copy paste with extra functionality) vs. DRY

-Bead inheritance and/or utility functions

-Beads of Beads?

 

Compiler

1. Compiler options.

initialised vs. uninitiliased variables

This is a topic of current discussion, with differing views.

todo: discussion (resolve varied views)

Question:

is it better to be compliant with actionscript language expectations as a priority (e.g. myUninitialisedObjectVar === null vs. myUninitialisedObjectVar  === undefined in javascript) or is this a case where we forgo compliance with actionscript expectations in favour of minimal code (e.g. force developer to use .myUninitialisedObjectVar == null for compatibility between targets)

Partial answer: we can support initialisation to defaults as specified by actionscript, and create a compiler option to switch it on or off. So the question then becomes : which is the default? Is full PAYG more important than compliance with actionscript (in the javascript output)? For the optional approach, what is the capacity for problems arising from having it in one state for the framework code vs. a different state for the sdk user's code?

 

 

 

 

 

 

  • No labels