Skip to content

Latest commit

 

History

History
53 lines (27 loc) · 5.32 KB

DESIGN.md

File metadata and controls

53 lines (27 loc) · 5.32 KB

Project Design

The design for Synectic is based on bubbling up principles used in the underlying ARCHITECTURE projects that we built upon, and including them within our own design and development processes. In particular, we maintain the following design principles:

Unidirectional Data Flow

Data has one, and only one, way to be transferred to other parts of the application.

This means that all data in Synectic follows the same lifecycle pattern, making the logic more predictable and easier to understand. A UI view is a result of the application state. State can only change when actions happen. And when actions happen, the state is updated. This principle encourages data normalization, so that we don't end up with multiple, independent copies of the same data that are unaware of one another (see Single source of truth, State is read-only, and Changes are made with pure functions).

Within our React component tree, this principles means that state is always owned by one component. Any data that is affected by this state can only affect components below it (i.e. it's children). Changing state on a component will never affect its parent, or its siblings, or any other component in the application; just its children. For this reason, a lot of the state is moved up in the component tree so that it can be shared between components that need access to it.

Derived from Redux's Data Flow.

Separate Presentational and Container Components

Presentational components maintain how things look, whereas Container components maintain how things work.

React bindings for Redux separate presentational components from container components. Presentational components are concerned with the visual presentation of data (e.g. markup, style, UI layout) and are unaware of Redux, which translates to all data being read through React component props and changes to data require invoking a props callback. Container components are concerned with the managing the state of data (e.g. data fetching, state updates) and are aware of Redux, which translates to all data being read by subscribing to Redux state and changes to data being propagated by dispatching Redux actions. This approach makes Synectic easier to understand and allow for more easily reusing components (see Composite reuse).

Derived from Redux's Usage with React Tutorial.

Composite Reuse

Components should be composed of reusable functionality, instead of inheriting predefined attributes.

Composition over inheritance (or composite reuse principle) in object-oriented programming (OOP) is the principle that classes should achieve polymorphic behavior and code reuse by their composition (by containing instances of other classes that implement the desired functionality) rather than inheritance from a base or parent class.

Derived from React's Composition vs Inheritance.

Single source of truth

The state of the whole application is stored in an object tree within a single store.

This makes it easy to create universal apps, as the state from the underlying server can be serialized and hydrated into the client with no extra coding effort. A single state tree also makes it easier to debug or inspect an application; it also enables you to persist your app's state in development, for a faster development cycle. Some functionality which has been traditionally difficult to implement - Undo/Redo, for example - can suddenly become trivial to implement, if all of your state is stored in a single tree.

Derived from Redux's Three Principles.

State is read-only

The only way to change the state is to emit an action, an object describing what happened.

This ensures that neither the views nor the network callbacks will ever write directly to the state. Instead, they express an intent to transform the state. Because all changes are centralized and happen one by one in a strict order, there are no subtle race conditions to watch out for. As actions are just plain objects, they can be logged, serialized, stored, and later replayed for debugging or testing purposes.

Derived from Redux's Three Principles.

Changes are made with pure functions

To specify how the state tree is transformed by actions, you write pure reducers.

Reducers are just pure functions that take the previous state and an action, and return the next state. Remember to return new state objects, instead of mutating the previous state. You can start with a single reducer, and as your app grows, split it off into smaller reducers that manage specific parts of the state tree. Because reducers are just functions, you can control the order in which they are called, pass additional data, or even make reusable reducers for common tasks such as pagination.

Derived from Redux's Three Principles.