UI Architecture: Building A 3-Layer Component Design

by Alex Johnson 53 views

UI architecture is a critical aspect of building robust and maintainable user interfaces. This article delves into a three-layer component design, offering a structured approach to UI development. Inspired by the architecture of modern chat applications such as WeChat, Feishu, and Slack, this design promotes separation of concerns, reusability, and ease of testing. Let's break down the layers: Pane, Container, and Workspace.

Overview

This redesigned UI package embraces a clean three-layer architecture inspired by the design of modern chat applications. This architectural style makes the UI code more structured and easier to manage. The architecture aims to create a more organized and maintainable UI package.

Architecture Deep Dive

The core of the architecture lies in its three-layer structure, which dictates how the components interact and where responsibilities are placed. This approach enhances the testability and reusability of UI components. Let's analyze a diagram to understand the arrangement of the layers and the relationships between the components. The diagram shows the Workspace at the top level, orchestrating the interaction between Container components, such as AgentList and Chat. The Container components, in turn, manage the Pane components, which handle the pure UI rendering.

┌─────────────────────────────────────────────────────────────┐
│                        Workspace                             │
│  (Top-level orchestrator)                                   │
│                                                             │
│  ┌──────┬────────────────┬─────────────────────────────────┐│
│  │      │                │                                 ││
│  │ Nav  │   AgentList    │           Chat                  ││
│  │ Bar  │   (Container)  │         (Container)             ││
│  │      │                │                                 ││
│  │      │  ┌──────────┐  │  ┌───────────────────────────┐  ││
│  │  📨  │  │ ListPane │  │  │      MessagePane          │  ││
│  │      │  │  (Pane)  │  │  │        (Pane)             │  ││
│  │  📋  │  └──────────┘  │  ├───────────────────────────┤  ││
│  │      │                │  │      InputPane            │  ││
│  │  ⚙️  │                │  │        (Pane)             │  ││
│  │      │                │  └───────────────────────────┘  ││
│  └──────┴────────────────┴─────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘

Three Layers: A Detailed Look

This section offers a thorough view of the three distinct layers: Pane, Container, and Workspace. Each layer has specific responsibilities, contributing to the overall architecture's modularity and maintainability. Let's get into the specifics of each one:

1. Pane Layer: The Visual Foundation

The Pane layer, located at components/pane/, is the pure UI presentation layer. These components are responsible for the visual representation of the UI elements. They accept data through props, emit user actions via callbacks, and internally utilize layout and element components. They are designed to be agnostic of business logic and domain-specific knowledge, making them highly reusable. This allows for easy customization and adaptation without affecting the underlying functionality. The Pane layer focuses solely on how things look, ensuring a clean separation of concerns.

Component Based On Purpose
NavBar ActivityBar Icon navigation bar
ListPane Sidebar + ListItem Generic list panel
MessagePane MainContent + MarkdownText Message display
InputPane Panel + InputBox Input with toolbar
InputToolBar - Toolbar buttons (internal)

Props Pattern:

The props pattern facilitates data flow and user interaction within the Pane components. The components use a well-defined set of props for data display, state management, and user interaction through callbacks. This pattern ensures a consistent interface across all Pane components, simplifying development and maintenance.

interface PaneProps {
  // Data (what to display)
  items?: Item[];
  selectedId?: string;

  // State
  isLoading?: boolean;
  disabled?: boolean;

  // Callbacks (user actions)
  onSelect?: (id: string) => void;
  onClick?: () => void;

  // Styling
  className?: string;
}

2. Container Layer: Bridging Logic and Presentation

The Container layer, situated at components/container/, is the layer where business logic and the UI are integrated. These components utilize Pane components for rendering and Hooks to handle the business logic. They translate hook data to pane props, handle events, and manage business-related states. By separating the business logic from the UI presentation, the Container layer simplifies testing and maintenance. This layer focuses on what the UI does, connecting the visual components with the application's core functionality.

Component Uses Pane Uses Hook Purpose
AgentList ListPane useImages Conversation list with CRUD
Chat MessagePane + InputPane useAgent Chat interface

Example:

Here is a code example to demonstrate how a Container component, AgentList, utilizes the Pane component ListPane and the hook useImages to manage and display conversation data. This illustrates the integration of the UI presentation and business logic within the Container layer.

function AgentList({ agentx, containerId, onSelect }) {
  const { images, resumeImage, deleteImage } = useImages(agentx);
  
  const items = images.map(img => ({
    id: img.imageId,
    title: img.name,
    // ... map to ListPaneItem
  }));

  return (
    <ListPane
      items={items}
      onSelect={(id) => resumeImage(id).then(({ agentId }) => onSelect(agentId, id))}
      onDelete={(id) => deleteImage(id)}
    />
  );
}

3. Workspace Layer: Orchestration and Coordination

