Skip to content

Instantly share code, notes, and snippets.

@markmarijnissen
Last active August 29, 2015 14:03
Show Gist options
  • Save markmarijnissen/30df864a6e3cb7650db0 to your computer and use it in GitHub Desktop.
Save markmarijnissen/30df864a6e3cb7650db0 to your computer and use it in GitHub Desktop.
Famo.us App Structure

Given the following goals:

  1. Famo.us should be framework agnostic and integrate with Firebase, AngularJS, Meteor and more.
  2. Famo.us is a rendering engine, so it should be easy to share and reuse custom rendering nodes in different context (app, web, with different frameworks).
  3. Famo.us components should be loosely coupled and have no external dependencies. Ideally, it can be contained in a single file.
  4. Code should scale well to big projects, for example by structering the app into small, testable self-containted modules.
  5. Apps should be easy to reskin, remix and reconfigure.

....and given the existing design decisions:

  • Famo.us is structured as an hierarchical rendering tree.
  • Famo.us has extensive support for an event-based structure.
  • Famo.us is organized in modules (RequireJS).

.. I suggest the following "types" of modules

1) Content modules: Apps-specific content - images, text, forms, etc.

2) Layout modules: Generic UI-components such as ScrollView, Popup, Sidepanel, etc (i.e. your "common app pattern" package).

3) Service modules: Every module not concerned with view and rendering, i.e. modules to encapsulate LocalStorage, a Rest API, Firebase, Audio API, Backbone, etc etc

4) Mediator modules: Event-based glue between services and layout/content.

1) Seperate Layout from Content

  • The AppView from Timbre is a counter-example: it mixes a generic UI component (a sidepanel menu slider) with app specific views (MenuView, PageView, animating stripes).
  • Better create a generic SidepanelLayout, and populate with content.

2) Modules are self-contained and loosely coupled.

  • Modules emit events
  • Modules have a public API or respond to events
  • The semantics should be in the domain of the module: i.e. a Todo-Item-View might have a method "updateContent", but not a method "onRestApiDataReceived". It's up to the "Mediator" to translate "RestApiDataReceived" to an "updateContent"

3) Content & Layout modules may have hierarchical dependencies.

  • A parent may initialize its children.
  • A parent might expect certain events from its children.

4) Service modules use an Adapter or Facade pattern.

  • The adapter simplifies and abstract a more low-level api, and can optionally add security. For example, a DataService might implement roles and permissions and abstract from a RestApiService.

5) Mediators "couple" the app by listening to events and calling public APIs.

  • A mediator adds virtually no logic and is as thin as possible.
  • A mediator listens to the event of one module, and calls the public api of another module.
  • Mediators are app-specific, throw-away code.

6) Mediators expect lifecycle events from modules

i.e. imagine a Todo-list app with multiple Todo items, which all need to be updated with the latest data from a Firebase service module.

A todo item might announce its existence as follows:

Engine.emit('created',this);

Using the Engine singleton to emit "created" and "destroyed" events is very useful. It provides an single entry-point for mediators to start coupling, and it makes sure that Content, Layout and Services have absolutely NO external dependencies and NO knowledge WHATSOEVER about other APIs.

7) A mediator is allowed to have many many dependencies

8) Mediators are singletons

9) Multiple mediators are allowed

i.e. a DataMediator to couple a RestAPI with Content modules, a AudioMediator to play audio when the user presses a button, etc, etc.

  • mediators are allowed to be "one-size-fits-all": i.e. it can respond to both a RestAPI service and a Firebase Service. Depending on which module is instantiated in the app, it updates content using the public API of content modules.

Folder layout:

/content
/layout
/services
/mediators
/config            (contains all configuration)
theme.css
main.js

A layout-module might consist of javascript and a single CSS. The CSS should contain only structure and minimal theming, so app theming (which is content!) can be done in a seperate *.css.

All services and modules can be re-used in other projects, whereas all mediators, content and config are app-specific.

bootstrapping in main.js

  1. instantiate mediators (so they can catch the "created" events from the modules)
  2. instantiate services
  3. construct rendering tree
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment