Skip to content

Power Platform Architecture Handbook · Part 5 of 10

Business Logic Placement

Kundan Sah December 28, 2025 8 min read
Dataverse business logic architecture layers

Introduction

Power Platform enables logic in multiple layers. That flexibility creates a common failure pattern. Logic gets implemented in the wrong layer, leading to data integrity issues, performance problems, unreliable automation and unmaintainable solutions. This article defines where each kind of logic belongs, what executes when and how to recover when it breaks.

Dataverse Execution Pipeline

text
Client/UI/API
  |
Security check
  |
PreValidation (Plugins)
  |
PreOperation (Plugins) - within transaction
  |
Core Operation (Create/Update/Delete)
  |
PostOperation (Plugins) - within transaction
  |
Commit
  |
Async Post-Commit:
  - Async Plugins
  - Async Workflows (legacy)
  - Power Automate triggers (eventual)
  - Azure integrations (pattern-dependent)

Pre/Post Operation (sync) are the only places we can reliably enforce transactional integrity. Anything post-commit is eventually consistent by nature.

Detailed Pipeline View

text
Stage 10: PreValidation
  - Validate inputs
  - Block early with clear error
  - No DB transaction

Stage 20: PreOperation (Transactional)
  - Modify target before save
  - Enforce business rules
  - Set default values safely

Stage 30: PostOperation (Transactional)
  - React to saved record
  - Create/update related records
  - Still in transaction

Stage 40: Async PostOperation (Non-transactional)
  - Long-running work
  - Integrations
  - Notifications
  - Retry-friendly operations

If the business rule must be true at the moment data is saved, it belongs in Preoperation sync plug-in (or Custom API if invoked explicitly)

Comparison Table (Execution Order and Pros/Cons)

Business Logic Options

MechanismExecutes WhereTypical TriggerExecution OrderProsConsBest For
Business RuleClient + Server(limited)Form events + limited server evaluationUsually before save UI,server eval varies by scenarioFast, no-code, easyNot reliable for complex server enforcement; limited capabilitiesSimple field validation/defaults
Javascript (Form)ClientForm load/change/saveBefore save(client), can block saveGreat UX control, immediate ffedbackNot server-enforced; bypassable via API/integrationUI behavior, form logic
Plugin (Sync)ServerDataverse message pipelineIn-transaction(Pre/post Operation)Strong integrity, consiostent across UI/APIPerformance risk if heavy, needs ALM disciplineValidation, invariants, transactional updates
Plugin (Async)ServerPost-commitAfter commitSafer for long work; retryable patternsEventual consistency; delayed user-visible effectsNotifications, integrations, enrichments
Custom APIServerExplicit invocationExecutes when called; can be transactionalStrong contract, reusable, explicit entrypointRequires dev discipline; still must manage performanceComplex business operations, "commands"
Legacy Background Workflow (Dataverse Process)Dataverse Async ServiceRecord Create / Update / Status Change / On-demandCan be sync or asyncNo 2-min sync limitLow-codeTransaction safe (runs post-commit)Retries via system jobSlower executionLimited debuggingNot future-facing (Power Automate preferred)Performance impact at scaleSimple background automationBusiness logic without codeLow-medium volume processing
Custom Workflow Activity (.NET extension of workflow)Dataverse Sandbox (Async Service)Invoked inside workflow stepRuns inside workflow execution (async)Extend workflow with custom logicReusable activityStructured inputs/outputsSandbox limits applyNo fine-grained pipeline controlHarder to maintainLegacy architectureNot ideal for heavy computeSmall custom business rules inside legacy workflow
Power AutomateCloudEvent triggers or manualAsync, post commitGreat for orchestration and connectors. ALM friendly and cofig seperation if made solution awareThrottling, retries cuase duplication risk, not transactionalOrchestration, approvals, notifications, integrations

Exceution order reality

  • Client(Business Rule/JS): before save but bypassable
  • Server transactional(Sync plugin/Custom API/Azure Webhook/Workflow): authoritative enforcement
  • Post commit(Flows/Async plugins): orchestration & side effects

Decision Matrix

RequirementChooseWhy
Must enforce data integrity at save timeSync plugin (Preoperation)Transactional,consistent across UI/API
Must run long-running work(seconds/minutes)Async plug-in or Power AutomateAvoid blocking transaction/UI
Needs lots of SaaS connectors/approvalsPower AutomateBest integration/orchestration surface
Needs explicit business operationCustom API(+ optional plugins behind it)Clear contract, reusable command model
Needs immediate UI feedback(hide/show/validate)Javascript+Business RuleBest user experience
Must be callable from multiple clients consistentlyCustom APIStandard interface for apps/integrations
Needs retries and resilience across systemsPower Automate(or Azure integration)Built in retry semantics(but design for idempotency)
Must guarantee exactly-once updatesSync plugin + idempotencyFlows may re-run;async can duplicate

Architect Guidance

  1. Use Custom API for "business commands"
  2. Use Sync Plug-in for "invariants"
  3. Use Power Automate for "orchestration"
  4. Use JS/Business Rule for "UX"

Failure Recovery Playbooks

Sync Plugin Causing Save Failures

Symptoms

  • Users cannot create/update records
  • Errors on save, often widespread

Immediate containment

  • Disable the plugin step(or reduce scope via filetring attributes)
  • If managed solution:deploy emergency patch/hotfix

Root cause checks

  • Recent deployment/version mismatch
  • Null reference/assumption on missing tables
  • Infinite loop due to update inside same message
  • Unexpected excution context(different entity/message)