The Workspace layer, located at components/studio/ or components/workspace/, is the top-level component. It manages the application's connection, coordinates between Container components, and handles the global state. The Workspace layer provides a ready-to-use interface, orchestrating the interaction between different parts of the application. It acts as the central hub, providing a cohesive and functional user experience.

function Studio({ agentx, containerId }) {
  const [currentAgentId, setCurrentAgentId] = useState(null);
  const [currentImageId, setCurrentImageId] = useState(null);

  return (
    <div className="flex h-full">
      <AgentList
        agentx={agentx}
        containerId={containerId}
        selectedId={currentImageId}
        onSelect={(agentId, imageId) => {
          setCurrentAgentId(agentId);
          setCurrentImageId(imageId);
        }}
        onNew={(agentId) => setCurrentAgentId(agentId)}
      />
      <Chat
        agentx={agentx}
        agentId={currentAgentId}
      />
    </div>
  );
}

Design Principles

The architectural design adheres to core principles to ensure maintainability, reusability, and flexibility. This section highlights the key design principles that guide the development process.

1. Separation of Concerns: A Fundamental Principle

Separation of Concerns is a fundamental principle that guides the three-layer architecture, with clear responsibilities assigned to each layer.

Pane (UI)        →  "How it looks"
Container (Logic) →  "What it does"  
Workspace (Orchestration) →  "How it fits together"

2. Pure UI Naming: Clarity and Consistency

Pure UI naming promotes clarity and consistency within the Pane layer. The naming conventions used for Pane components are generic, emphasizing UI presentation. This approach ensures that the components are not tied to specific business concepts.

❌ Bad ✅ Good
AgentListPane ListPane
ChatPane MessagePane + InputPane
ConversationView ListPane

3. Composition Over Configuration: Flexibility and Control

Composition over configuration advocates the use of React composition instead of complex props. This design pattern enhances flexibility and control over the UI components, which results in more reusable and maintainable components.

// ✅ Good
<ListPane
  items={items}
  renderItemActions={(item) => <DeleteButton />}
/>

// ❌ Bad
<ListPane
  items={items}
  showDeleteButton={true}
  deleteButtonPosition="right"
/>

4. Layout Components as Base: Foundation for Structure

Layout components as base involves utilizing layout components as the foundation for Pane components. This method provides a clear structural base for the UI elements, which promotes organization and consistency.

layout/         →  pane/
─────────         ──────
ActivityBar   →  NavBar
Sidebar       →  ListPane
MainContent   →  MessagePane
Panel         →  InputPane

File Structure and Organization

The file structure is carefully organized to reflect the component design. This ensures a clean and manageable project structure. The components are grouped into logical directories based on their roles in the three-layer architecture. This facilitates easier navigation and maintenance of the codebase.

packages/ui/src/components/
├── layout/           # Pure structural containers (existing)
│   ├── ActivityBar.tsx
│   ├── Sidebar.tsx
│   ├── MainContent.tsx
│   └── Panel.tsx
│
├── element/          # Atomic UI elements (existing)
│   ├── ListItem.tsx
│   ├── Button.tsx
│   └── ...
│
├── pane/             # Pure UI components (NEW)
│   ├── NavBar.tsx
│   ├── ListPane.tsx
│   ├── MessagePane.tsx
│   ├── InputPane.tsx
│   ├── InputToolBar.tsx
│   └── index.ts
│
├── container/        # Business components (NEW)
│   ├── AgentList.tsx
│   ├── Chat.tsx
│   └── index.ts
│
└── studio/           # Top-level workspace
    └── Studio.tsx

Tasks and Implementation

The implementation involves creating the specified directories and components. This section details the specific tasks required to complete the UI package redesign.

  • [x] Create pane/ directory with README
  • [x] Implement NavBar component
  • [x] Implement ListPane component
  • [x] Implement InputPane + InputToolBar components
  • [x] Implement MessagePane component
  • [x] Create container/AgentList component
  • [x] Create container/Chat component
  • [x] Update Studio to use new architecture
  • [ ] Add Storybook stories for Container components
  • [ ] Write integration tests
  • [ ] Update package exports

Benefits: Why This Architecture Works

Adopting this three-layer architecture offers several benefits, significantly enhancing the development process and the quality of the UI.

  1. Testability: The clear separation of concerns makes it easy to write unit tests for each component in isolation.
  2. Reusability: Pane components are designed to be reusable in various contexts.
  3. Maintainability: The separation of logic and presentation simplifies changes and updates.
  4. Flexibility: The design allows swapping containers without affecting the UI.
  5. Documentation: Each layer has clearly defined responsibilities, which improves the project documentation.

This approach will increase the project's long-term sustainability and scalability. This architecture lays the foundation for a well-organized, scalable, and maintainable UI package.

For more information, consider exploring the React documentation.