9  Architecture Thinking

While most of this book is dedicated to how things technically work, one key part of mastering Shiny communication between modules relies on architecture thinking.

9.1 Global communication schema

As we saw previously, communication between the UI and the server(s) and communication between servers are different streams used for different purposes. A good method to make things clear when you start a project is to draft a global schema and mentally check how the information flow will travel from one component to another.

Global architecture schema

This way, you can evaluate the best configuration whether maybe some tasks should be delegated to a sub / nested module or not. I would recommend here to do a review of the features / tasks you want to implement in your application, where they should be handled, on which information / data they rely, and where those data should come from.
When your schema validates the full review, then you have a strong basis to start coding.

Remember that the objective is not only to make things work but also to build a smart design pattern that can support evolutions and new features. I believe this point is particularly critical when it comes to Agile development where projects usually start with a few features and will grow as we iterate.

9.2 Local communication schema

In addition to the global architecture schema, I would recommend to spend some time at the module level (local) to specify how each component will:

  • receive information from the UI using Inputs

  • send information to the UI using outputs

  • receive information / data through the server function parameters

  • send information / data through the server function return value

Local architecture schema

This is particularly important for modules handling complex tasks or those that will share their outputs (in the sense of data / information) across different modules or places in the UI.

9.3 Data driven modules vs feature driven modules

There is an additional topic that I believe is critical when it comes to dealing with bigger / more complex applications.

I have realized over time that I pretty much always intuitively think of a module as a data driven module.
By data driven module, I mean that the purpose of a specific module is to handle a specific type of data: maybe do the read / write operations and provide methods contextual to these data (filter, select, etc…).

But as my apps were growing and getting more complex – so involving more data, features but also information flows – I started to face communication challenges between the different modules mostly because they handled both say the data management part and the feature / task as seen from the UI (hence from the user).

The main challenge here is that coding the data / information flow and coding the user interactions handling are two different things that need to be linked to each other.

This is when feature driven modules come in action.
Shiny applications very often rely on tab-based components that group similar tasks a user would like to perform and having a dedicated module to handle a specific group of tasks can avoid mixing both the data and user streams.

In this architecture pattern, data driven modules would handle all the data management tasks (and this is the reason why I work on the {kitems} package) while feature driven modules would consume these data to answer the user interactions.

Data and feature driven modules

I believe this approach can help make things easier to understand as the data driven module will concentrate on the pure back-end tasks (typically create / update / delete & read / write operations) while the feature driven modules will focus on transforming those data into actionable insights (tables, plots, buttons) for the user to interact with the application.

Then both steams have dedicated modules and tasks have a clear location where to be performed.