HarmonyOS App Architecture Upgrade: Refactoring from Page-Driven to System-Driven with a Four-Layer Model

This article outlines a practical architecture upgrade path for HarmonyOS application development: moving from a page-driven model to a System-driven model. The core idea is to separate state, business rules, and workflow orchestration to reduce page coupling and improve reuse and testability. Keywords: HarmonyOS, System-driven architecture, state management.

Technical Specification Snapshot

Parameter Description
Language ArkTS / TypeScript-style examples
UI Framework ArkUI
Architecture Model Store / System / Engine / UI
License Not provided in the source; content is assumed to follow a CC 4.0 BY-SA reposting context
Stars Not provided in the source
Core Dependencies Global Store, domain Systems, workflow Engine

Illustration AI Visual Insight: This image appears to be the article’s cover visual. It emphasizes the theme of “HarmonyOS App Architecture Upgrade,” with a visual focus on moving from page-centric logic to system-level abstraction. It works well as an architectural evolution guide image rather than a code implementation diagram.

Illustration AI Visual Insight: This animated image looks like a supplemental illustration for architecture switching or dynamic UI behavior. It conveys layered changes during structural migration and helps explain the transition from piling logic into a single page to coordinating across layered components.

Traditional page-driven architecture breaks down under complex business requirements

In the early stages of many ArkUI/ArkTS projects, teams often default to the assumption that “a page is the functional unit.” As a result, requests, state, interaction checks, and business workflows all get pushed into the Page. This feels fast at first, but complexity grows exponentially.

Three symptoms are especially common: page files become too large, logic gets copied across pages, and state synchronization becomes chaotic. The problem is not HarmonyOS itself. The issue is that the page takes on a mixed set of responsibilities from the View, Controller, Service, and Store layers.

// HomePage.ets
@State userInfo: UserInfo | null = null

onAppear() {
  this.loadUser() // Fetch data directly in the page lifecycle
}

loadUser() {
  api.getUser().then(res => {
    this.userInfo = res // The page directly holds and mutates business state
  })
}

handleClick() {
  if (this.userInfo?.vip) {
    this.doSomething() // The page directly performs business rule checks
  }
}

The problem with this code is that the UI handles data fetching, state persistence, and rule evaluation at the same time. That makes the logic difficult to reuse and nearly impossible to test in isolation.

When a page carries everything, coupling spreads with the business

As soon as requirements include login state, cart management, cross-page coordination, order placement, or payment, the page stops being just a presentation layer and turns into the business hub. At that point, any change forces synchronized modifications across multiple pages.

Moving from pages to Systems is the key mindset shift for complex HarmonyOS apps

The real upgrade is not renaming folders. It is changing the driving relationship. The old model is “the user clicks, and the page handles everything.” The new model is “user input triggers the System, and the UI reacts automatically after the state changes.”

That means pages must become thinner, while Systems must become richer. The page no longer decides the business behavior. It only triggers actions and consumes state.

// New driving relationship
// User input -> System processing -> Store update -> UI refresh
function submitBySystem(engine: OrderEngine, store: AppStore) {
  engine.submitOrder(store) // The UI only triggers the action; it does not orchestrate the flow
}

The key point in this example is not the syntax. It is the transfer of responsibility: business control moves from the page to the system layer.

A four-layer structure can unify complexity management in HarmonyOS apps

For complex applications, a stable approach is to use four layers: Store manages state, System manages rules, Engine manages workflows, and UI manages presentation. The advantage of this model is clear boundaries and long-term evolvability.

In this structure, the Store becomes the single source of truth and avoids one copy of state per page. The System encapsulates business rules for a single domain. The Engine composes multiple Systems. The UI only renders data and responds to user input.

The first step is to extract state out of the page

In many projects, the earliest issue is scattered state. The @State property inside a page is suitable for local UI state, but it should not serve as the primary source of business state, especially for cross-page data such as carts, user sessions, and order context.

// store/CartStore.ts
export class CartStore {
  items: Item[] = [] // Centralized cart data management
}

export const globalCartStore = new CartStore()

The purpose of this code is to establish a single state entry point and prevent multiple pages from maintaining separate cart copies.

// Page.ets
@State cartStore: CartStore = globalCartStore // The page only consumes shared state

Once the page references a shared Store, the cost of state synchronization drops significantly.

Business logic must move down from the page into domain Systems