Hardening actions

  • Add strict input validation in PreValidation
  • Guard with execution depth checks
  • Use filtering attributes and early exit logic
  • Add robust tracing(but don't leave verbose tracing on indefinitely)

Async Plugin/Flow Duplication

Symptoms

  • Duplicate records
  • Duplicate notification
  • Double updates in downstream systems

Immediate containment

  • Pause/disable flow or async step
  • Stop external side effects(connectors/webhooks) if possible

Root cause checks

  • Retries and non-idempotent design
  • Trigger configured on both create and update
  • Concurrency settings causing race conditions
  • Multiple automations doing the same job

Hardening actions

  • Implement idempotency keys(alternate keys/ processed flags)
  • Add "already processed?" checks
  • Reduce trigger conditions
  • Use controlled concurrency and retry policies

Post-commit Orchestration Delay Causes Business Break

Symptoms

  • It eventually updates, but business process expects immediate results
  • Users see incomplete data after save

Immediate containment

  • Communicate expected delay and provide UI indicator if possible
  • If truly blocking, move critical logic into sync plugin or Custom API

Hardening actions

  • Split logic: invariants sync, side effects async
  • Add status fields (Pending/Completed) and user messaging
  • Use asynchronous patterns intentionally, not accidently

Deployment Layering Breaks Logic

Symptoms

  • Works in Dev, failed in Prod
  • Old behavior persists after removal
  • Conflicting steps/rules

Immediate containment

  • Identify which solution layer is active
  • Roll back to last known good managed solution
  • Remove unmanaged customizations in Prod(policy enforcement)

Hardening actions

  • Strict no unmanaged in Prod
  • Versioning discipline
  • Component ownership and dependency mapping
  • Use solution aware flows and environment variables

When Integration Causes Deadlock

Symptoms

  • Save operation hangs or times out
  • SQL timeout or deadlock exception surfaces in logs
  • Intermittent failures under load
  • Multiple related records locked during transaction
  • Users report inconsistent or “random” save failures

Often appears only in higher-volume production environments.

Immediate Containment

  • Disable heavy synchronous plugins calling external systems
  • Move external HTTP calls to Async plugin or Power Automate
  • Reduce transactional scope (avoid cross-entity writes in same transaction)
  • Review long-running logic inside PreOperation/PostOperation

If deadlocks are frequent:

  • Temporarily isolate high-traffic updates
  • Reduce concurrency as a short-term mitigation

Root Cause Checks

  • Is a synchronous plugin making an HTTP call?
  • Is the plugin updating multiple related records in one transaction?
  • Are two plugins updating records in opposite order (lock inversion)?
  • Are bulk operations triggering complex synchronous logic?
  • Is Power Automate updating records also modified by sync plugins?

Classic deadlock pattern:

text
Transaction A:
- Locks Record 1  
- Attempts to lock Record 2  

Transaction B:
- Locks Record 2  
- Attempts to lock Record 1  

Result → deadlock.

In Dataverse, SQL is abstracted — but transactional locking behavior still applies.

Hardening Actions

  • Keep synchronous plugins minimal and deterministic
  • Avoid cross-table updates in PreOperation unless absolutely required
  • Move integration logic to Async stage
  • Implement idempotency for external calls
  • Reduce transaction size and write scope
  • Avoid external API calls from sync plugins

Architectural rule:
The longer a transaction runs, the higher the probability of deadlock.

When Plugin Depth Recursion Happens

Symptoms

  • Save fails with an infinite loop or depth-related exception
  • Plugin executes multiple times unexpectedly
  • Record updates repeatedly without user action
  • Performance degradation during create/update
  • Execution context Depth value increases beyond 1
  • Entire transaction rolls back

Immediate Containment

  • Temporarily disable the specific plugin step (not the full solution)
  • Add a depth guard in code:
    if (context.Depth > 1) return;
  • Restrict Update steps to specific filtering attributes
  • Remove "All Attributes" registrations where unnecessary
  • Review PostOperation steps that update the same entity

If production impact is high, unregister only the problematic step while investigating.

Root Cause Checks

  • Is the plugin updating the same entity that triggered it?
  • Is it registered on Update without filtering attributes?
  • Is a PostOperation plugin modifying fields that retrigger Update?
  • Are multiple plugins updating each other across entities?
  • Is Power Automate updating the same record and causing ping-pong execution?

Common failure pattern:

PostOperation plugin updates the same record
→ Update message fires again
→ Plugin runs again
→ Recursion escalates until depth limit is hit

Depth guards prevent explosion but do not fix flawed design.

Hardening Actions

  • Always use filtering attributes on Update steps
  • Prefer PreOperation when modifying the target entity
  • Add idempotency checks (only update if value actually changes)
  • Introduce a processing flag field if necessary
  • Avoid cross-layer recursion (Plugin ↔ Flow loop)
  • Log Depth and CorrelationId during debugging

Architectural rule:
If your logic modifies the triggering entity, design it to execute exactly once.

Common Confusion and Technical Challenge

  1. Business Rules enforce server logic
  • They are not substitue of server side invariants
  1. Power Automate is a rules engine
  • It is an orchestration engine, not transactional
  1. Async= Safe
  • Async is safer for UX, but introduces timing and duplication risks
  1. Custom API replaces Plugin
  • Custom API is an entrypoint; enforcement still needs pipeline logic behind it

Summary

  1. Client logic improves UX but is bypassable
  2. Server transactional logic is authoritive
  3. Async orchestration is powerful but needs idempotency and exception management
  4. Custom APIs are the right tool for explicit business operation

Related articles