/react-adventure-pattern-proposal

Adventure Proposal Design is a high-ending modular architecture with screen centric development for building spa applications with clean separation of concerns, high cohesion and maintainability. Grow big, keep smart.

Primary LanguageShellMIT LicenseMIT

[React] Adventure Design Proposal

high-ending modular architecture with screen centric development for building spa applications with clean separation of concerns, high cohesion and maintainability.

Grow big, keep smart.

Table of Contents

Introduction

React.js introduced a new way to create modern web applications by giving the developers power to code in a very concise and declarative way, besides its short learning curve.

Due to its popularity over the years, you can find with easy many articles about code pattern and even greats JSX Style Guide to follow, and it's great!

But when it comes to project design, there's a real struggle to find or create one that provides scalability and enforces a development contract between developers, after all, whenever a new developer joins the team, another structure pattern might happen and will happen if he/she does not understand its project structure.

And the result of that? A real mess over time.

For this reason we created the Adventure Design Proposal in order to provide a understandable, reliable, maintainable and scalable environment over time.


⬆ back to top


Why Adventure as a proposal name?

Because the whole journey until this point have been an adventure and we would like to the adopter have the same adventure, focusing more on coding quality and less on stressing matters.


⬆ back to top


Goal

In addition to the points mentioned in the introduction, we, as Engineers, we're constantly looking for new ways to improve our code base with the minimum refactor effort, after all, something might break.

By all those reason, we came up with this approach, which give us the ability to:

  • Establish a development contract between developers.
  • Ensure application scalability.
  • Guarantee the operability, simplicity and evolvability.
  • Provide a readable and searchable structure.
  • Make every component, function or screen self-evident.
  • Implement new tools and safely delete legacy/unused with the minimum effort and greater confidence.
  • To easily create or update different part of the application in the shortest possible time.

Paraphrasing Steve Krug:

"Making every component, function or screen self-evident is like having good lighting in a store: it just makes everything seem better."


⬆ back to top


Naming Convention

Extensions

Use .jsx for React components, otherwise use .js.


File name

All file name should be written in Lower Camel Case. E.g. profile.jsx, modalHeader.jsx.


Optional file name prefix

In some cases when you follow the Modular Structure and end up with just a few files inside the subdomain directory, you can choose to use a file prefix instead of placing then inside a subdomain folder. This might increase your structure readability and file search capability.

For that, you should follow the following pattern: [subdomain].[fileName].[ext]

Example:

// let's assume you have the file tree shown below.
// you can turn this tree
configurations
├── webpack
   ├── configuration // this is the Modular Structure 'subdomain'
      ├── development.js
      ├── production.js
      └── tests.js


// into this simpler and more readable one
configurations
├── webpack
   ├── configuration.development.js
   ├── configuration.production.js
   └── configuration.tests.js

Note: subdomain can also be read as group, association, common denomination.


Component Naming

All React Component name declaration should be written in Upper Pascal Case and should be named after its modular structure path: Domain + Subdomain.

See Modular Structure for more info.

Example:

// bad - Has Domain naming BUT in lower case
// --------------------------------------------
// | Directory path: button/index.jsx         |
// | Component name: button                   |
// --------------------------------------------
class button extends Component { ... }
const button = props => ...

// also bad - Domain naming + unnecessary extra name
// --------------------------------------------
// | Directory path: button/index.jsx         | 
// | Component name: buttonComponent          |
// --------------------------------------------
class buttonComponent extends Component { ... }
const buttonComponent = props => ...


// good - Domain naming
// --------------------------------------------
// | Directory path: button/index.jsx         |
// | Component name: Button                   |
// --------------------------------------------
class Button extends Component { ... }
const Button = props => ...

// also good - Domain + Subdomain naming
// --------------------------------------------
// | Directory path:  modal/header/index.jsx  |
// | Component name:  ModalHeader             |
// --------------------------------------------
class ModalHeader extends Component { ... }
const ModalHeader = props => ...

