IRIS App Architecture Decision Guide
Overview
One of the most important decisions in IRIS App development is knowing where to put your logic. Magentrix provides two paths for data access and server-side work: the REST API V3 via the Magentrix SDK, and custom C# controllers. Choosing the right path for each requirement keeps your app maintainable, secure, and performant.
This section defines the boundary between the frontend and the backend, explains when each path is appropriate, and provides a practical decision flowchart you can apply to any requirement.
The Fundamental Boundary
IRIS Apps are front-end only. This is not a guideline — it is a hard architectural constraint.
| Frontend (Vue 3 — IRIS App) | Backend (C# — Controllers & Classes) |
|---|
| Rendering and display logic | Business rule evaluation |
| User interaction and input handling | Calculations and data transformation |
| State management within the component | Multi-step transactions |
| Calling the SDK to read and write data | Scheduled jobs and background processing |
| Formatting and presenting API responses | Email dispatch and platform API orchestration |
| Navigation and routing | Permission elevation and system-mode operations |
⚠️ Warning: Never implement business rules, validation logic, or data calculations inside Vue components. If the server data changes, front-end logic becomes stale and inconsistent. All rules must live server-side.
Data Access: Two Paths
Path 1 — Magentrix SDK via REST API V3 (Default)
The SDK wraps the REST API V3 and is your default data access layer for every IRIS App. It handles authentication, session management, token refresh, and response parsing automatically.
Use this path for:
- Querying records with MEQL via
query() - Retrieving a single record via
retrieve() - Creating, updating, or deleting records via
create(), edit(), delete() - Batch operations — arrays passed to
create(), edit(), or upsert() - Getting the current user's context via
getUserInfo()
Path 2 — Custom C# Controller via execute() (When REST API Is Insufficient)
When the REST API cannot fulfil a requirement, you build a standalone Active Controller (.ctrl file) and call it from the IRIS App using the SDK's execute() method. The controller handles the server-side logic and returns a JSON response.
Use this path only when you need:
- Complex multi-step business logic or conditional workflows
- Transactions that span multiple entity operations atomically
- Operations requiring system-mode or admin privileges (
DatabaseOptions { SystemMode = true }) - Server-side scheduling via
CronJob - Cache invalidation via
CacheManager - Email dispatch via platform email APIs
- Custom data aggregation not achievable with MEQL
- Webhook endpoints receiving data from external systems
💡 Note: Always check whether the SDK's built-in methods can satisfy the requirement before building a controller. Controllers add deployment overhead — they must be published to the server and require a hard browser refresh to take effect.
Decision Matrix
| Requirement | Use SDK | Use Controller (/acls/) |
|---|
| Fetch a list of records with filters | ✅ query() with MEQL | |
| Fetch a single record by ID | ✅ retrieve() | |
| Create one or many records | ✅ create() | |
| Update one or many records | ✅ edit() | |
| Delete one or many records | ✅ delete() / deleteMany() | |
| Get current user info | ✅ getUserInfo() | |
| Count records matching a condition | ✅ MEQL COUNT(Id) aggregate | |
| Traverse a lookup relationship | ✅ MEQL dot notation | |
| Complex validation across multiple entities | | ✅ Controller with business logic |
| Atomic transaction across multiple entities | | ✅ Controller with Database.BeginTransaction() |
| Operation requiring admin privileges | | ✅ Controller with SystemMode = true |
| Schedule a background job | | ✅ Controller calling CronJob.RunOnce() |
| Send an email | | ✅ Controller using platform email API |
| Invalidate or refresh server cache | | ✅ Controller calling CacheManager.RefreshUserTabs() |
| Aggregate data not achievable with MEQL | | ✅ Controller using Database.QueryFlex() |
| Receive an inbound webhook | | ✅ Standalone controller endpoint |
Decision Flowchart
Can the SDK's query(), retrieve(), create(),
edit(), or delete() methods fulfil this requirement?
│
├── YES ──► Use the SDK. No controller needed.
│
└── NO
│
Does it require a transaction, system-mode
privileges, scheduling, email, or caching?
│
├── YES ──► Build a standalone controller (.ctrl)
│ and call it via SDK execute().
│
└── NO
│
Can MEQL aggregates or QueryFlex
satisfy the data requirement?
│
├── YES ──► Use MEQL via query()
│ or QueryFlex in a controller.
│
└── NO ──► Build a controller with
custom C# logic.
Data Flow Diagrams
Path 1 — SDK and REST API (Standard)
IRIS App (Vue 3)
│
│ dataService.query('SELECT Id, Name FROM Account')
│
▼
Magentrix SDK
│
│ POST /api/3.0/query
│ Authorization: Bearer {token}
│
▼
REST API V3
│
│ Applies field-level security
│ Evaluates record permissions
│ Enriches response with metadata
│
▼
IrisDataResponse { data, __permissions, __metadatas }
│
▼
IRIS App renders result
Path 2 — Custom Controller (Complex Logic)
IRIS App (Vue 3)
│
│ dataService.execute('/acls/order/processorder', payload)
│
▼
Magentrix SDK
│
│ POST /acls/order/processorder
│
▼
Active Controller (.ctrl)
│
│ Validates input
│ Runs business logic
│ Executes transaction across multiple entities
│ Calls CronJob / CacheManager / Email as needed
│
▼
ActionResponse (JSON)
│
▼
IRIS App handles result
Common Mistakes to Avoid
Building a Controller for Standard CRUD
Creating a controller just to fetch or save records adds unnecessary complexity. The SDK handles this natively with less code, automatic security enforcement, and no deployment step.
// ❌ Avoid — controller wrapping a simple query
public ActionResponse GetAccounts()
{
var accounts = Database.Query<Account>()
.Where(f => f.IsActive == true)
.ToList()
return Json(accounts, true)
}
// ✅ Correct — use the SDK directly from the IRIS App
const result = await dataService.query(
'SELECT Id, Name FROM Account WHERE IsActive = true'
)
Putting Business Logic in Vue Components
Calculating totals, evaluating eligibility rules, or enforcing workflow conditions inside a Vue component means those rules can be bypassed and become inconsistent when data changes server-side.
// ❌ Avoid — business logic in the frontend
function calculateDiscount(account: Account): number {
if (account.Revenue > 1000000)
return 0.2
if (account.Revenue > 500000)
return 0.1
return 0
}
// ✅ Correct — call a controller that applies the rule server-side
const result = await dataService.execute(
'/acls/pricing/getdiscount',
{ accountId: account.Id }
)
Using execute() for Everything
The SDK's execute() method exists for calling custom controllers — not as a replacement for the standard SDK methods. Over-relying on execute() bypasses the SDK's built-in security enforcement, metadata enrichment, and error handling.
Publishing Considerations
The choice between Path 1 and Path 2 also has a deployment impact:
| | SDK / REST API Changes | Controller Changes |
|---|
| Where deployed | Frontend bundle only | Server-side .ctrl file |
| Deploy command | magentrix vue-run-build | magentrix publish |
| Browser refresh needed | Standard refresh | Hard refresh (Ctrl+Shift+R) required |
| Takes effect | Immediately on next page load | After server compilation and hard refresh |
What to Read Next