A Closer Look

As mentioned previously, the basic process of feature-u is that each feature promotes a Feature object that contains various aspects of that feature ... things like: the feature's name, it's Public API, whether it is enabled, initialization constructs, and resources used to configure it's slice of the frameworks in use.

In turn, these Feature objects are supplied to launchApp(), which configures and starts your application, returning an App Object (which promotes the public API of each feature).

Let's take a closer look at this process ...

aspects

In feature-u, "aspect" is a generalized term used to refer to the various ingredients that (when combined) constitute your application. Aspects can take on many different forms ... for example:

  • UI Components and Routes,
  • State Management (actions, reducers, selectors),
  • Business Logic,
  • Startup Initialization Code,
  • etc.

Not all aspects are of interest to feature-u ... only those that are needed to setup and launch the app ... all others are considered to be an internal implementation detail of the feature. As an example, consider the redux state manager: while it uses actions, reducers, and selectors ... only reducers are needed to setup and configure redux.

feature-u provides a base set of built-in aspects (out-of-the-box) but allows additional aspects to be introduced through it's extendable API.

  1. Built-In aspects:

    These aspects are promoted directly by the base feature-u package. They provide very rudimentary capabilities, such as feature enablement, public API, and application life-cycle hooks.

  2. Extendable aspects:

    These aspects are promoted by external packages (or self defined in your project). They provide feature-u integration with other frameworks (for example redux state management, or redux-logic business logic, or navigational routers, etc.). They are created with feature-u's extendable API, and are packaged separately, so as to not introduce unwanted dependencies.

Feature Object (relaying aspect content)

The Feature object is merely a container that holds aspect content that is of interest to feature-u.

Each feature within your application promotes a Feature object (using createFeature()) that catalogs the AspectContent of that feature.

Ultimately, all Feature objects are consumed by launchApp().

Built-In aspects

Built-in aspects are promoted directly by the base feature-u package. They provide very rudimentary capabilities, such as feature enablement, public API, and application life-cycle hooks.

