目录

postman-gpui Architecture Design

postman-gpui Architecture Design

What Is GPUI

GUI stands for Graphical User Interface. GPUI is a Rust framework for building native graphical applications.

At a high level, GPUI can be understood as an event-driven system:

Event → Update State → Render

Core Abstraction

A simplified GUI event loop can be expressed as:

loop {
    event = wait_event();
    state = update(state, event);
    render(state);
}

The application waits for events such as mouse clicks, keyboard input, window resizing, timers, and asynchronous task results.

Each event may update the application state. When the state changes, GPUI schedules the affected interface to be rendered again.

How GPUI Draws the UI in a Window

The application state is not drawn directly to the screen. It passes through several stages:

State
  ↓ render()
Element Tree
  ↓ layout()
Layout
  ↓ paint()
Pixels

Each stage has a different responsibility:

State
    The current application and UI state.

Element Tree
    The UI structure produced by render().

Layout
    The position and size of every element.

Paint
    Drawing commands for text, backgrounds, borders, and shapes.

Pixels
    The final image displayed in the window.

A complex interface grows by composing smaller elements:

Application
├── Header
├── Sidebar
└── Workspace
    ├── Request Editor
    │   ├── Method Selector
    │   ├── URL Input
    │   └── Send Button
    └── Response Panel

Each component renders a small part of the element tree. The root component combines these smaller trees into the complete user interface.

Complete Abstraction

The event loop and drawing pipeline can be combined into one model:

Event Loop
    ↓ dispatch Event
update(State, Event)
New State
    ↓ render()
Element Tree
    ↓ layout()
Layout
    ↓ paint()
Pixels
Wait for the next Event

It can also be expressed as pseudocode:

loop {
    event = wait_event();

    state = update(state, event);

    element_tree = render(state);
    layout = compute_layout(element_tree);
    pixels = paint(layout);

    present(pixels);
}

In a real GPUI application, developers do not write this event loop manually. GPUI owns the platform event loop, while application code updates state stored in entities and uses cx.notify() to request rendering.

In summary:

GPUI is an event-driven GUI framework. Events update application state, and application state is transformed into pixels through rendering, layout, and painting.

Why MVVM

GPUI explains how events are processed and how application state is drawn into a window:

Event
→ Update State
→ Render

However, GPUI does not decide where the state should live, who is allowed to change it, or where business logic and external side effects should be implemented.

For a small application, everything can be written directly inside the View. As the application grows, the View may begin to own too many responsibilities:

View
├── Read User Input
├── Store UI State
├── Validate Data
├── Apply Business Rules
├── Execute HTTP Requests
├── Handle Responses
├── Access Storage
└── Draw the UI

The visible result is usually a very large file. However, file size is only the symptom. The deeper problem is that display logic, application state, business rules, and external side effects have become tightly coupled.

MVVM applies the idea of separation of concerns to stateful GUI applications.

All-in-One Page

An unlayered JSP-style page provides a useful comparison.

A single page may read input, apply business rules, access a database, and generate HTML:

JSP Page
├── Read Input
├── Store Page State
├── Apply Business Logic
├── Access Database
└── Render HTML

It can be summarized as:

Page
    = View
    + State
    + Business Logic
    + Infrastructure

This does not mean that JSP cannot be layered or that Java applications are automatically layered.

The real comparison is:

All-in-One Page
vs
Layered Application

The same problem can appear in a GUI application:

GPUI Component
├── Read Input
├── Store State
├── Build Request
├── Execute HTTP
├── Handle Response
└── Draw the UI

In this structure, every piece of logic belongs to the concrete page or component.

As the application grows:

More Features
→ More Event Handlers
→ More Shared State
→ More Dependencies
→ Larger View Files
→ Higher Coupling

Splitting one large View into several files does not necessarily solve the problem:

view_part_1.rs
view_part_2.rs
view_part_3.rs

If those files still mix state, business rules, HTTP, storage, and drawing, the architecture remains all-in-one.

Layered Application

A layered Java Web application separates responsibilities behind explicit boundaries:

Controller
Service
Repository
Database

Each layer has a different responsibility:

Controller
    Receives external requests.

Service
    Coordinates application operations.

Repository
    Loads and stores application data.

Database
    Persists the data.

The central idea is:

Display
    ≠ State Management
    ≠ Business Logic
    ≠ External Side Effects

Layering does not remove complexity. It gives each kind of complexity a clear owner.

