Note: On 8/14/2018 feature-u V1 was released, that re-designed Cross Feature Communication to include UI Composition as a core offering. This article covers the V1 release. The first article, based on feature-u V0, can be found here. We are very excited about this update because it promotes one solution for all feature collaboration!
Most developers would agree that organizing your project by feature is much preferred over type-based patterns. Because application domains grow in the real world, project organization by type simply doesn’t scale, it just becomes unmanageable!
There are a number of good articles that discuss this topic with insight on feature-based design and structure (see: References below). However when it comes to the implementation, you are pretty much left to fend for yourself.
feature-u is a utility library that manages and streamlines this process. It automates the mundane details of managing features and helps to promote features that are truly plug-and-play.
This article provides a foundation of feature-u concepts and terminology, building insight into how you can promote individual plug-and-play features within your project. It makes the case for why feature-u was developed and gives you a better understanding of it’s benefits.
feature-u opens new doors into the exciting world of feature-based development. It frees you up to focus your attention on the “business end” of your features!
At a Glance
For your convenience, this Table of Contents (TOC) links directly to each section. Also note that each section title links back to the TOC.
Feature Based Development Segregating Features Feature Goals Feature Runtime Consolidation Feature CollaborationThe feature-u Solution launchApp() Feature Object aspects Running the App App Initialization Framework Configuration Launching Your Application Cross Feature Communication Feature Based UI Composition Resource Contracts Feature EnablementIn SummaryBenefitsReferences
Please help me get the word out on feature-u. Your claps determine the distribution/promotion of this article. If you think feature-u has potential, please give this article multiple claps :-)
At a 30,000 ft view, feature-based development (as in most software) is all about dissecting hard problems into smaller pieces. Even when I started my career (back in the 70's), this was a prominent quote:
“All problems in computer science can be solved by another level of indirection.” David Wheeler
By breaking up your application into features, each feature can focus on a more specific and isolated set of tasks. In some ways you can think of a feature as a “mini application”!
There are many design considerations in defining your feature boundaries. You can find several articles on this topic that provide insight on feature-based design.
For the most part, these considerations are part of the design of each individual project. While feature-u does not dictate overall design considerations, it does facilitate good feature-based principles (such as encapsulation). This will be the focus of this article.
If you are like me, when you think about feature-based development, the first thing that comes to mind is to isolate your code into feature directories.
In doing this your code is organized by what it accomplishes (i.e. features), rather than what it is (i.e. components, routes, logic, actions, reducers, selectors, etc.).
By segregating your features into individual directories, there is a semblance of isolation.
Our goal is to encapsulate each feature in such a way as to make them truly plug-and-play. But how is this accomplished?
The directory structure is just a start. There are several hurdles that must be overcome to realize our goal …
- How do we encapsulate and isolate our features, while still allowing them to collaborate with one another?
- How can selected features introduce start-up initialization (even injecting utility at the root DOM), without relying on some external startup process?
- How can feature-based UI Composition be accomplished in an isolated and autonomous way?
- How do we configure our chosen frameworks now that our code is so spread out?
- How do we enable/disable selected features which are either optional, or require a license upgrade?
In short, how do we achieve a running application from these isolated features?
When you boil it all down, there are two overriding characteristics that must be accomplished to achieve our goals:
Feature Runtime Consolidation: pulling our features back together into one running application
Feature Collaboration: provide a mechanism by which our features can interact with one another
As it turns out, everything else is a byproduct of these two artifacts. Let’s take a closer look at each of these items.
Now that we have isolated our features into separate entities, how do we bring them back together so they run as one application? We must be able to pull and configure various aspects of our individual features, and “launch” them as a single homogeneous running application.
This concern can be further divided into two sub-concerns:
Some features may require certain startup initialization. As an example, a feature that encapsulates some DB abstraction will rely on a run-time setup of a DB service.
Certainly we don’t want to rely on some global app logic to accomplish this (once again, we want our features to be encapsulated and self-sufficient).
If your application relies on other frameworks, chances are there are resources contained within each feature that must be accumulated and fed into the framework configuration process.
How is this accomplished?
The second characteristic (mentioned above) is Feature Collaboration — providing a mechanism by which our features can interact with one another.
A best practice of feature-based development (to the extent possible) is to treat each feature as an isolated implementation. Most aspects of a feature are internal to that feature’s implementation (for example, actions are typically created and consumed exclusively by logic/reducers/components that are internal to that feature).
From this perspective, you can think of each feature as its own isolated mini application.
With that said, however, we know that “no man is an island”! Any given feature ultimately exists as part of a larger application. There are cases where a feature needs to promote a limited subset of its aspects to other features. For example, a feature may need to:
- be knowledgeable of some external state (via a selector)
- emit or monitor actions of other features
- consolidate component resources from other features — as in UI Composition
- invoke the API of other features
- etc. etc. etc.
To complicate matters, as a general rule, JS imports should NOT cross feature boundaries. The reason being that this cross-communication should be limited to public access points — helping to facilitate true plug-and-play.
Given all this then, how is Cross Feature Communication achieved in a way that doesn’t break encapsulation?
Features need a way to promote their Public Interface to other features, and consume other feature’s Public Assets.
Let’s take a look at the solution feature-u provides for all of these goals. The following sections will build feature-u concepts incrementally.
launchApp() is an essential utility in feature-u. It is an agent, working on your behalf, which provides the foundation that accomplishes all the goals of feature-u! It facilitates both
Feature Runtime Consolidation and
With this utility, your mainline startup process is extremely simple … it merely invokes
launchApp(), and you are done!
launchApp() function actually starts your application running, employing various hooks that drive BOTH App Initialization and Framework Configuration!
How does this work? What are the bindings to
launchApp()? ... let's delve a bit deeper…
This is the primary input to
In feature-u, “aspect” (little “a”) is a generalized term used to refer to the various ingredients that (when combined) constitute your application. Aspects can take on many different forms: UI Components • Routes • State Management(actions, reducers, selectors) • Business Logic • Startup Initialization Code • etc. etc. etc.
Not all aspects are of interest to feature-u … only those that are needed to setup and launch the application … all others are considered 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 object is merely a lightweight container that holds aspects of interest to feature-u. These aspects can either be
Built-In aspects (from core feature-u), or
Extendable aspects (from plugin extensions).
Let’s see how
launchApp() accommodates the two sub-goals of running the app:
This allows each feature to perform app-specific initialization, and even inject components into the root of the app.
There are two hooks:
Feature.appWillStart()- invoked one time at app startup time
Feature.appDidStart()- invoked one time immediately after app has started
Application Life Cycle Hooks greatly simplify your app's mainline startup process, because initialization specific to a given feature can be encapsulated in that feature.
A fundamental goal of feature-u is to automatically configure the framework(s) used in your run-time-stack (by accumulating the necessary resources across all your features). This greatly reduces the boilerplate code within your app.
How can this be accomplished when there are so many frameworks out there … and every project uses a different mix?
feature-u is extendable! It operates in an open plugable architecture where Extendable Aspects integrate feature-u to other frameworks, matching your specific run-time stack. This is good, because not everyone uses the same frameworks!
Extendable Aspects can be found in external NPM packages (the normal case), or you can create your own using
createAspect() (a more advanced topic).
AspectContentacross all features
- perform some desired setup and configuration
- expose it’s functionality in some way (typically a framework integration)
Aspect.name: 'reducer') permits a
- and the
Aspect.name: 'logic') permits a
It is important to understand that the interface to your chosen frameworks is not altered in any way. You use them the same way you always have (just within your feature boundary). feature-u merely provides a well-defined organizational layer, where the frameworks are automatically setup and configured by accumulating the necessary resources across all your features.
In feature-u, the 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
Features, and starts the app by invoking
Here are some important points of interest (match the numbers to
*n* in the code above):
- the supplied
Aspects(pulled from separate npm packages) reflect the frameworks of our run-time stack (in our example
feature-router) and extend the acceptable Feature properties (
Feature.routerespectively) ... see:
- all of our app features are supplied (accumulated from the
registerRootAppElm()callback is used to catalog the supplied
rootAppElmto the specific React platform in use. Because this registration is accomplished by your app-specific code, feature-u can operate in any of the React platforms, such as:
- as a bit of a preview, the return value of
Fassets object, which promotes the accumulated Public Face of all features, and is exported to provide
Cross Feature Communication.
In support of Feature Collaboration that doesn’t break encapsulation, feature-u promotes feature-based resources through something called
fassets (feature assets). This is how all Cross Feature Communication is accomplished. You can think of this as the Public Face of a feature.
SideBar: The term
fassets is a play on words. While it is pronounced "facet" and is loosely related to this term, it is spelled fassets (i.e. feature assets).
A feature can expose whatever it deems necessary through the built-in
Feature.fassets aspect). There is no real constraint on this resource. It is truly open.
fassets aspect has a
define directive where resources are cataloged.
Here is a simple example of how
fassets are defined:
SideBar: There are several ways to obtain access the
Fassets object (see
Obtaining fassets object).
This is an example of a push philosophy. Here the supplier is is simply publicly promoting a resource for other features to use (take it or leave it). The supplier is merely saying: “this is my Public Face”.
You can find more information about this topic in
Cross Feature Communication.
It is common for a UI component to be an accumulation of sub-components that span several features. As a result, UI Composition is a very important part of Cross Feature Communication.
In support of this, feature-u introduces the
withFassets() Higher-order Component (HoC) that auto-wires fasset properties into a component. This is a common pattern popularized by Redux
connect() (simplifying component access to application state).
Here is how a component would access a
company.logo (defined by another feature):
withFassets() HoC auto-wires named feature assets as component properties through the
mapFassetsToPropsStruct hook. In this example, because the
Logo property is a component,
MyComponent can simply reference it using JSX.
You can find more information about this topic in
It is common for UI Composition to be represented as a contract, where a component in one feature has a series of injection needs that are to be supplied by other features.
fassets aspect has additional constructs to facilitate this contractual arrangement, allowing feature-u to provide more validation in the process.
Rather than just defining resources in one feature and using them in another:
- A given feature can specify a series of injection needs using the
fassets.usedirective. This identifies a set of injection keys that uniquely identify these resources.
- Other features will supply this content using the
fassets.defineUsedirective, by referencing these same injection keys.
This represents more of a pull philosophy. It gives feature-u more knowledge of the process, allowing it to verify that supplied resources are correct.
*) can be used to add additional dynamics to the process, allowing features to inject their content autonomously.
Here is a
main feature that is pulling in a series of sub-components (links and bodies) from other features:
Because our specification includes wildcards, a series of definitions will match!
Here is the
MainPage component that fulfills the usage contract:
withFassets() encounters wildcards (
*), it merely accumulates all matching definitions, and promotes them as arrays.
Through this implementation, any feature may dynamically inject itself in the process autonomously! In addition, this dynamic implicitly handles the case where a feature is dynamically disabled (very kool indeed)!!
The following snippets are taken from other features that supply the definitions for the content to inject:
Two external features (cart and search) define the content that is requested by the main feature.
fassets.defineUse directive requires that the resource keys match a
fassets.use feature request. This is the contract that provides feature-u insight when enforcing it's validation.
SideBar: Because we are also dealing with navigation, we introduce
react-router into the mix (with the
Route components). Because of RR's V4 design, our routes are also handled through component composition (see
Feature Based Routes for more information).
You can find more information about this topic in
Features can be dynamically disabled by setting the
Feature.enabled boolean property (part of the
In this example, it is just as though the
sandbox feature doesn't exist. In other words it has been logically removed.
Typically, this indicator is based on some run-time expression, allowing packaged code to be dynamically enabled/disabled during the application’s start-up process:
This dynamic is useful in a number of different situations. For example:
- some features may require a license upgrade
- other features may only be used for diagnostic purposes, and are disabled by default
You can find more information about this topic in
The following diagram summarizes feature-u’s Basic Concepts (as discussed above):
There are many benefits in using feature-u!
The two fundamental artifacts from which most benefits are derived are:
- A formal means by which features can collaborate with one another (
Cross Feature Communication), making them truly plug-and-play
This includes the ability for
UI Compositionto cross feature boundaries. It even allows UI Content to be injected autonomously. This is something that has to be seen ... it shows off feature-u very well.
- A significant reduction in boilerplate code through:
Auto configuration of the frameworks in-use (via plugin extensions —
Startup initialization that is encapsulated within features (via
Application Life Cycle Hooks)
The following list of benefits can be directly correlated to the considerations that formed the basis of why feature-u was developed (see:
- Feature Encapsulation: isolating feature boundaries improves code manageability
- Feature Collaboration: promote Cross Feature Communication through a well-defined feature-based Public Interface
- Feature Based UI Composition: facilitate seamless cross-feature component composition
- Application Life Cycle Hooks: features can initialize themselves without relying on an external process
- Feature Enablement: enable/disable features through a run-time switch
- Minimize Feature Order Dependency Issues during in-line code expansion
- Framework Integration: automatically configure used framework(s) (matching the app’s run-time-stack) by accumulating all feature aspects (employing an extendable API)
- UI Component Promotion: features can autonomously promote their UI components through Feature Based Route Management
- Single Source of Truth: is facilitated in a number of ways within a feature’s implementation
- Simplified App Startup: launching an app can be accomplished through a single line of executable code!
- Operates in any React Platform React Web, React Native, Expo, etc.
- Plug-and-Play: features can be more easily added or removed
feature-u allows you to focus your attention on the “business end” of your features!
Go forth and compute!!
- A feature based approach to React development … Ryan Lanciaux
- How to better organize your React applications? … Alexis Mangin
- The 100% correct way to structure a React app (or why there’s no such thing) … David Gilbertson
- Redux for state management in large web apps … David Clark