|
| 1 | +Architectural Patterns |
| 2 | +====================== |
| 3 | + |
| 4 | +Over the `past 5 years <NPM-trends>`__ front-end developers seem to have concluded that |
| 5 | +programs written with a declarative_ style or framework tend to be easier to understand |
| 6 | +and maintain than those done imperatively. Put more simply, mutable state in programs |
| 7 | +can quickly lead to unsustainable complexity. This trend is largely evidenced by the |
| 8 | +`rise <Frontend-Frameworks-Popularity>`_ of Javascript frameworks like Vue_ and React_ |
| 9 | +which describe the logic of computations without explicitly stating their control flow. |
| 10 | + |
| 11 | +.. _React: https://reactjs.org |
| 12 | +.. _NPM-trends: https://www.npmtrends.com/react-vs-angular-vs-vue |
| 13 | +.. _Vue: https://vuejs.org |
| 14 | +.. _Declarative: https://www.youtube.com/watch?v=yGh0bjzj4IQ |
| 15 | +.. _Frontend-Frameworks-Popularity: https://gist.github.com/tkrotoff/b1caa4c3a185629299ec234d2314e190 |
| 16 | + |
| 17 | +.. image:: _static/npm-download-trends.png |
| 18 | + |
| 19 | +So what does this have to do with Python and IDOM? Well, because browsers are the de |
| 20 | +facto "operating system of the internet", even back-end languages like Python have had |
| 21 | +to figure out clever ways to integrate with them. While standard REST_ APIs are well |
| 22 | +suited to applications built using HTML templates, modern browser users expect a higher |
| 23 | +degree of interactivity than this alone can achieve. |
| 24 | + |
| 25 | +.. _REST: https://en.wikipedia.org/wiki/Representational_state_transfer |
| 26 | + |
| 27 | +A variety of Python packages have since been created to help solve this problem: |
| 28 | + |
| 29 | +- IPyWidgets_ - Adds interactive widgets to `Jupyter Notebooks`_ |
| 30 | +- Dash_ - Allows data scientists to produces enterprise-ready analytic apps |
| 31 | +- Streamlit_ - Turns simple Python scripts into interactive dashboards |
| 32 | +- Bokeh_ - An interactive visualization library for modern web browsers |
| 33 | + |
| 34 | +.. _IPyWidgets: https://github.com/jupyter-widgets/ipywidgets |
| 35 | +.. _Jupyter Notebooks: https://jupyter.org/ |
| 36 | +.. _Dash: https://plotly.com/dash/ |
| 37 | +.. _Streamlit: https://www.streamlit.io/ |
| 38 | +.. _Bokeh: https://docs.bokeh.org/ |
| 39 | + |
| 40 | +However they each have drawbacks that can make them difficult to use. |
| 41 | + |
| 42 | +3. **Restrictive ecosystems** - UI components developed for one framework cannot be |
| 43 | + easily ported to any of the others because their APIs are either too complex, |
| 44 | + undocumented, or are structurally inaccesible. |
| 45 | + |
| 46 | +1. **Imperative paradigm** - IPyWidgets and Bokeh have not embraced the same declarative |
| 47 | + design principles pioneered by front-end developers. Streamlit and Dash on the |
| 48 | + otherhand, are declarative, but fall short of the features provided by React or Vue. |
| 49 | + |
| 50 | +1. **Limited layouts** - At their initial inception, the developers of these libraries |
| 51 | + were driven by the visualization needs of data scientists so the ability to create |
| 52 | + complex UI layouts may not have been a primary engineering goal. |
| 53 | + |
| 54 | +As a result, IDOM was developed to help solve these problems. |
| 55 | + |
| 56 | + |
| 57 | +Ecosystem Independence |
| 58 | +---------------------- |
| 59 | + |
| 60 | +IDOM has a flexible set of :ref:`core abstractions <Core Concepts>` that allow it to |
| 61 | +interface with its peers. At the time of writing Jupyter, Dash, and Bokeh (via Panel) |
| 62 | +are supported, while Streamlit is in the works: |
| 63 | + |
| 64 | +- idom-jupyter_ (try it now with Binder_) |
| 65 | +- idom-dash_ |
| 66 | +- `IDOM in Panel`_ |
| 67 | + |
| 68 | +.. _Panel: https://panel.holoviz.org/Comparisons.html#comparing-panel-and-bokeh |
| 69 | +.. _idom-jupyter: https://github.com/idom-team/idom-jupyter |
| 70 | +.. _Binder: https://mybinder.org/v2/gh/idom-team/idom-jupyter/main?filepath=notebooks%2Fintroduction.ipynb |
| 71 | +.. _idom-dash: https://github.com/idom-team/idom-dash |
| 72 | +.. _IDOM in Panel: https://panel.holoviz.org/reference/panes/IDOM.html#panes-gallery-idom |
| 73 | + |
| 74 | +By providing well defined interfaces and straighforward protocols, IDOM makes it easy to |
| 75 | +swap out any part of the stack with an alternate implementation if you want to. For |
| 76 | +example, if you need a different web server for your application, IDOM already has |
| 77 | +several options to choose from or, use as blueprints to create your own: |
| 78 | + |
| 79 | +- :ref:`Sanic <Sanic Servers>` |
| 80 | +- :ref:`FastAPI <FastAPI Servers>` |
| 81 | +- :ref:`Tornado <Tornado Servers>` |
| 82 | +- :ref:`Flask <Flask Servers>` |
| 83 | + |
| 84 | +You can even target your usage of IDOM in your production-grade applications with IDOM's |
| 85 | +Javascript `React client library <idom-client-react>`_. Just install it in your |
| 86 | +front-end app and connect to a back-end websocket that's serving up IDOM models. This |
| 87 | +documentation acts as a prime example for this targeted usage - most of the page is |
| 88 | +static HTML, but embedded in it are :ref:`interactive examples <examples>` that feature |
| 89 | +live views being served from a web socket: |
| 90 | + |
| 91 | +.. _idom-client-react: https://github.com/idom-team/idom/tree/main/src/idom/client/packages/idom-client-react |
| 92 | + |
| 93 | +.. image:: _static/live-examples-in-docs.gif |
| 94 | + |
| 95 | + |
| 96 | +Declarative Components |
| 97 | +---------------------- |
| 98 | + |
| 99 | +IDOM, by adopting the :ref:`Hook <Life Cycle Hooks>` design pattern from React_, |
| 100 | +inherits many of its aesthetic and functional characteristics. For those unfamiliar with |
| 101 | +hooks, user interfaces are composed of basic HTML elements that are constructed and |
| 102 | +returned by special functions called "components". Then, through the magic of hooks, |
| 103 | +those component functions can be made to have state. Consider the component below which |
| 104 | +displays a basic representation of an AND-gate: |
| 105 | + |
| 106 | +.. example:: simple_and_gate |
| 107 | + |
| 108 | +Note that the code never explicitely describes how to evolve the frontend view when |
| 109 | +events occur. Instead, it declares that, given a particular state, this is how the view |
| 110 | +should look. It's then IDOM's responsibility to figure out how to bring that declaration |
| 111 | +into being. This behavior of defining outcomes without stating the means by which to |
| 112 | +achieve them is what makes components in IDOM and React "declarative". For comparison, a |
| 113 | +hypothetical, and a more imperative approach to defining the same interface might look |
| 114 | +similar to the following: |
| 115 | + |
| 116 | +.. code-block:: |
| 117 | +
|
| 118 | + layout = Layout() |
| 119 | +
|
| 120 | +
|
| 121 | + def make_and_gate(): |
| 122 | + state = {"input_1": False, "input_2": False} |
| 123 | + output_text = html.pre() |
| 124 | + update_output_text(output_text, state) |
| 125 | +
|
| 126 | + def toggle_input(index): |
| 127 | + state[f"input_{index}"] = not state[f"input_{index}"] |
| 128 | + update_output_text(output_text, state) |
| 129 | +
|
| 130 | + return html.div( |
| 131 | + html.input({"type": "checkbox", "onClick": lambda event: toggle_input(1)}), |
| 132 | + html.input({"type": "checkbox", "onClick": lambda event: toggle_input(2)}), |
| 133 | + output_text, |
| 134 | + ) |
| 135 | +
|
| 136 | +
|
| 137 | + def update_output_text(text, state): |
| 138 | + text.update( |
| 139 | + children="{input_1} AND {input_2} = {output}".format( |
| 140 | + input_1=state["input_1"], |
| 141 | + input_2=state["input_2"], |
| 142 | + output=state["input_1"] and state["input_2"], |
| 143 | + ) |
| 144 | + ) |
| 145 | +
|
| 146 | +
|
| 147 | + layout.add_element(make_and_gate()) |
| 148 | + layout.run() |
| 149 | +
|
| 150 | +In this imperative incarnation there are several disadvantages: |
| 151 | + |
| 152 | +1. **Refactoring is difficult** - Functions are much more specialized to their |
| 153 | + particular usages in ``make_and_gate`` and thus cannot be easily generalized. By |
| 154 | + comparison, ``use_toggle`` from the declarative implementation could be applicable to |
| 155 | + any scenario where boolean indicators are toggled on and off. |
| 156 | + |
| 157 | +2. **No clear static relations** - There is no one section of code through which to |
| 158 | + discern the basic structure and behaviors of the view. This issue is exemplified by |
| 159 | + the fact that we must call ``update_output_text`` from two different locations. Once |
| 160 | + in the body of ``make_and_gate`` and again in the body of the callback |
| 161 | + ``toggle_input``. This means that, to understand what the ``output_text`` might |
| 162 | + contain, we must also understand all the business logic that surrounds it. |
| 163 | + |
| 164 | +3. **Referential links cause complexity** - To evolve the view, various callbacks must |
| 165 | + hold references to all the elements that they will update. At the outset this makes |
| 166 | + writing programs difficult since elements must be passed up and down the call stack |
| 167 | + wherever they are needed. Considered further though, it also means that a function |
| 168 | + layers down in the call stack can accidentally or intentionally impact the behavior |
| 169 | + of ostensibly unrelated parts of the program. |
| 170 | + |
| 171 | + |
| 172 | +Communication Scheme |
| 173 | +-------------------- |
| 174 | + |
| 175 | +To communicate between its back-end Python server and Javascript client, IDOM uses |
| 176 | +something called a Virtual Document Object Model (:ref:`VDOM <VDOM Mimetype>`) to |
| 177 | +construct a representation of the view. The VDOM is constructed on the Python side by |
| 178 | +components. Then, as it evolves, IDOM's layout computes VDOM-diffs and wires them to its |
| 179 | +Javascript client where it is ultimately displayed: |
| 180 | + |
| 181 | +.. image:: _static/idom-flow-diagram.svg |
| 182 | + |
| 183 | +By contrast, IDOM's peers take an approach that aligns fairly closely with the |
| 184 | +Model-View-Controller_ design pattern - the controller lives server-side (though not |
| 185 | +always), the model is what's synchronized between the server and client, and the view is |
| 186 | +run client-side in Javascript. To draw it out might look something like this: |
| 187 | + |
| 188 | +.. image:: _static/mvc-flow-diagram.svg |
| 189 | + |
| 190 | +.. _Model-View-Controller: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller |
| 191 | + |
| 192 | + |
| 193 | +Javascript Integration |
| 194 | +---------------------- |
| 195 | + |
| 196 | +If you're thinking critically about IDOM's use of a virtual DOM, you may have thought... |
| 197 | + |
| 198 | + Isn't wiring a virtual representation of the view to the client, even if its diffed, |
| 199 | + expensive? |
| 200 | + |
| 201 | +And yes, while the performance of IDOM is sufficient for most use cases, there are |
| 202 | +inevitably scenarios where this could be an issue. Thankfully though, just like its |
| 203 | +peers, IDOM makes it possible to seemlesly integrate :ref:`Custom Javascript Components`. |
| 204 | +They can be custom built for your use case, or you can just leverage the existing |
| 205 | +Javascript ecosystem without any extra work: |
| 206 | + |
| 207 | +.. example:: material_ui_slider |
0 commit comments