You can use a ViewModel to prepare model data and view-specific behavior for a UI, API, export, or report without placing reusable business rules in the UI.
Keep business logic out of the UI
A user interface should render information, collect interaction, and invoke behavior. Do not make it the place where the application decides what is valid, permitted, or required by the business.
When business logic is embedded in UI code or UI bindings, it is difficult to find, reuse, test, and change. The same rule is often copied into another screen, client type, or integration. A later UI redesign can then accidentally change application behavior.
For example, a screen may display a customer's address as one formatted string. Formatting that string for this screen belongs in the ViewModel. A rule such as a customer cannot be deleted while it has orders is a reusable business rule and belongs in the Model, where every use case can apply it.
Why UI code accumulates business logic
UI code often gains logic because the UI must bridge the gap between model information and a presentation-specific use case:
- Data must be transformed before it can be displayed in the required format.
- A user selects or edits presentation data that must be mapped back to model objects before model behavior can run.
- An interaction may coordinate several operations and check intermediate results.
- Input must be checked before an action can continue.
Adding all of this directly to the UI may appear fast for one screen. It creates long-term problems:
- You cannot readily reuse logic that is tied to one UI.
- Rules become hard to discover because they are spread across screens and UI technologies.
- Changes become risky because the full set of UI-side rules is unknown.
- UI specialists must change application logic to change presentation.
- A UI on another physical tier may not be able to access the domain layer directly.
Code review and architectural patterns can detect these problems, but the sustainable approach is to make the correct placement of logic easier than placing it in the UI.
Use the Model, ViewModel, and UI for different responsibilities
A ViewModel is a transformation layer between the Model and a consumer. It presents a selected perspective of the model for one purpose. That purpose is commonly a UI use case, but it can also be an API, a data-transfer structure, an export, or a report.
| Layer | Responsibility | Example |
|---|---|---|
| Model | Holds reusable business concepts, rules, and behavior that must apply regardless of UI or consumer. | Prevent deleting a customer that has orders. |
| ViewModel | Selects, transforms, and coordinates information for one context. It may contain logic that is unique to that context. | Provide a customer name-and-address display value and the order rows required by a customer-details screen. |
| UI | Renders the ViewModel, collects user interaction, and invokes the available action. | Show the customer-details fields and let the user choose the Delete action. |
This separation does not mean that a ViewModel is only for displaying data. A ViewModel can expose the data and behavior that a particular view needs, including validation and actions for that context. See Documentation:ViewModel actions and Documentation:Application actions.
Put reusable rules in the Model
Put logic in the Model when it expresses a business rule that more than one use case can need, or when the rule must protect the model from entering an invalid state.
Examples:
- An order must have at least one order line before it can be confirmed.
- A customer with orders cannot be deleted.
- An invoice total is calculated from its invoice lines.
These rules must hold whether the change comes from a rich client, web UI, API, import, or background process.
Put context-specific transformations in the ViewModel
Put logic in the ViewModel when it is specific to how one consumer sees, edits, or coordinates model information. The ViewModel can navigate associations, combine attributes, and expose only the information needed for its purpose.
For example, a customer-details ViewModel can expose:
- Customer name and contact information for editing.
- A formatted address for a summary area.
- A collection of the customer's orders.
- A view-specific indicator that helps the UI decide whether to show a delete option.
The underlying delete rule still belongs in the Model. The ViewModel provides the perspective required by the customer-details use case; it does not replace the model's protection of the business rule.
A practical placement test
Before adding logic to a UI, use these steps:
- State the rule or transformation in one sentence.
- Ask whether it must apply outside this one view or consumer.
- If it must apply generally or protect model validity, add it to the Model.
- If it only shapes data, interaction, or coordination for this use case, add it to the ViewModel.
- Keep the UI responsible for rendering the result and invoking the exposed behavior.
- Test Model and ViewModel logic separately from the rendered UI.
For example, consider a customer screen that shows an address on one line and offers deletion:
- The one-line address combines street, postal code, and city for this screen. Put that transformation in the ViewModel.
- The customer must not be deleted when orders exist. Put that rule in the Model.
- The UI displays the one-line address and calls the available delete behavior. Do not duplicate the order check in UI code as the authority for the rule.
Why use a ViewModel
A ViewModel provides a defined boundary around a use case. That boundary has practical benefits:
- Testability: You can test transformations, validation, and view-specific behavior without driving a UI and interpreting rendered output.
- Reuse: Different UIs can use the same use-case perspective, such as a beginner and advanced UI, or a rich client and web API.
- Type checking: A well-defined ViewModel supports design-time or compile-time checking rather than relying only on string-based bindings checked at runtime.
- Presentation independence: UI presentation can change without moving business logic into the new UI.
- Security and control: The ViewModel exposes the information needed for the use case rather than the whole business object. A view developer cannot unintentionally expose information outside that boundary.
- Clear ownership: Model-focused developers can maintain business rules while UI-focused developers work on presentation without editing the rules.
Avoid polluting the Model with screen-only members
You can add derived attributes, derived associations, and methods directly to the Model to support a UI. This is appropriate when they represent reusable domain behavior. It becomes a maintenance problem when every screen adds its own presentation-only members to the shared model.
For example, if many screens each add their own derived values and associations solely for rendering, the model becomes harder to understand. When a screen is removed, its presentation-specific model members may remain even though nothing uses them.
Use a ViewModel for these view-specific perspectives. Keep the Model focused on concepts and rules that have value beyond one presentation.
ViewModels in MDriven
In MDriven, the ViewModel defines a perspective over a part of the UML model. It unfolds model data by navigating associations and can transform that data for the intended consumer.
A ViewModel can be:
- Rooted: it starts from a specific model object, such as one customer, then shows information related to that object.
- Unrooted: it has no specific starting object and is commonly used to find or list objects, such as a customer search result.
For a detailed explanation of rooted and unrooted ViewModels and ViewModel classes, see Documentation:ViewModel. For a guided conceptual introduction, see Training:The ViewModel.
Applying the boundary in MVC
The same boundary applies when you use MVC. Instead of exposing a full business object to a view, use a ViewModel as the view's façade. The ViewModel limits which values can be displayed or updated and can supply view-specific derivations.
When updating, apply incoming values to the corresponding online ViewModel and commit through the model-facing flow. This keeps updates within the ViewModel's defined scope and lets model constraints protect the business state. See Documentation:MVC View Model handling and Documentation:MVC View Model constraints for MVC-specific patterns.
