How Clean Architecture and Vue 3 Composition API Simplify Complex Frontend Projects
This article examines the growing complexity of a points‑mall front‑end, identifies architectural and code‑organization pain points, and demonstrates how applying Clean Architecture together with Vue 3's Composition API can decouple business logic from UI, improve maintainability, and streamline development.
1. Introduction
As front‑end business complexity increases, code‑maintenance cost, scalability, and team‑collaboration efficiency become critical issues. Studies show developers spend about 80% of their time understanding code rather than writing it. Two main dimensions cause this: uncontrolled architecture (tight coupling between business logic and framework) and limited code‑organization patterns (fragmented logic, deep nesting, bloated components).
2. Points Mall Project Background and Requirements
2.1 Project Background
The Application Bao points mall is a non‑typical e‑commerce front‑end that handles user assets, product display, and point‑based redemption. Core functions include:
User asset display – view point balance, composition, and acquisition records.
Product display – list product name, price, stock, pre‑exchange conditions, and associated games.
Point redemption – users exchange points for products.
Different product types involve distinct interaction logic, such as physical goods requiring shipping address, game items needing server selection, Q‑coin requiring QQ number, WeChat discount linked to WeChat, and coupons showing usable games.
2.2 Product Architecture Evolution
During requirement iterations, the product architecture evolved from single‑product to single‑SKU, then to SPU where an SPU can associate multiple SKUs displayed in a popup.
Dimension
SPU (Standard Product Unit)
SKU (Stock Keeping Unit)
Definition
A set of products sharing the same attributes (brand, model, core function).
A sellable physical item with a unique specification (color, storage, etc.).
Purpose
Product classification, standardized display, aggregation analysis.
Inventory management, sales tracking, order fulfillment.
Granularity
Coarse – describes a product series.
Fine – describes the smallest sellable unit.
Example
iPhone 16 (represents the whole series).
iPhone 16 Pro 512GB Titanium (unique stock identifier).
2.3 Project Pain Points
Developers often place data requests and logic directly in components. With frequent product‑architecture changes and new product types, components become bloated and nested, making code comprehension difficult and exposing maintainability and extensibility issues. When the architecture shifted from single‑SKU to SPU, the data flow and code organization required a thorough refactor.
3. Stripping the Framework: Clean Architecture
3.1 Clean Architecture
MoonWebTeam promotes applying Clean Architecture (proposed by Robert C. Martin) to front‑end development. It centers on a domain model and achieves framework‑independence through layered decoupling:
Domain Layer – pure business entities and rules (e.g., order calculation, inventory logic).
Application Layer – orchestrates domain capabilities (e.g., user order flow).
Adapter Layer – bridges UI, API, and external services.
Framework Layer – concrete Vue/React integration.
Dependency rule: outer layers may depend on inner layers, but inner layers must not depend on outer layers.
3.2 Group Layer Implementation Survey
Our investigation found most code does not follow strict inner‑to‑outer one‑way dependencies; instead, it leans toward DDD practices.
DDD
Clean Architecture
Layering Logic
Divided by business domain (user, order, etc.)
Divided by technical responsibility (entity → use‑case → adapter → framework)
Dependency Direction
Cross‑layer calls allowed
Strict one‑way (inner layers unaware of outer)
Relation to Framework
Not forced to decouple
Core logic must be framework‑agnostic
Typical Components
Aggregate roots, domain events, anti‑corruption layer
Use‑case controller, adapter, DI container
Common violations include:
Updating store data directly in entity or use‑case layers to trigger UI updates – this couples core logic to Vue’s reactivity.
Calling adapter methods from the entity layer to update entity properties – adapters should remain outside the domain.
Mixing UI view logic with business logic – breaks separation of concerns.
3.3 Current State of the Points Mall
The redemption logic is tightly coupled with UI: each product’s exchange flow updates UI components, triggers pop‑ups, and handles errors, leading to:
Difficult code reading – logic scattered across many files.
Code bloat – each product maintains its own specific logic, causing duplication.
High complexity – tangled data flow and deep call chains.
3.4 Clean‑Architecture Practice for the Points Mall
Key solutions:
Extract UI dialogs (exchange, result) from the use‑case; the use‑case only handles data flow.
Let view components consume the data emitted by the use‑case and trigger UI actions (open/close dialogs, error handling).
3.4.1 Extract Domain Entities and Use‑Cases
Domain Entities (Domain Layer) – Product entities (SPU, SKU) are defined as TypeScript types.
// Domain entities (Domain Layer)
// User entity
class User {
private balance: number;
private consumptionRecords: ConsumptionRecord[];
}
// Product entity
class Product {
id: string;
stock: number;
checkStock(): boolean { return this.stock > 0; }
applyLimitRule(): boolean { return true; }
}Use‑Case (Application Layer)
class ExchangeUseCase {
constructor(private exchangeService: ExchangeService) {}
async execute(user: User, product: Product): Promise<Order> {
if (!product.checkStock()) throw new Error('库存不足');
user.deductPoints(product.pointsRequired);
return this.exchangeService.createOrder(user, product);
}
}3.4.2 Composition API Integration
Using Vue 3 hooks to implement the layered architecture:
// useExchange.ts – composable business logic
export const useExchange = (product: Ref<Product>, user: Ref<User>) => {
const orderStore = useOrderStore();
const error = ref<Error | null>(null);
const executeExchange = async () => {
try {
const exchangeService = inject(ExchangeServiceKey);
await exchangeService.deductPoints(user.value, product.value.points);
orderStore.orderResult = await exchangeService.createOrder(product.value);
} catch (err) {
error.value = err as Error;
}
};
const showConfirmDialog = () => {
Modal.confirm({ title: '确认兑换?', content: product.value.name });
};
return { executeExchange, showConfirmDialog, orderResult: orderStore.orderResult, error };
};The composable maps to Clean Architecture layers: useExchange lives in the UI/Hooks layer, orchestrates domain entities and use‑cases, while adapters handle API‑to‑entity conversion.
3.5 Comparative Advantages
Dimension
Traditional Layered Architecture
Reactive Clean Architecture
Data Flow
Strict one‑way
Reactive closed‑loop
State Management
Prop drilling across layers
Reactive context sharing
Code Reuse
Inheritance / composition patterns
Hooks composition
Framework Coupling
Fully decoupled
Selective coupling via hooks
Development Efficiency
High cognitive cost
Matches front‑end intuition
Typical Code Size
5‑layer structure
3‑layer reactive
5. Conclusion
Front‑end architecture balances determinism and flexibility. Vue 3 Composition API enables a responsive, layered approach that aligns with developers’ mental models while preserving the benefits of Clean Architecture—clear separation, easier maintenance, and better reusability. Applying these practices to the points‑mall project demonstrates how complex business requirements can be managed effectively and provides a reusable blueprint for other front‑end systems.
MoonWebTeam
Official account of MoonWebTeam. All members are former front‑end engineers from Tencent, and the account shares valuable team tech insights, reflections, and other information.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
