feature-u is a utility library that facilitates feature-based
project organization in your react
project. It assists
in organizing your project by individual features.
Most software engineers would agree that organizing your project by feature is much preferred over type-based patterns. As 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 insights on feature-based design and structure).
feature-u is a utility library that manages and streamlines this process. It automates some of the mundane details of managing features and helps in promoting features that are plug-and-play.
The following article is an introduction to feature-u with
examples from a real-world app: eatery-nod
(where feature-u
was conceived): feature-u: Feature Based Project Organization for
React.
feature-u allows you to focus your attention on the "business end" of your features!
-
peerDependencies:
feature-u has a peerDependency on react (most likely you should already have this installed ... but just in case):
npm install --save react
-
the main event:
npm install --save feature-u
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 Interface,
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 a Fassets
object
(which promotes the Public Face of each feature).
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: 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 app ... 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.
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). Because not everyone uses the same frameworks, feature-u accomplishes this through Extendable Aspects (you can find them in external NPM packages, or you can create your own). 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).
The basic usage pattern of feature-u is to:
-
Choose the
Aspects
that you will need, based on your selected frameworks (i.e. your run-time stack). This extends the aspect properties accepted by the Feature object (for example:Feature.reducer
forredux
, orFeature.logic
forredux-logic
).Typically these Aspects are packaged separately in NPM, although you can create your own Aspects (if needed).
-
Organize your app into features.
-
Each feature should be located in it's own directory.
-
How you break your app up into features will take some time and thought. There are many ways to approach this from a design perspective.
-
Each feature will promote it's aspect content through a
Feature
object (usingcreateFeature()
).
-
-
Your mainline starts the app by invoking
launchApp()
, passing allAspects
andFeatures
.
Easy Peasy!!
Here is a sample directory structure of an app that uses feature-u:
src/
app.js ... launches app using launchApp()
feature/
index.js ... accumulate/promote all Feature objects (within the app)
featureA/ ... a feature (within the app)
actions.js
appDidStart.js
appWillStart.js
comp/
ScreenA1.js
ScreenA2.js
index.js ... promotes featureA object using createFeature()
logic.js
reducer.js
route.js
featureB/ ... another feature
...
util/ ... common utilities used across all features
...
Each feature is located in it's own directory, containing it's aspects (actions, reducers, components, routes, logic, etc.).
Each feature promotes it's aspect content through a
Feature
object (using createFeature()
).
src/feature/featureA/index.js
import {createFeature} from 'feature-u';
import reducer from './state';
import logic from './logic';
import route from './route';
import appWillStart from './appWillStart';
import appDidStart from './appDidStart';
export default createFeature({
name: 'featureA',
enabled: true,
fassets: {
define: {
'api.openA': () => ... implementation omitted,
'api.closeA': () => ... implementation omitted,
},
},
reducer,
logic,
route,
appWillStart,
appDidStart,
});
The docs will fill in more detail, but for now notice that the feature
is conveying reducers, logic modules, routes, and does some type of
initialization (appWillStart/appDidStart). It also promotes something
called fassets
(feature assets - the Public Face of a feature) with
openA()
and closeA()
functions which will be publicly promoted to
other 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
Aspects
and Features
, and starts the app by invoking
launchApp()
:
src/app.js
import ReactDOM from 'react-dom';
import {launchApp} from 'feature-u';
import {createRouteAspect} from 'feature-router';
import {createReducerAspect} from 'feature-redux';
import {createLogicAspect} from 'feature-redux-logic';
import features from './feature';
// launch our app, exposing the Fassets object (facilitating cross-feature communication)
export default launchApp({ // *4*
aspects: [ // *1*
createRouteAspect(), // Feature Routes ... extending: Feature.route
createReducerAspect(), // redux ... extending: Feature.reducer
createLogicAspect(), // redux-logic ... extending: Feature.logic
],
features, // *2*
registerRootAppElm(rootAppElm) { // *3*
ReactDOM.render(rootAppElm,
getElementById('myAppRoot'));
}
});
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 exampleredux
,redux-logic
, andfeature-router
) and extend the acceptable Feature properties (Feature.reducer
,Feature.logic
, andFeature.route
respectively) ... see:Extendable aspects
-
all of our app features are supplied (accumulated from the
features/
directory) -
a
registerRootAppElm()
callback is used to catalog the suppliedrootAppElm
to 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: React Web, React Native, Expo, etc. ... see:React Registration
-
as a bit of a preview, the return value of
launchApp()
is aFassets
object, which promotes the accumulated Public Face of all features, and is exported to provideCross Feature Communication
... here is what thefassets
looks like (for this example):fassets: { api: { openA(), closeA(), }, }
Hopefully this gives you a basic feel of how feature-u operates.
The sample above just scratches the service!
Comprehensive Documentation can be found at https://feature-u.js.org/, which includes both a Dev Guide (building concepts with full and thorough examples), and a complete API Reference.
The benefits of using feature-u include:
-
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
Want to see a real feature-u app?
eatery-nod
is the application where feature-u was
conceived. It is a react-native
expo
mobile
app, and is one of my sandbox applications that I use to test
frameworks. I like to develop apps that I can use, but have enough
real-world requirements to make it interesting.
eatery-nod
randomly selects a "date night" restaurant
from a pool of favorites. My wife and I have a steady "date night",
and we are always indecisive on which of our favorite restaurants to
frequent :-) So eatery-nod
provides the spinning
wheel!
I hope you enjoy feature-u, and comments are always welcome.
</Kevin>