Reference Naming

Use the Component Name as referencing name. It should follow the same guiding principles.

// bad 
import Modal from './modal/index';
import Header from './modal/header';
import Body from './modal/body';
import Footer from './modal/footer';

// good
import Modal from './modal';
import ModalHeader from './modal/header';
import ModalBody from './modal/body';
import ModalFooter from './modal/footer';

Note: As Airbnb React Style Guide, we don't declare index file as part of the import statement.


⬆ back to top


Overview

Naming Convention


⬆ back to top


Elements

The design proposal is composed by 6 basic elements: components, enhancers, screen, state, styles and utils.

Components

The Components represents small units of the UI (User Interface) and are presentational; it should be open for extension and have only one reason (responsibility) to exists. You can also define reason or responsibility as a reason for change.

It should be an autonomous part of your application.

Component example: Button, Avatar, List.

Connects with: others Components.


Enhancers

Enhancers are functions that receives class/function/method/property and return given element intensified/improved.

Enhancers example: Hight Order Components and Decorators.

Connects with: Components and State.


Screen

Screen is the central part of your application, it is responsible to compose the page with Components, consume data from State and handle all the user actions.

It's the component target of your routing system.

Screen example: Home, Contact, About Us.

Connects with: Components, Enhancers and State.

Layout

Screens frequently shares the same visual structure: header, sidebar and footer for example.

For that reason the Layout Screen is used to centralized those commons UI (User Interface) and its logic into a single and reusable screen.

Layout, although is an optional Screen, we strongly recommend you to have it. It might shorten the reconciliation process time when working with a routing system, due to the logical abstraction adopted.

Layout example:

Page layout example

Note: In case you have a multiple layouts system, the Modular Structure allows you to have a nice and concise structure by using the subdomain directory.

E.g. Screens/Layouts/Errors, Screens/Layouts/Account, Screens/Layouts/Default, so on.

Connects with: Components, Enhancers, State and Screen.


Shared

It is common to find designs specifications that shares the same visual structure (UI) and logic across others Screens, such as breadcrumbs, image sliders and product cards.

For that reason the Shared folder is used to centralized those commons UI (User Interface) and its logic into a single and shareable endpoint, avoiding then, unnecessary logic and interface duplications.

Connects with: Components, Enhancers, State and Screen.


State

The State refers to the Data State, and is responsible to contain and manage the data flow core of your application; this is the right place to put your Business Logic.

State example: Redux, Mobx, Flux, Middleware and so on. Tip: re-ducks proposal - a modular redux approach.

Connects with: no one.


Styles

Styles as the name indicates, is the place you should put all the global styles of your application. If your style is custom case, you should take a look in the Modular Approach section.

Styles exaple: .css, .scss, .sass, .less

Connects with: Global application.


Utils

Utils is the place where you should place your global code snippets. If your code snippet is not meant to be global, you should take a look in the Modular Approach section.

Connects with: Global application, except Styles.


⬆ back to top


Interactions

  • The Components are responsible to the UI (User Interface) of your application. It receives inputs directly from the Screen or from the Enhancers.
  • The Screen is the presentation of a component composition and is interconnected with the State; it is responsible to consume the State and to handle the user actions.
  • The State is accountable for the application's data flow core. It performs data manipulation according to your business logic, providing normalized data and handling actions for the Screen or for the Enhancers.
  • The Enhancers, in its turn, represents a improvement of a given Screen, intensifying its logic. It also can be composed of Components and/or be consumer of the State.
  • The Styles is responsible to manage the global styles of your application.
  • The Utils, as well as the Styles, is responsible for the global code snippets.

⬆ back to top


Overview

The following diagram represent the interaction flow between the elements.

Diagram of Interactions


⬆ back to top


Design Proposal Directory Tree

Basic Elements Composition

The following directory tree represents basic elements hierarchy approached in the past section.

.
└── source
    ├── components
    ├── enhancers
    ├── screen
    │   ├── layout
    │   └── shared
    ├── state
    ├── styles
    └── utils