Like all aspects, Built-In aspect content is relayed through Feature object properties (via createFeature()).

  • Feature.name

    A string property which represents the identity of the feature. Feature names are used to index the App Object by feature (in support of Cross Feature Communication), and are therefore guaranteed to be unique. Application code can also use Feature Name in various single-source-of-truth operations.

  • Feature.enabled

    A boolean property that determines whether the feature is enabled or not. This indicator is typically based on a dynamic expression, allowing packaged code to be dynamically enabled/disabled at run-time (please refer to: Feature Enablement).

  • Feature.publicFace

    An optional resource object that is the feature's Public API, promoting cross-communication between features. This object is exposed through the App object as: app.{featureName}.{publicFace} (please refer to: publicFace and the App Object ).

  • Feature.appWillStart

    An optional Application Life Cycle Hook invoked one time, just before the app starts up. This life-cycle hook can do any type of initialization, and/or optionally supplement the app's top-level content (using a non-null return) (please refer to: appWillStart).

  • Feature.appDidStart

    An optional Application Life Cycle Hook invoked one time, immediately after the app has started. Because the app is up-and-running at this time, you have access to the appState and the dispatch() function ... assuming you are using redux (when detected by feature-u's plugable aspects) (please refer to: appDidStart).

Extendable aspects

feature-u is extendable! Extendable Aspects provide feature-u integration with other frameworks (for example redux state management, or redux-logic business logic, etc.). For this reason (by in large) they provide the most value, because they fully integrate your features into your run-time stack!

Extendable Aspects are packaged separately from feature-u, so as to not introduce unwanted dependencies (because not everyone uses the same frameworks). You pick and choose them based on the framework(s) used in your project (matching your project's run-time stack).

Extendable Aspects are created with feature-u's Extension API, using createAspect(). You can define your own Aspect (if the one you need doesn't already exist)! For more information, please see Extending feature-u.

Like all aspects, Extendable Aspect content is relayed through Feature object properties (via createFeature()).

Because Extendable Aspects are not part of the base feature-u package, it is a bit problematic to discuss them here (they are either in a separate npm package, or self contained in your project). You should search the npm registry with the 'feature-u' keyword to find the ones that meet your requirements. With that said, we will briefly discuss the Extendable Aspects that were created in conjunction with the initial development of feature-u (just to give you a feel of what is possible).

  • Feature.reducer via: feature-redux

    feature-redux is the feature-u integration point to redux!

    It configures redux through the reducerAspect (to be supplied to launchApp()), which extends the Feature object, adding support for the Feature.reducer property, referencing feature-based reducers.

    Only reducers are of interest because that is all that is needed to configure [redux]. All other redux items (actions, selectors, etc.) are considered to be an internal implementation detail of the feature.

    The Feature.reducer content must be embellished by slicedReducer(), which provides instructions on how to combine multiple feature-based reducers in constructing the overall top-level application state tree.

    Because feature-redux manages redux, it also provides an integration hook to other Aspects that need to inject redux middleware.

    Please refer to the feature-redux documentation for complete details.

  • Feature.route via: feature-router

    feature-router is the feature-u integration point to Feature Routes!

    It configures Feature Router through the routeAspect (to be supplied to launchApp()), which extends the Feature object, adding support for the Feature.route property, referencing routes defined through the featureRoute() function.

    Feature Routes are based on a very simple concept: allow the application state to drive the routes! It operates through a series of registered functional callback hooks, which determine the active screen based on an analysis of the the overall appState. This is particularly useful in feature-based routing, because each feature can promote their own UI components in an encapsulated and autonomous way! Because of this, feature-router is a preferred routing solution for feature-u.

    Feature Routes is the recommended solution for feature-based navigation. You can find more information in the Feature Based Routes section.

    Please refer to the feature-router documentation for complete details.

Launching Your Application

By interpreting the set of Aspects and Features that comprise an application, feature-u can actually coordinate the launch of your application (i.e. start it running)!

This is accomplished through the launchApp() function.

  • It manages the setup and configuration of the frameworks in use, such as redux, redux-logic, etc. This is based on a set of supplied plugable Aspects that extend feature-u, integrating external frameworks to match your specific run-time stack.

  • It facilitates application life-cycle hooks on the Feature object, allowing features to manage things like: initialization and injecting root UI elements, etc.

  • It creates and promotes the App object which contains the publicFace of all features, facilitating a cross-communication between features.

As a result, your application mainline is very simple and generic. There is no real app-specific code in it ... not even any global initialization! That is because each feature can inject their own app-specific constructs!! The mainline merely accumulates the Aspects and Features, and starts the app by invoking launchApp():

src/app.js

import React             from 'react';
import ReactDOM          from 'react-dom';
import {launchApp}       from 'feature-u';
import {routeAspect}     from 'feature-router';      // *1*
import {reducerAspect}   from 'feature-redux';       // *1*
import {logicAspect}     from 'feature-redux-logic'; // *1*
import SplashScreen      from './util/comp/SplashScreen';
import features          from './feature';           // *2*


// define our set of "plugable" feature-u Aspects, conforming to our app's run-time stack
const aspects = [ // *1*
  routeAspect,    // Feature Routes ... order: early - it's DOM injection does NOT support children
  reducerAspect,  // redux          ... order: later - <Provider> DOM injection should be on top
  logicAspect,    // redux-logic    ... order: N/A   - NO DOM injection
];


// configure our Aspects (as needed)
// ... Feature Route fallback screen (when no routes are in effect)
routeAspect.config.fallbackElm$ = <SplashScreen msg="I'm trying to think but it hurts!"/>;


// launch our app, exposing the feature-u App object (facilitating cross-feature communication)!
export default launchApp({         // *4*
  aspects,                         // *1*
  features,                        // *2*
  registerRootAppElm(rootAppElm) { // *3*
    ReactDOM.render(rootAppElm,
                    getElementById('myAppRoot'));
  }
});

The Aspect collection (see *1* in the code snippet above) reflects the frameworks of our run-time stack (in our example redux, redux-logic, and feature-router ) and extend the acceptable Feature properties (Feature.reducer, Feature.logic, and Feature.route respectively) ... see: Extendable aspects. In this case, all our Aspects were pulled from external npm packages, however you can define your own using createAspect().

All of our supplied app features are accumulated from the features/ directory ... (see *2* in the code snippet above).

React Registration

The launchApp() function uses a registerRootAppElm() callback (see *3* in the code snippet above) to catalog the supplied rootAppElm to the specific React platform in use.

API: registerRootAppElm(rootAppElm): void

NOTE regarding the rootAppElm:

  • Typically the supplied rootAppElm will have definition, based on the Aspects and Features that are in use. In this case, it is the responsibility of this callback to register this content in some way (either directly or indirectly).

  • However, there are atypical isolated cases where the supplied rootAppElm can be null. This can happen when the app chooses NOT to use Aspects/Features that inject any UI content. In this case, the callback is free to register it's own content.

Because this registration is accomplished by your app-specific code, feature-u can operate in any of the React platforms, such as:

React Web

import ReactDOM from 'react-dom';
...
export default launchApp({
  aspects,
  features,
  registerRootAppElm(rootAppElm) { // *3*
    ReactDOM.render(rootAppElm,
                    getElementById('myAppRoot'));
  }
});

React Native

import {AppRegistry} from 'react-native';
...
export default launchApp({
  aspects,
  features,
  registerRootAppElm(rootAppElm) { // *3*
    AppRegistry.registerComponent('myAppKey',
                                  ()=>rootAppElm); // convert rootAppElm to a React Component
  }
});

Expo

import Expo from 'expo';
...
export default launchApp({
  aspects,
  features,
  registerRootAppElm(rootAppElm) { // *3*
    Expo.registerRootComponent(()=>rootAppElm); // convert rootAppElm to a React Component
  }
});

App Object

An App object is emitted from the launchApp() function, which promotes the accumulated Public API of all features (see: publicFace and the App Object).

The App object should be exported (see *4* in the code snippet above) so other modules can access it (providing Cross Feature Communication). Please note that depending on the context, there are various techniques by which the App object can be accessed (see: Accessing App).

The App object contains named feature nodes, structured as follows:

App.{featureName}.{publicFace}

The app object can be used for two distinct purposes:

  1. to access a feature's Public API, and
  2. to determine whether a feature exists.

Feature Public API

The App object promotes the feature's Public API (i.e. it's publicFace).

As an example, an application that has two features (featureA, and featureB) will look like this:

app: {
  featureA: {
    action: {
      open(),
      close()
    }
  },
  featureB: {
  }
}

You can see that featureA is promoting a couple of actions (open(), close()) in it's publicFace, while featureB has NO publicFace.

Does Feature Exist

The App object can be used to determine if a feature is present or not. If a feature does not exist, or has been disabled, the corresponding app.{featureName} will NOT exist.

  • It could be that featureA will conditionally use featureB if it is present.

    if (app.featureB) {
      ... do something featureB related
    }
    
  • It could be that featureC unconditionally requires that featureD is present. This can be checked in the appWillStart() Application Life Cycle Hook.

    appWillStart({app, curRootAppElm}) {
      assert(app.featureD, '***ERROR*** I NEED featureD');
    }
    

results matching ""

    No results matching ""