-
Notifications
You must be signed in to change notification settings - Fork 2
Home
Reflet is a set of tools for building Re-frame + React based web apps with graph and non-graph data models.
At the core of Reflet is a single macro, with-ref
, that generates
references to any kind of thing, and also transparently manages the
lifecycles of those things throughout your application. This leads to
highly extensible components, and excellent APIs.
Besides with-ref
, Reflet also provides:
- Performant multi-model DB with graph queries and mutations
- Data-driven descriptions: a new kind of polymorphic query
- Simple but powerful hierarchical FSMs
- JS and DOM interop utilities
- Novel API-driven visual debugging of complex apps (don't sleep on this!)
And more!
Reflet aims to be a natural progression on top of Re-frame to support complex, data-driven requirements. In that sense, it is both easy to learn, but powerful. You could say it's sort of like Re-frame++ (or Fulcro for Re-frame). Its main design goals are:
- A la carte feature set: it is not a "framework", so use as much or as little of it as you want
- Power up existing Re-frame applications: iterative, minimal approach to integration, so big re-writes can be avoided
- Graph and non-graph data models can be mixed freely with a single source of truth: a Clojure map
- Entity references connect things together: this encourages excellent, pluggable APIs
- Makes very few assumptions about your application boundaries
- Performance and stability: Reflet has already been deployed in complex, data-driven production applications for 4+ years (e.g. in Bioinformatics, Business analytics... )
At minimum include the following in your deps.edn
:
{:deps {io.zalky/reflet {:mvn/version "0.3.0-rc1"}}}
Or project.clj
:
[io.zalky/reflet "0.3.0-rc1"]
Additionally, React is considered a peer dependency, so you will have to ensure that it is available. The same approach you would use to provide React for Reagent or Re-frame will also work for Reflet.
See the additional notes on how to configure the debugger for development.
Reflet builds on top of the concepts and design patterns of Re-frame, and these resources assume a working knowledge of Re-frame basics. If you are not familiar with Re-frame, check out that documentation first, it is very good. Otherwise:
Reflet can be broadly divided into 6 feature sets.
At the core of Reflet is a single macro: with-ref
. If you use
nothing else, this is it. It lets you name things via unique
references, and parameterize components so that they are easily
reusable. Sprinkled throughout your component hierarchy, these
references become the connective tissue that binds all the facets of
your application together:
- Domain app state
- Component state
- Events, queries, and FSMs
- Mutable JS object management
- DOM elements
- Miscellaneous ids for things like focus management
Anywhere you need an id, a name, or a reference to something,
with-ref
is your friend. This is true not just in some esoteric
sense of a "unified approach", but because how the specific behaviour
of with-ref
leads to excellent APIs.
Reflet also introduces a multi-model, reactive graph db. Or rather, it brings a graph data model to your existing Re-frame db (a simple Clojure map) in a way that supports high performance graph queries and mutations. But your graph data can happily co-exist along side non-graph data, not just in the db, but in your event handlers and components. Anywhere in your app, you are free to choose one or the other data model, or a combination of both, depending on your needs. This is especially useful if you already have a complex app that could benefit from a graph data model, but you want a minimalist approach that does not require an extensive re-write.
If you've used Datomic, Datascript, EQL or GraphQL, the semantics of Reflet's graph queries will be familiar. In fact, the Reflet query DSL is a variant of EQL grammar, with union queries replaced by the Reflet's polymorphic descriptions. A differential event sourcing algorithm drives fast, reactive graph queries that only update when necessary. Tens of thousands of query instances can exist at once, such that the browser's ability to render DOM nodes to the window is often a more likely bottleneck.
EQL, GraphQL, and their variants provide a level of query polymorphism through a feature called union queries. Reflet provides a new variant of polymorphic query called descriptions that enhances expressiveness, extensibility, and re-use, especially when the data is deeper and more complex.
Importantly, descriptions are defined outside of the queries in which they will eventually participate. Each description is defined for a particular context, and can reference other contexts in its definition. What's more, everything from the polymorphic type hierarchies to the descriptions themselves can be defined as run-time data, providing a more data-driven experience. This is especially nice if your types and taxonomies reside in a remote ontology, schema, or knowledge representation layer.
This document explains how Re-frame itself can be seen as a machine that transitions through states, though not one that is formally specified as an FSM. Often, Re-frame's transition logic is spread throughout many event handlers, which can make complex behaviours difficult to reason about. Reflet provides a hierarchical FSM DSL, that allows these behaviours to be modelled in a declarative, concise way. Critically, FSMs can be used as inputs to other FSMs to create nested models that are more like Behaviour Trees. This allows you to separate out the logic of your application from the implementation, and in some cases even eliminate the implementation entirely.
Two other places Reflet helps are:
- Interop with mutable Javascript libraries
- Direct DOM access and manipulation
Working with mutable Javascript libraries or accessing the DOM
directly is inevitable. When this breaks the model of the UI as a
materialized, pure function of immutable state, the tried-and-true
inner/outer component
pattern
can help. However, the traditional approach to this pattern can hamper
extensiblity. Reflet's unified with-ref
model streamlines this
pattern and helps bring some of the same API and application design
benefits to the mutable state story.
Finally, Reflet provides a novel visual debugger that is specifically
designed to inspect complex, data driven apps. While debuggers like
re-frame-10x
are indispensable, they provide a global lens into the
behaviour of your application: everything all at once. With a complex
web app, it can sometimes be a challenge to reduce the signal to noise
ratio.
More often you want to drill directly down into a subset of
components, their data, and interdependencies. Since references are
the connective tissue that binds everything together in Reflet,
with-ref
becomes the perfect entry point for exactly this kind of
exploration. The Reflet visual debugger renders an overlay of your
with-ref
API directly onto your components in the browser. You can
then explore your application via a set of four lenses: DB, EVENTS,
QUERY, and FSM. References are everywhere in Reflet, so follow your
nose.
Oh, and of course the debugger was written entirely using Reflet.
The following features have been mapped out, but are currently in the backlog until demand for them materializes:
- Canonicalization of unique references
You can either submit an issue here on Github, or alternatively tag me
(@zalky
) with your question in the #re-frame
channel on the
Clojurians slack.
Special thanks to Inge Solvoll.
Reflet is distributed under the terms of the Apache License 2.0.