Once business logic no longer depends on a specific page, it can be reused by different entry points:

Web Page ─────┐
REST API ─────┼──> Service
CLI Command ──┤
Test ─────────┘

Without layering:

Business Logic
→ Belongs to a Specific Page

With layering:

Business Logic
→ Belongs to an Independent Service

This makes the logic easier to test, maintain, replace, and reuse.

Applying Layering to GUI

MVVM applies the same separation principle to the presentation side of a stateful GUI application:

View
    ↓ Action
ViewModel
    ↓ Command
Application Service
Model / HTTP / Storage

Each part has a clear responsibility:

View
    Receives user events and draws the current state.

ViewModel
    Owns presentation state and handles user actions.

Model
    Represents business data and business rules.

Application Service
    Executes HTTP, storage, and other external side effects.

The ViewModel gives the state in GPUI’s event loop a clear owner:

Event
View
    ↓ Action
ViewModel
New State + Command
    ↓             ↓
Render View    Execute Service
                  Result
             Update ViewModel

For postman-gpui, this separation can be summarized as:

GPUI View
    Draws inputs, buttons, tabs, history, and responses.

ViewModel
    Owns URL, method, headers, body, loading,
    response, error, and selected-tab state.

Application Service
    Coordinates asynchronous request execution
    and delivers results back to the ViewModel.

HTTP Core
    Transforms a Request into
    Result<Response, Error>.

The HTTP Core no longer belongs to a particular button or GPUI component:

GPUI Application ─┐
Future CLI ────────┼──> HTTP Core
Tests ─────────────┘

The ViewModel itself is usually presentation-specific, so a simple CLI may not reuse the complete ViewModel. The most reusable parts are normally the Model, business rules, and services that do not depend on GPUI types.

MVVM is not the only possible solution. MVC, MVP, MVU, and other architectural patterns can also separate responsibilities. The important idea is not the name of the pattern, but clear state ownership and explicit module boundaries.

In summary:

MVVM applies the idea of layered architecture to stateful GUI applications. It gives presentation state a clear owner and separates UI rendering from application logic and external side effects, making independent parts easier to test, maintain, and reuse.

postman-gpui Architecture

postman-gpui is a native, cross-platform HTTP client built with Rust and GPUI.

From the user’s perspective, it accepts request information, communicates with an HTTP server, and displays the response through a graphical interface.

At the highest level:

User Input
→ HTTP Request
→ HTTP Server
→ HTTP Response
→ Graphical Output

However, postman-gpui is not only an HTTP function. It is also a long-running GUI application that must preserve state, process events, execute asynchronous operations, and continuously redraw the interface.

It therefore combines two systems:

GUI System
    Event → State → Render

HTTP System
    Request → Network → Response

MVVM and the Application Service connect these two systems.

Core Abstraction

The core purpose of postman-gpui is to transform graphical user input into an HTTP request and transform the HTTP result back into graphical output:

User Input
Request
HTTP
Response
Application State
Graphical Output

For example:

URL Input
Method Selector
Headers Editor
Body Editor
HTTP Request
Status
Response Headers
Response Body

The HTTP operation itself can be expressed as a function:

Result<Response, Error>
    = HTTP(Request, Network Environment)

The GUI operation can also be expressed as a state transition:

(new_state, command)
    = update(old_state, user_event)

The complete application combines them:

User Event
→ Update State
→ Produce HTTP Command
→ Execute HTTP
→ Receive Result
→ Update State
→ Render UI

Unlike a simple CLI command, this process does not end after one request. The application returns to the event loop and waits for the next user action.

Architecture Layers

postman-gpui separates its responsibilities into several architectural layers:

View
    ↓ Action
ViewModel
    ↓ Command
Application Service
HTTP Core / Storage

Each layer has a different responsibility.

View

The View is the graphical interface built with GPUI:

View
├── URL Input
├── Method Selector
├── Parameters Editor
├── Headers Editor
├── Body Editor
├── Send Button
├── History List
└── Response Panel

The View is responsible for:

User Event → Action
State → Element Tree

It receives mouse and keyboard events, converts them into application actions, and draws the current state.

The View should not need to understand how an HTTP client opens a connection, follows redirects, stores cookies, or reads from SQLite.

ViewModel

The ViewModel owns the state required by the interface:

ViewModel
├── Request Draft
├── Selected Method
├── Selected Tab
├── Loading State
├── Response State
├── Error State
└── History State

It receives actions from the View:

Set URL
Select Method
Add Header
Click Send
Cancel Request
Select History

