/feature-u

Feature Based Project Organization for React

Primary LanguageJavaScriptMIT LicenseMIT

feature-u

feature-u is a utility library that facilitates Feature-Driven Development in your react project. It provides tangible assistance in promoting features that are truly plug-and-play.

You can quickly "come up to speed" with feature-u by viewing the Playful Features Video, that builds concepts, and demonstrates them in a real world app (eatery-nod-w).

Build Status Codacy Badge Codacy Badge Known Vulnerabilities NPM Version Badge

Introduction

Feature-Driven Development (FDD) has become more prevalent in today's landscape, and for good reason! This is a lightweight Agile technique, manifest in a project structure where your code is organized by what it accomplishes (i.e. features), rather than lumping all modules of like types into separate blobs of components, routes, logic, actions, etc. This technique greatly improves your code comprehension because there is a direct correlation between the problem space (the requirements) and the implementation (the code)!

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!

However, FDD involves more than just organizing your project's directory structure into features. You want to encapsulate your features into isolated and self-sufficient modules, and yet they must also be able to collaborate with other features.

Truly isolated FDD is something that is incredibly powerful! You can improve the modularity of your system by loosely coupling your features, making your app easier to understand, develop, test, and refactor. If done right, your features actually become "miniature applications" that simply plug-and-play (where the mere existence of a feature dynamically exudes the characteristics it implements)!

As it turns out there are a number of hurdles to overcome in order to accomplish this. Rather than being left to fend for yourself, feature-u has already tackled these hurdles.

feature-u promotes a new and unique approach to code organization and app orchestration.

With feature-u ...

  • your features can be encapsulated and isolated

  • they can collaborate with other features in an extendable way

  • your components can employ cross-feature composition (even injecting their content autonomously)

  • your features can initialize themselves

  • they can be activated or deactivated at run-time

  • and as a bonus, your frameworks will even auto-configure with only the active features (via a plugin architecture)

feature-u opens new doors into the exciting world of FDD. It frees you up to focus your attention on the "business end" of your features!

Install

  • 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

feature-u Basics

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).

Basic Concepts

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 ComponentsRoutesState Management (actions, reducers, selectors)Business LogicStartup Initialization Codeetc. 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).

Usage

The basic usage pattern of feature-u is to:

  1. Organize your app into features.

    • Each feature should be located in it's own directory, typically within a features/ parent 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 characteristics through a Feature object (using createFeature()).

    • A features/index.js module will accumulate and promote all of the Features that make up your entire application.

  2. Choose the Aspects that you will need, based on your selected frameworks (i.e. your run-time stack).

    • Typically these Aspects are packaged separately in NPM, although you can create your own (if needed).

    • Each [Aspect] will extend the properties accepted by the Feature object (for example: Feature.reducer for redux, or Feature.logic for redux-logic).

    • A best practice is to organize an aspects/ directory, mimicking the same pattern as your features/ directory.

    • An aspects/index.js module will accumulate and promote all of the aspects used by your application.

  3. Your mainline will start the app by invoking launchApp(), passing all Features and Aspects.

Easy Peasy!!

Directory Structure

Here is a sample directory structure of an app that uses feature-u:

src/
  app.js              ... launches app using launchApp()

  aspects/
    index.js          ... accumulate/promote all Aspect objects (used by the app)

                      ... NOTE: the aspects/ dir can contain local Aspects, however
                                because most Aspects are pulled from external 
                                NPM packages, this directory is typically empty!

  features/
    index.js          ... accumulate/promote all Feature objects (for the entire app)

    featureA/         ... a feature (within the app)
      actions.js
      appDidStart.js
      appWillStart.js
      comp/
        ScreenA1.js
        ScreenA2.js
      feature.js      ... promotes featureA object using createFeature()
      index.js        ... redirect parent dir import to the feature object
      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.).

Feature Object

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.

Note: Feature directory imports are redirected to the feature object reference ... for example:

src/features/featureA/index.js

// redirect parent dir import to the feature reference
export {default} from './feature';

launchApp()

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 Aspects, and starts the app by invoking launchApp():

src/app.js

import ReactDOM     from 'react-dom';
import {launchApp}  from 'feature-u';
import features     from 'features';
import aspects      from 'aspects';

// launch our app, exposing the Fassets object (facilitating cross-feature-communication)
export default launchApp({          // *4*
                                    
  features,                         // *1*
  aspects,                          // *2*

  registerRootAppElm(rootAppElm) {  // *3*
    ReactDOM.render(rootAppElm,
                    document.getElementById('root'));
  },
});

Here are some important points of interest (match the numbers to *n* in the code above):

  1. all app features are supplied (accumulated from the features/ directory) ... see: Feature Accumulation

  2. the app aspects (i.e. the run-time stack) are supplied (accumulated from the aspects/ directory) ... see: Aspect Accumulation

  3. a registerRootAppElm() callback is used to catalog the supplied rootAppElm 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

  4. as a bit of a preview, the return value of launchApp() is a Fassets object, which promotes the accumulated Public Face of all features, and is exported to provide Cross Feature Communication ... here is what the fassets looks like (for this example):

    fassets: {
      api: {
        openA(),
        closeA(),
      },
    }

Hopefully this gives you a basic feel of how feature-u operates.

Comprehensive Documentation

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.

Benefits

The benefits of using feature-u include:

  1. Feature Encapsulation: isolating feature boundaries improves code manageability

  2. Feature Collaboration: promote Cross Feature Communication through a well-defined feature-based Public Interface

  3. Feature Based UI Composition: facilitate seamless cross-feature component composition

  4. Application Life Cycle Hooks: features can initialize themselves without relying on an external process

  5. Feature Enablement: enable/disable features through a run-time switch

  6. Minimize Feature Order Dependency Issues during in-line code expansion

  7. Framework Integration: automatically configure used framework(s) (matching the app's run-time-stack) by accumulating all feature aspects (employing an extendable API)

  8. UI Component Promotion: features can autonomously promote their UI components through Feature Based Route Management

  9. Single Source of Truth: is facilitated in a number of ways within a feature's implementation

  10. Simplified App Startup: launching an app can be accomplished through a single line of executable code!

  11. Operates in any React Platform React Web, React Native, Expo, etc.

  12. Plug-and-Play: features can be more easily added or removed

Real Example

Want to see a real feature-u app?

eatery-nod-w 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-w 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-w provides the spinning wheel!

Video Presentation

When grasping a new concept, it is helpful to see it in action!

You can quickly "come up to speed" with feature-u by reviewing the Playful Features Video.

This is a screencast video of a presentation that has been given to a number of regional conferences and local meetup groups. It closely follows the Basic Concepts section, and demonstrates the newly developed concepts in a real world app (eatery-nod-w).

I hope you enjoy feature-u, and comments are always welcome.

</Kevin>