This module encapsulates the application business logic, and interacts with the Networking and Storage layers.
Business logic is broken down into different subclasses of Store. A Store encapsulates the business logic related to one and only one of the domain level concerns (i.e. Notifications, Orders, Shipment…).
Some of these concerns require a valid user session (a user to be logged into the system) while some others don’t require a session. To handle that, Stores are aggregated by a StoresManager.
StoresManager is a state machine, that manages two states. Those states are implementations of the StoresManagerState protocol. Currently, we have two implementations, AuthenticatedState and DeauthenticatedState.
Each of the implementations of the StoresManagerState aggregates a collection of subclasses of Store protocol. That’s how we enforce certain concerns (i.e. Orders) to require users to be authenticated.
StoresManager takes care of the transition from the authenticated (AuthenticatedState) to the deauthenticated (DeauthenticatedState) state, while executing the necessary side effects of those transitions (for example, clearing the user data locally stored)
StoresManager is a singleton.
ℹ️ The AuthenticatedState aggregates a reference to Dispatcher. This is an important piece of the system, as it is responsible for executing the actions declared in the different Stores. Therefore we will discuss it in depth later on.
As mentioned in the previous section, the business logic pertaining each domain level concern is encapsulated in a separate subclass of Store.
Store is an implementation of the ActionsProcessor protocol. We will discuss this protocol later, but for now, let’s say it is something that can process an action.
Store is injected with a reference to a Dispatcher, an implementation of the StorageManagerType protocol (the storage stack) and an implementation of the Network protocol (the networking stack).
As a quick aside, please note this pattern: dependencies are usually injected.
Store is a lightweight implementation of the Template Method Pattern. It enforces its subclasses to implement a method (called registerSupportedActions) that will register a collection of supported actions with the Dispatcher and to implement a method that deals with incoming actions. We will discuss what actions are and how they are modelled a bit later.
Each subclass of Store registers a processor with the Dispatcher. To register a processor, a subclass of Store overrides registerSupportedActions. In the implementation of that method, it will register itself as processor, for a given set of actions.
That set of actions is declared in an implementation of the Action protocol. We will look at Action in details in the next section, but for now, it is important to note that there is a one to one relationship between each subclass of Store and a corresponding implementation of the Action protocol. (i.e. for AccountStore we would also have a AccountAction).
At the moment, we provide the following subclasses of Store:
AccountStore. RegistersAccountActionwith theDispatcher. It implements the business logic necessary to manage an account (load an account, load a site, synchronise account information…)AppSettingsStore. Registers and responds to actions declared inAppSettingsAction. It implements the logic to save and retrieve application settings.CommentStore. Registers and responds to actions declared inCommentActionIt implements the business logic pertaining comments.CardPresentPaymentsStore. Registers and responds to actions declared inCardPresentPaymentsAction. It implements the logic to collect payments with external card readers.CustomerStore. Implements support for operations related to Customers, like searching and retrieving customer details.NotificationStore. Registers and responds toNotificationActionOrderStore. RegistersOrderActionwith theDispatcherand responds to the actions declared in it.OrderNoteStore. Business logic pertaining order notes. Registers and responds to operation declared inOrderNoteActionOrderStatusStore. Business logic related to order payment statuses, implementing operations declared inOrderStatusActionReceiptStoreregisters and respond to operations declared inReceiptAction, providing support for storing and printing card present payment receipts.SettingStoreregisters and responds to operations declared inSettingActionStatsStoreresponds to operations declared inStatsAction, providing support for the order stats.StatsStoreV4responds to operations declared inStatsActionV4, providing support for the order stats based on the V4 API, yet to be integrated in the UI.ShipmentStoreimplements support for the operations declared inShipmentActionTaxClassesStoreimplements support for the operations declared inTaxClassesActionStatStoreimplements the logic to present statistics, supporting operations declared inStatsAction
As mentioned in the precious section, there is an implementation of the Action protocol per domain level concern.
The Action protocol is a tag interface.
An implementation of Action declares the facade to the business logic that corresponds to the domain level concern it represents.
To illustrate that with an example, let’s think of statistics. WooCommerce presents different types of statistics (related to an order, to the number of visitors to the store, of the top earning product, to name a few).
Each of those types of statistics will be declared by a different method in the implementation of Action called StatsAction.
All current implementations of the Action protocol are enumerations.
Actions work in close cooperation with Stores. An Action declares a number of operations that the Store will implement.
To continue with the example of the statistics, let’s say we want to present the statistics related to the top earning products.
The sequence for the UI layer to trigger a request to fetch data would be:
- Initialise the operation that we want to perform. In this case that would mean getting an
Action(the return value of aStatsAction.retrieveTopEarnerStats,) passing it a completion block. - Dispatch that operation to the
StoresManager - The
StoresManagerwill find the currently activeStoresManagerState, forward that operation to theDispatcheraggregated by it, which in turn will forward it to whateverStoreregistered as capable of handling it (in our example,StatsStore)
The full listing:
func syncTopPerformers(onCompletion: ((Result<Void, Error>) -> Void)? = nil) {
let action = StatsAction.retrieveTopEarnerStats(siteID: siteID,
granularity: granularity,
latestDateToInclude: Date()) { [weak self] result in
// Error handling removed for brevity
onCompletion?(result)
}
ServiceLocator.stores.dispatch(action)
}Why did we implement it that way?
We wanted the design to be highly cohesive and loosely coupled. We also wanted the design to be able to be capable of supporting adding new domain level concerns, without having to rewrite the pre-existing code.
The system, is highly cohesive. Each module has a distinct, clearly defined responsibility. StatsStore implements business logic for statistics, and NotificationsStore for notifications. There are no mixed responsibilities.
Individual modules don’t know anything about the implementation of other modules. Modules collaborate without knowing too much about each other.
When designing the system, we wanted to be able to add new domain level concerns (i.e. shipment tracking or custom order statuses) without having to rewrite any significant part of the system.
Adding new domain level concerns is a matter of adding the Action declaring the facade of the operations that are required to fulfil that concern, and the Store that implements those operations (and related networking and storage, which will be reviewed in a separate document). The rest of the system remains unchanged.