Real World Implementation

.
├── configurations
│   ├── jest
│   └── webpack
│       ├── config.dev.js
│       ├── config.prod.js
│       └── config.rules.js
└── source
    ├── components
    │   ├── index.js
    │   └── v1
    │       ├── breadcrumbs
    │       │   ├── breadcrumbs.jsx
    │       │   ├── breadcrumbs.scss
    │       │   └── index.js
    │       ├── button
    │       │   ├── button.jsx
    │       │   ├── button.scss
    │       │   └── index.js
    │       ├── dropdown
    │       │       ├── index.js
    │       │       ├── dropdown.jsx
    │       │       ├── divider.jsx
    │       │       ├── divider.styles.js
    │       │       └── styles.js
    │       ├── index.js
    │       └── modal
    │           ├── body
    │           │   ├── body.jsx
    │           │   └── index.js
    │           ├── footer
    │           │   ├── footer.jsx
    │           │   └── index.js
    │           ├── header
    │           │   ├── header.jsx
    │           │   ├── header.scss
    │           │   └── index.js
    │           └── index.js
    ├── enhancers
    │   ├── index.js
    │   └── withTransition
    │       ├── index.js
    │       ├── withTransition.container.js
    │       └── withTransition.jsx
    ├── screen
    │   ├── error
    │   │   ├── 404
    │   │   │   ├── 404.jsx
    │   │   │   ├── 404.scss
    │   │   │   └── index.js
    │   │   ├── 500
    │   │   │   ├── 500.jsx
    │   │   │   ├── 500.scss
    │   │   │   └── index.js
    │   │   ├── error.graphql
    │   │   └── index.js
    │   ├── home
    │   │   ├── home.container.js
    │   │   ├── home.graphql
    │   │   ├── home.jsx
    │   │   ├── home.scss
    │   │   └── index.js
    │   ├── index.js
    │   ├── layout
    │   │   ├── default
    │   │   │   ├── default.graphql
    │   │   │   ├── default.jsx
    │   │   │   ├── default.scss
    │   │   │   └── index.js
    │   │   ├── errors
    │   │   │   ├── errors.jsx
    │   │   │   ├── errors.scss
    │   │   │   └── index.js
    │   │   ├── index.js
    │   │   └── layout.jsx
    │   ├── root
    │   │   ├── index.js
    │   │   ├── root.graphql
    │   │   ├── root.jsx
    │   │   └── routes.js
    │   └── shared
    │       └── breadcrumbs
    │           ├── breadcrumbs.container.js
    │           ├── breadcrumbs.graphql
    │           └── breadcrumbs.jsx
    ├── state
    │   ├── actions
    │   │   ├── food.js
    │   │   ├── index.js
    │   │   └── types.js
    │   ├── reducers
    │   │   └── foo.js
    │   ├── saga
    │   │   ├── index.js
    │   │   └── saga.js
    │   ├── selectors
    │   │   └── getFoo.js
    │   └── store
    │       ├── config.dev.js
    │       ├── config.prod.js
    │       └── index.js
    ├── styles
    │   ├── base.scss
    │   ├── grid.scss
    │   └── variables.scss
    └── utils
        ├── flatten.js
        └── renderClass.js

⬆ back to top


The Modular Approach

The Modular Approach is a software design technique that emphasizes separating the functionality of a software into independent and interchangeable modules, such that each contains everything necessary to execute only one aspect of the desired functionality.

The Modularity can be summarized into three guiding principles:

  • Strong encapsulation: implementation details are hidden inside components, leading to low coupling between different parts. Teams can work in isolation on decoupled parts of the system.
  • Well-defined interfaces: A component can be replaced by any implementation that conforms to the interface specification.
  • Explicit dependencies: having a modular system means distinct components must work together.

⬆ back to top


Basic Hierarchy Principle

Working with medium/large applications usually implies working into a dynamic environment, which require smart and fast solutions for even the simplest changes, like when there's a change in the brand design guide.

