Understanding SubwayJS

Content of this section:

Concepts


1. TODO: clarify app design approach - identify domains/aggregates of the system e.g. use [ecommerce react website](https://github.com/subway-js/subway-react-ecommerce) as an example - describe in its README the app design approach

2. TODO: provide context and differences e.g. MVC, SAM, React, Redux, Sagas etc.

SubwayJS is about structuring your application, codebase, logic and teams: it doesn’t make any assumptions on the UI library you are going to use.

The current state of SubwayJS matches the overall idea of having:

All of this happens in each aggregate’s scope, with commands and events that are very specific to each aggregate, and the specific aggregate’s state being updated. But, in order to have a working application, we need a way for aggregates to communicate between each other - they do it by:

Features under investigation:

Micro-frontends


Having highly decoupled aggregates (each of them with its own logic, entities, state and UI), makes it easy to transition to a micro-frontends approach.

SubwayJS provides a micro-frontends orchestration utility (implemented with the same SubwayJS library through aggregates, commands and events) to make it easy to split each aggregate (or domain, or sub-system) into its own project/codebase with its own release pipeline.

Check subway-react-ecommerce-microfrontends for updates.

SubwayJS diagrams


1. Basic SubwayJS application

Simple SubwayJS app diagram

We could imagine a very basic SubwayJS application to be made up by only one aggregate.

Aggregate

Let’s describe the diagram above by starting with the aggregate one.

An aggregate can connect to the message queue in order to:

Both command and event handlers can trigger additional events.

An aggregate can have a state, which can only be updated by an EVENT handler: commands can’t change the aggregate’s state, events can as they describe a change in the system state.

Command handlers can perform RPC calls or whatever is needed before trigger any event, and can also reject a command.

Event handlers can also perform RPC calls to perform their duties.

Every time an event handler changes the aggregate states, the UI that subscribes to that, will be notified with the new state.

There is no way to create a command from inside ANY handler: commands are a result of a UI interaction (with the user or in a callback e.g. after a response or a timer) - it’s like taking an intent of an action and translating it into a command that is relevant in the context of our domain.

2. SubwayJS application design

SubwayJS approach to app development

SubwayJS benefits are related to breaking down our application domain logic into aggregates (or sub-domains).

The vertical grey line that separates the aggregates express the fact that each aggregate should be totally decoupled from the others. Each aggregate should contain:

specific and related to the part of the domain this aggregate is taking care of.

Aggregates should be mapping the project folders structure, so that there is a clear separation of the code.

From aggregate A, which lives inside src/aggregates/a we should NEVER call directly any API on aggregate B (e.g. by doing SubwayJS.selectAggregate('B)…`: this is an anti-pattern in SubwayJS.

We shouldn’t observe aggregate B state from any other aggregate: this is an anti-pattern in SubwayJS.

At no time aggregate A should also rely on domain events from aggregate B as they are considered to be internal to the aggregate (we will talk more about this in the next section).

Take a look at the projects section to see how aggregates are defined in each project

TODO: section to explain SubwayJS approach at splitting domain logic into aggregates

3. Communication between aggregates

Communication between aggregates

If each aggregate is indepentend, how can we implement an application where components need to communicate between each other?

In SubwayJS an aggregate can:

This way we create an API for each aggregate as a way to differenciate:

The above diagram is an example of the ecommerce react website application logic:

4. Microfrontends and shared UI components

SubwayJS microfrontends and shared UI components

This way of designing apps facilitates the transition to microfrontend. In this diagram, each grey box is a codebased, independently deployed (which is, a JS file imported on an HTML page).

There is a ‘master’ app which define which microfrontend goes where on the page, and then all other microfrontends install themselves: once everyone is ready, each microfrontend will be notified, passing the target dom element selector where it should initialize itself.

Aggregates code should be independent and decoupled, isolated in its specific folder, so only minor refactoring is required (e.g. imports etc.)

In the case of the ecommerce react website , the shopping cart aggregate was exporting a dropdown list used in the navigation bar, but now we can’t directly import it as we are in different codebases: SubwayJS provides the ability of exporting UI components so that they can be requested at runtime by other aggregates (check the ecommerce react website as an example).

A microfrontend might contain multiple aggregates, it depends on the needs of each specific application, and not all aggregates might need to be mounted on a specific dom element. Some of them might just publish reusable UI components, others might provide some logic or service (e.g. logging).