Organization of Ember apps' state machines


This is a cross post from Ember Slack channel #topic-architecture.

Over the past several years I have settled down to four kinds of state manager patterns when it comes to Ember and Ember components.

  1. Models/Objects - Your base state machine. This is your ember-data model or basic EmberObject. Its responsibility is to manage the state of data and can offer some computed properties and methods specific to the business logic of that data. For example you might have a set of properties that are flags for each state a model is in. The state property is a string from the API and each flag property is a computed that is derived from the value of state. Notice that the responsibility is for values that are intended to drive app behavior and not presentational concerns (like how to convert a state code into an I18N label to be rendered; see #3 and #4 below).
  2. Black box components - What has been describes as container components where the responsibility is to take a model (or object, or multiple objects) and me responsible for the management of that object. It delegates data and actions to other components (presentational components #3). It is a black box of sorts in that from the outside it does two-way binding on the objects’ states. Examples might include a form or a composable component with nothing but a <div> wrapper.
  3. Presentational components - are what you will likely see in most Ember tutorials. Their responsibility is to render a value to the user and manage prorogation of user actions. This is your classic Data Down Actions Up situation. You give it a value but the component does not own that value it only sets the state of the component. User actions are sent as actions and again don’t change the state itself but relies on others to change the value and then it reacts/renders. Examples of this would include form inputs, headers, tables, etc.
  4. Decorators - There are some cases where you need to compute or store presentational state but that state is specific to a particular model/object instance and it is not feasible to have a dedicated Presentational component for it. In these more rare situations I use a Decorator pattern which is proxy that wraps the instance giving it new properties/behavior that is specific to presentation but is not so specific to warrant a component for it. Examples may include ArrayProxy with an isAllSelected flag, I18N representations of common fields shared across components, timezone conversions getters/setters, UI validations, dynamic select-box labels, ember-changeset, etc.

Controllers, Routes, and Services could almost fall under #2

I wrote a blog post about #4 (Decorators)