It then produces a new state or a command:

(new_state, command)
    = ViewModel.update(old_state, action)

The ViewModel does not draw pixels. It describes what the application currently means:

Not Sent
Loading
Success
Error
Cancelled

The View converts that state into visual elements.

Model

The Model represents the data and rules of the application:

Request
Response
Header
Request Body
History Entry
HTTP Error

These types do not need to know about buttons, windows, colors, focus, or layout.

They form the contracts passed between the other layers.

Application Service

The Application Service connects the ViewModel with external capabilities:

ViewModel Command
Application Service
HTTP Core / Storage
Result
ViewModel

It coordinates operations such as:

Start HTTP Request
Cancel HTTP Request
Receive HTTP Result
Load History
Save History

The Application Service is not part of the View, and it is not the HTTP implementation itself. It is the coordinator between application state and external side effects.

HTTP Core and Storage

The HTTP Core is responsible for network behavior:

Request
→ HTTP Execution
→ Result<Response, Error>

Storage is responsible for persistence:

History Entry
→ Storage
→ Stored History

These modules should remain independent from the graphical interface.

Complete Request Flow

When the user clicks the Send button, the complete flow can be expressed as:

Click Send
View emits Action
ViewModel validates current state
ViewModel becomes Loading
ViewModel produces Send Command
Application Service executes Command
HTTP Core sends Request
HTTP Core returns Result
Application Service delivers Result
ViewModel becomes Success or Error
GPUI renders the new State

The same process can be written more compactly:

Click Send
→ Action
→ Command
→ HTTP Request
→ Result
→ State
→ Render

The important point is that the View does not directly execute HTTP and then modify another UI component.

Instead:

View
    produces Action

ViewModel
    produces State and Command

Application Service
    executes Command

HTTP Core
    produces Result

ViewModel
    accepts Result and produces New State

View
    renders New State

This creates a predictable one-directional flow:

Event
→ Action
→ Command
→ Result
→ State
→ View

The result is then followed by another event, so the application continues as a loop.

Isolating the HTTP Core

The HTTP Core should have a small and explicit boundary:

HTTP(Request)
    → Result<Response, Error>

Its input is a normal Request:

Request
├── Method
├── URL
├── Headers
└── Body

Its output is a normal result:

Result
├── Response
│   ├── Status
│   ├── Headers
│   └── Body
└── Error

The HTTP Core may understand:

HTTP Methods
Headers
Bodies
Cookies
Redirects
Timeouts
Network Errors

It should not understand:

GPUI Window
Button
Input Element
Selected Tab
Response Panel
Element Tree
Layout
Paint

This dependency boundary is what makes the HTTP Core independent from the GUI:

GUI depends on HTTP Core

HTTP Core does not depend on GUI

Because the HTTP Core accepts and returns ordinary data, it can be reused by different entry points:

GPUI Application ─┐
Future CLI ────────┼──> HTTP Core
Tests ─────────────┘

The GPUI application and CLI may use different presentation states, but they can share the same request models, response models, HTTP execution rules, authentication logic, redirect behavior, and error handling.

The same isolation principle applies to Storage:

Application
→ Storage Interface
→ SQLite

The View does not need to know where History is stored or which SQL statements are executed.

Complete Abstraction

The complete architecture can be summarized as:

postman-gpui
    = GPUI
    + MVVM
    + Application Service
    + HTTP Core
    + Storage

Each part answers a different question:

GPUI
    How are events processed and the UI drawn?

MVVM
    How are View, state, and Model organized?

Application Service
    How are commands and external operations coordinated?

HTTP Core
    How is a Request transformed into a Response?

Storage
    How is application history persisted?

The complete runtime flow is:

Event Loop
User Event
GPUI View
    ↓ Action
ViewModel
    ↓ Command
Application Service
HTTP Core / Storage
    ↓ Result
ViewModel
    ↓ New State
GPUI View
    ↓ render()
Element Tree
    ↓ layout()
Layout
    ↓ paint()
Pixels
Wait for the next Event

In functional form:

(new_state, command)
    = ViewModel(old_state, user_event)

result
    = ApplicationService(command, environment)

final_state
    = ViewModel(new_state, result)

pixels
    = GPUI(final_state)

In summary:

postman-gpui combines an event-driven GUI, an MVVM-style state architecture, application services, and an isolated HTTP Core. User actions become commands, commands produce results, results update state, and GPUI transforms that state into pixels.