If a page directly pushes data, validates rules, and handles exceptions, the logic becomes fragmented. The correct approach is to define behavior inside domain Systems so that pages only call actions.

// system/CartSystem.ts
export class CartSystem {
  addItem(store: CartStore, item: Item) {
    store.items.push(item) // Let the System mutate state in a unified way
  }

  removeItem(store: CartStore, id: string) {
    store.items = store.items.filter(i => i.id !== id) // Encapsulate delete rules in one place
  }
}

This code extracts “add to cart” and “remove” behavior from the UI and turns it into reusable, testable business capabilities.

Systems should be split by domain instead of becoming another giant class

Do not push all logic into AppSystem. That only replaces page bloat with system bloat. A better approach is to split by domain, such as UserSystem, CartSystem, OrderSystem, and PaymentSystem.

export class UserSystem {
  login(store: UserStore, userInfo: UserInfo) {
    store.user = userInfo // Write user information after successful login
    store.isLogin = true // Maintain login state consistently
  }

  logout(store: UserStore) {
    store.user = null // Clear user information
    store.isLogin = false // Reset login state
  }
}

This code shows that the responsibility of a System is to maintain domain rules rather than handle page presentation logic.

When multiple Systems need to collaborate, introduce an Engine orchestration layer

Complex workflows often span multiple domains. For example, placing an order may depend on user state, order creation, inventory, and payment. If those checks are pushed back into the UI, the page will spiral out of control again.

The value of the Engine is that it composes Systems and packages the complete workflow into a single entry point that stays transparent to the UI.

class OrderEngine {
  userSystem = new UserSystem()
  orderSystem = new OrderSystem()
  paymentSystem = new PaymentSystem()

  submitOrder(store: AppStore) {
    if (!store.user.isLogin) {
      return // Stop the workflow immediately if the user is not logged in
    }

    this.orderSystem.create(store) // Create the order first
    this.paymentSystem.pay(store) // Then execute payment
  }
}

This code removes workflow control from the page and prevents the UI from becoming the orchestration center.

In its final form, the UI keeps only three responsibilities

The UI should only display data, receive input, and trigger actions. It should not manage complex state directly, control workflows directly, or carry domain rule evaluation.

Button('Place Order')
  .onClick(() => {
    orderEngine.submitOrder(store) // A click only triggers the action
  })

This example reflects a trigger-based UI. As a result, pages become shorter, more stable, and easier to refactor.

This upgrade is fundamentally about building a business state machine system

Once you complete the Store, System, Engine, and UI layering, you will find that the application is no longer just “a collection of pages.” Instead, it becomes “a system in which state keeps flowing under rule-driven transitions.”

This is also why the model resembles game architecture. Both depend on state changes, rule execution, and UI response. As a result, a System-driven model naturally fits complex interactive applications on HarmonyOS.

The before-and-after difference can be summarized in one sentence

Before the upgrade: the Page holds state, writes logic, calls APIs, and controls workflows. After the upgrade: the Store persists data, the System encapsulates rules, the Engine orchestrates workflows, and the UI focuses on presentation.

This layering does not make simple projects unnecessarily heavy, but it does prevent complex projects from becoming unmanageable. The truly expensive cost is not separating concerns early. It is being forced into reactive refactoring after problems have accumulated.

FAQ

1. Why can’t a HarmonyOS app stay page-driven forever?

Because a page-driven model works for small-scale requirements, but once the business includes cross-page state, permission validation, and multi-step workflows, the page ends up carrying presentation, state, and rules at the same time. That ultimately leads to high coupling and poor maintainability.

2. What boundaries are most commonly confused among Store, System, and Engine?

The Store only stores state and should not contain business logic. The System handles rules within a single domain and should not orchestrate global workflows. The Engine connects multiple Systems and controls cross-domain process flow.

3. Is this four-layer structure only suitable for large projects?

No. Small and mid-sized projects can start with “move state out of the page and sink logic downward,” and introduce an Engine only when workflows become complex. This is a progressive evolution model, not a one-time rewrite template.

Core Summary: This article reconstructs an architectural evolution path for HarmonyOS apps. It focuses on how to move from a page-centric model to a four-layer Store-System-Engine-UI architecture in ArkUI/ArkTS scenarios, solving problems such as scattered state, tightly coupled logic, runaway workflows, and poor testability.