Sometimes those changes can break your application and require an extra and even maybe unforeseen effort of your team to fix it, which might not be good for your business, especially working with Agile Environment.

By this reason, we adopted an optional versioning directory as part of the basic hierarchy. This very simple solution provides the power to create a breaking content without harm the application.

In that case, let's assume the following simple scenario:

A certain company updated its design guide, which requires to update the jumbotron component to keep it up to date. Before there was a rounded neutral typography, now switched to a square and colorful one.

The problem is: this update will affect every component that this jumbotron was implemented, since the change was visually significant, it will make the content around it look odd. Because of that, it will require a complete update/refactor of the places it was implemented, until it be able to go to production.

The solution: by simply implementing a versioning directory, we can place all new contents inside this directory. This will give us the power to migrate gradually the screens into the new style guide, and at the same time, those screens can be deployed to production.

Also this is a good fit for A/B tests:

A certain company wants to test which feature leads to a better CvR (Conversion Ratio).

The problem is: creating a new component and append a number in its name is not a good idea. E.g. <ProductGallery2 />.

This simply is meaningless, by which we do not understand its reason for existence by simply looking at it.

The solution: implementing a versioning directory and naming it after its desired reason to exist. E.g. abTestBlackFriday, abTestSummerSales, so on.

This will give us a concise structure and easily understandable. A further clean-up/refactor would be a nice and smooth task to perform.


Summarizing

The Basic Hierarchy Principle can be defined by the following maximum:

Divide and conquer From latim: divide et impera


⬆ back to top


Example

  • With versioning directory:
components                  // category
├── v1                      // versioning
   ├── [modular component]
   ├── [modular component]
   ├── [modular component]
   ├── [modular component]
components                  // category
├── abTestBlackFriday       // versioning
   ├── [modular component]
   ├── [modular component]
   ├── [modular component]
   ├── [modular component]
  • Without versioning directory:
screen                    // category
├── [modular component]
├── [modular component]
├── [modular component]
├── [modular component]
├── [modular component]

Note: We don't consider that the Screen need versioning due to the independency given by the Modular Structure and also for its single use case, which makes an upgrade much easier.

However, you should apply what fits better for your system requirements.


⬆ back to top


Overview

Basic Hierarchy Structure


⬆ back to top


Modular Structure: Designing Modules

The module structure is the most crucial part of the design proposal, it will define how our application will be structured and its behavior from the day one.

The following diagram represents the basic structure of a modular structure.

Modular Structure

Note: the subdomain directory listed above can be implemented as many times as necessary. Although in some cases it might be a better approach to use the Optional file name prefix instead.

See also: Optional file name prefix


⬆ back to top


Example

  • Without subdomain directory:
header             // domain
├── index.js       // file exports / entry point
├── header.jsx     // main implementation
├── query.graphql  // specialization
├── routes.js      // specialization
└── styles.js      // specialization
  • With subdomain directory:
modal                 // domain
├── header            // subdomain
   ├── index.js      // file exports / entry point
   ├── header.jsx    // main implementation
   └── styles.js     // specialization

├── body              // subdomain
   ├── index.js      // file exports / entry point
   ├── body.jsx      // main implementation
   └── styles.js     // specialization

├── footer            // subdomain
   ├── index.js      // file exports / entry point
   ├── footer.jsx    // main implementation
   └── styles.js     // specialization
├── index.js          // file exports / entry point
  • With subdomain suppressed:
dropdown              // domain
├── index.js          // file exports / entry point
├── dropdown.jsx      // main implementation
├── divider.jsx       // main implementation of the suppressed subdomain
├── divider.styles.js // specialization of the suppressed subdomain
└── styles.js         // specialization

⬆ back to top


UI Component: Development Principle

During the development process, we want to make sure that we achieve the maximum level of component universality, that is, to ensure that it can be inserted into any type of context and requires no further implementation to execute its job.

