Reference Architecture

To anyone interested in architectural solutions for Meteor/DDP/Blaze, I’ve developed some packages which may be of use. The documentation is on the sparse side but the code and example app should suffice to demonstrate. The goal is not to promote the use of these packages but rather to provide a different perspective and some concrete reference solutions.

Three packages are provided:

  1. Core Components (https://atmospherejs.com/dfnt/meteor-composite-core). Provides a set of “use-at-will” components. While these may be used selectively, more advanced features are exposed when using the full-set, or certain sub-sets together.

  2. App Instance and Helpers meteor-composite-app. Exposes an app instance (named MeteorApp), extension methods and template helpers for working with the components in meteor-composite-core. The MeteorApp global export is provided as a convenience mechanism to promote experimentation and to assist in debugging.

  3. Bootstrap Controls meteor-composite-app-bootstrap. Contains controls that work with the app instance, extension methods and helpers provided by meteor-composite-app.

An example app is available here https://github.com/definite-software/meteor-composite-todos. This also serves to demonstrate how to integrate a router and the Meteor Accounts system. You’ll either need to setup the MAIL_URL environment or bypass the registration system and manually create an account.

In the interest of keeping it brief, I’ve omitted many pertinent details. In case anyone has an interest in or use for this type of work, I’m open to any questions, comments or feedback.

Note: the system maxes out at 2 links per post for new users and so I was unable to link to all of the packages. Instead, find these on the atmosphere site.

1 Like

Nice, I’m taking a look, thanks for sharing @mattl!

Thanks @alawi. Here’s some additional info in case yourself or anyone else is interested:

To summarize a few of the primary problems that I’ve faced and the solutions that I’ve arrived at…

Ephemeral v Long-Lived State. The usefulness of ephemeral/template-level state is limited. At the same time, representing all long-lived state using a single object, e.g. Session, is cumbersome and difficult to manage. Session is just a ReactiveDict. An app can create as many ReactiveDicts as it needs to faithfully represent and isolate its’ long-lived state. Easy enough except that some means of organizing and storing the various “state stores” is needed - which is where larger structures come into play.

Additionally, I wanted a consistent means of wiring these up via config data and various other “enhancements”, and so I created a thin wrapper around ReactiveDict - see createStateStore. Enhancements include:

  • simple type checking
  • input transformation
  • default/initial state
  • reset methods
  • dirty checking methods

Static v Dynamic Resource Access. The import/export system in Meteor is indispensable but, at some point, explicit imports become difficult to manage. For instance, apps representing tens of domain models each having multiple variations of search, form, and other views. Instead, I wanted to be able to access resources* dynamically/reactively, e.g. SomeApp.getModule('someModule').getView('someView').doSomething(), and to not be restricted by a template or any other construct in accessing those resources.

The solution I arrived at is just a simple, in-memory key/value registry component - see createRegistry. Various registries are used throughout the system. For example, each instance of the createApp component has its’ own module and shell registries. Registering a resource is just a matter of calling a method, e.g. SomeApp.addModule('moduleId', moduleObject). Any object can be registered but the intent is to store sub-component instances in some structured manner so that those resources, and their methods, can be accessed later.

Intrinsic v Extrinsic Routing. I wanted a system that would work independent of, or even without, routing. In my view, while the URL is unlikely to die anytime soon, routing shouldn’t be a de-facto requirement. The meteor-composite-* packages have no concept of routing. Instead, any routing is implemented at the app-level. The example app demonstrates a very minimalistic solution using msavin:parrot.

Page v Nested Region Model. I wanted the ability to compose very complex UIs. For instance, displaying many search views (with complex filtering and pagination) on the same screen. I realize that there are existing “layout” solutions that support the notion of “regions” but I didn’t want to run into any walls and so developed my own solution. I was able to develop a flexible, powerful solution in a little over 100 lines. The example app demonstrates the power and flexibility of these simple solutions. Additionally, since template helpers, UI controls, etc are “region aware” large swaths of functionality can be reused across regions. Take a look at the “tools” regions in the example app and the associated “search_tools” template.

Additionally, these solutions demonstrate how to:

  • handle complex publication scenarios
  • take control of complex ready and loading state
  • pub/sub relational data efficiently and atomically
  • implement customizable search and pagination over large collections
  • share state and behavior across components, e.g. two or more search views that share filter state or even sub/query component instances
  • create reusable, higher-order components, generic components (such as createSearchView) but also Blaze components and global template helpers
  • use unmanaged collections on the client to create advanced features such as notifying users when a record changes or when new search results are available
1 Like

It’s interesting how you’ve a fresh take on those problems, I’m using React these days and I’m happy with how it’s scaling. But I remember running into some of those issues when I was trying to scale the view layer of a blaze app.

Thanks again. I appreciate the kind comment. I’ve spent a little time with React and GraphQL/Apollo but need to dig in deeper. I’m curious to know whether the same level of complexity can be replicated and, if so, how the solutions compare (as far as the technologies can be compared anyhow).