To accomplish this, we need to have the right level of abstraction and zero dependencies.

The right level of abstraction

Abstraction is a useful and powerful tool to solve any kind of problems, since it allows you to mentally isolate an element or property of a whole, to consider it individually.

In that way, we are able to work with a simpler solutions, easier to work with and achieve the maximum level of reusability.

In other words, a right level of abstractions will allow you to separate what is important within a given context. Simplify.

See Abstraction Principle


⬆ back to top


Example

Let's assume you just received a new task to create the the navigation bar from the following UI design:

Navigation Bar UI Design

Component abstraction example - design

After having a good looking at the UI design and understanding its criteria, you feel confident to accomplish this task.

So, most of the develops would create the navigation bar as a single component, developing it based into 2 main structures:

  1. A main wrapper, acting as the main container structure;
  2. A navigation item to be iterated over, containing: 2.1. An icon image; 2.2. A label; 2.3. And a target url.

Although this approach is not wrong and achieve the expected result, it is possible to have a better component reusability approach, which makes your development process faster and your production code smaller in the medium/long term, thus achieving, the same initial performance.

That said, applying the right level of abstraction in this navigation bar, we would realize that we could split it into 2 small reusable components:

  1. An icon component and;
  2. A bar component.

Each of those component will compose the navigation bar main structure and still give us n implementation possibility.

So we would end-up having the following composition structure:

Component abstraction example


Realize that the application of the right level of abstraction, besides providing you the benefits mentioned above, is also a great way to enforce the separation of concerns during the development process.


⬆ back to top


The development process

The component development process is quite straightforward and very pleasant, for that, we use a Style Guide Service in order to ensure zero level of dependency.

Besides the intrinsic benefits of a Style Guide Service, such as having a component playground and documentation, it also provides a brand new development environment to work with, away from all dependencies of your application.

Due to that lean environment, develop a component with no dependencies is quite simple and easy, such way that if you by mistake or even intentionally add a dependency, the Style Guide Service will give throw an error and the component simply will not render.

That said, all you need to do is:

  1. Install and configure a Style Guide Service.
  2. Create your component and the required connection;
  3. Code it;
  4. Check it out in the Style Guide Service.
  5. Return to the step 3 until its done.

Simple, straightforward and good for business.


⬆ back to top


Development flow diagram

Component Development

See Live Style Guide reference.


⬆ back to top


Advantages & Disadvantages

Advantages

  • Simultaneous development: Allow multiple developers to work simultaneously in the Elements.
  • High cohesion: Adventure Design Proposal allow logical grouping of the related structure.
  • Low coupling: The Modular Structure component's can be replaced with alternative implementations that provide the same services.
  • Maintainability: Due to separation of concerns, create, update or delete code/files can be done with the minimum effort and greater confidence, ensuring then the operability, simplicity and evolvability.
  • Maximum Universality for the Components: The UI Component Development Principle provides a road-map to ensure that we achieve the maximum level of reusability in any type of context with zero dependencies.

⬆ back to top

Disadvantages

  • Code Navigability: The navigation can be complex in view of it introduces new layers of abstraction and requires users to adapt to the decomposition criteria of [React] Adventure Design Proposal.
  • Pronounced learning curve: Knowledge on multiple technologies becomes the norm. Developers using [React] Adventure Design Proposal need to be skilled in multiple technologies.
  • Over complexity for simple use case: Usually the simple use cases has a lack of complexity, by which adopting the [React] Adventure Design Proposal would not bring the benefits listed above, but otherwise, increase its complexity and development time with no need.
  • High level of registration: The down side of the Modular Structure is the high level of registration required to connect the modules into a whole. Without a proper organization, it can decrease the code readability.

⬆ back to top


Resources

Documentation Generators

Live Style Guide

JavaScript Style Guide

Learn

Articles

License

MIT License

Copyright (c) 2018 Marcos Gonçalves

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Amendments

We encourage you to fork this design pattern and update the proposal to fit your application requirements.