DEV Community

Manohari Jayachandran
Manohari Jayachandran

Posted on

Azure Integration Services Interview Prep Part 2: Logic Apps, Function Apps, Durable Functions, and the Complete Orchestration Picture

Part 1 covered Service Bus, Storage Queues, Event Hub, and Event Grid - the services that move data around a system. None of them actually do anything with that data once it arrives. This part covers the two services that do - Logic Apps and Function Apps - and just as importantly, connects them into the complete picture: what typically triggers this orchestration layer, and where the result actually goes afterward. A panel interview rarely asks about one service in isolation; it asks you to describe a whole pipeline, start to finish.

Complete pipeline

Service 1: Azure Logic Apps

Azure Logic Apps is a low-code, visual workflow orchestration service. Triggers and actions are connected in a designer, backed by hundreds of prebuilt connectors, Salesforce, ServiceNow, SQL, Service Bus, and many more, with retry policies and error handling configurable without writing code.

Think of a flowchart that actually runs itself. Instead of drawing a diagram of "if this, then that" and handing it to a developer to implement, the flowchart is the running system - each box is a real, executing step, and the connectors are pre-wired plugs into other systems rather than something you build a client for yourself.

Logic Apps fit connector-heavy orchestration where the actual logic is relatively straightforward, sequence, conditions, simple transformation, and being readable by a non-developer stakeholder, or having built-in retry and error handling with zero code, is a genuine advantage.

Logic Apps' strengths include hundreds of prebuilt connectors for SaaS systems, databases, and Azure services with no custom auth or client code needed, a visual design reviewable by non-developers, built-in retry policies per action configurable without code, and fast build time for straightforward orchestration. The weaknesses are real too - complex branching logic becomes hard to read as a diagram, genuine unit testing is awkward compared to real code, and complex data transformation is technically possible in the expression language but painful compared to the equivalent C#.

Logic Apps: A Real Example

Consider a scenario where a new case is created in Salesforce. If the case priority is High, the workflow needs to create a matching incident in ServiceNow and notify the on-call engineer via Teams.

Built as a Logic App, this becomes four steps: a trigger firing when a new record is created via the Salesforce connector, a Condition action checking whether Priority equals High, a Create Record action against the ServiceNow connector if true, and a Post Message action through the Microsoft Teams connector. All four steps are entirely visual, each with its own configurable retry policy, with no custom HTTP client code written for either Salesforce or ServiceNow - the connectors handle authentication and API specifics automatically.

Logic Apps: Problem Scenario and Solving Strategy

The problem: a partner integration needs to pull new orders from an external partner's REST API every 15 minutes, check each order against three different validation rules, and if valid, insert it into Azure SQL - if invalid, send an email to the operations team with the specific reason. The integration needs to be reviewable by a non-technical operations lead, and built quickly, since this is a short-term partner relationship.

The strategy, step by step:

  • First, recognize the shape of this problem - scheduled polling, simple sequential validation, a database write, a conditional notification - this is connector-heavy orchestration with straightforward logic, not complex custom computation.

  • Second, use a Logic App with a Recurrence trigger set to 15 minutes. Third, add an HTTP action to call the partner's REST API and retrieve new orders.

  • Fourth, use a For Each loop over the returned orders, with three Condition actions checking each validation rule in sequence.

  • Fifth, branch on the combined result - valid orders go through a SQL connector action inserting a row into the Orders table, invalid orders trigger an Outlook or Office 365 connector action sending an email with the specific failed rule included in the message body.

  • Sixth, configure a retry policy on the HTTP action specifically, exponential backoff with 3 retries, in case the partner's API is briefly unavailable.

  • Finally, the non-technical reviewability requirement is satisfied inherently by the visual designer, requiring no additional work for that specific requirement.

Service 2: Azure Function Apps

Azure Function Apps provide serverless compute that runs a specific piece of C#, or other language, code in response to a trigger, billed per execution rather than for a server sitting idle. This was covered in more depth in an earlier post on this blog.

Think of a specialist called in for one specific job, paid only for the time actually worked, rather than a full-time employee sitting at a desk waiting for something to do. The specialist shows up when triggered, does the specific task, and leaves, with no idle overhead.

Function Apps fit genuinely complex logic, custom validation with many conditions, algorithms too sophisticated for a visual designer's expression language, and anywhere real unit testing of the logic matters.

Function Apps' strengths include real C# with full language power and genuine testability, six trigger types covering HTTP, Timer, Service Bus, Blob, Queue, and Event Grid, bindings that eliminate boilerplate connection code, and Durable Functions handling workflows beyond the 10-minute execution limit. The weaknesses include no built-in visual retry configuration, requiring retry logic to be written explicitly in code, commonly with Polly, less transparency to a non-developer reviewer, and more upfront setup than dragging in a connector.

Function Apps: A Real Example

[Function("ValidateAndTransformOrder")]
public async Task Run(
    [ServiceBusTrigger("incoming-orders")] string message,
    ILogger log)
{
    var order = JsonSerializer.Deserialize<RawOrder>(message);

    // Complex validation logic - genuinely easier to
    // read, test, and maintain as real code than as a
    // Logic App expression chain
    if (order.Amount <= 0 || order.Amount > 100000)
        throw new ValidationException("Amount out of range");

    if (!IsValidSku(order.ProductCode))
        throw new ValidationException("Unknown product code");

    var transformed = new Order
    {
        Id = order.ExternalId,
        Amount = order.Amount,
        NormalizedSku = NormalizeSkuFormat(order.ProductCode),
        ProcessedAt = DateTime.UtcNow
    };

    await SaveToDbAsync(transformed);
    log.LogInformation("Order {Id} processed successfully", transformed.Id);
}
Enter fullscreen mode Exit fullscreen mode

Function Apps: Problem Scenario and Solving Strategy

The problem: incoming order data from Service Bus needs to go through a genuinely complex transformation - reconciling product codes against three different SKU formats used historically, applying a multi-step pricing adjustment algorithm with several conditional tiers, and validating against business rules that involve checking multiple related fields together. The team needs to unit test this logic directly, since a bug here has real financial impact.

The strategy, step by step:

  • First, recognize this is not connector-heavy orchestration - it's genuinely complex, multi-step business logic that needs to be read, tested, and maintained as real code, which immediately points to Function Apps over Logic Apps.

  • Second, use a Function App with a Service Bus Trigger, consuming messages from the incoming-orders queue directly, since Part 1 already covered why Service Bus fits this scenario upstream, given ordering and reliability matter for order data.

  • Third, structure the transformation logic as a separate, independently testable class, such as OrderTransformer, rather than inline in the function itself, since this is what actually enables genuine unit testing.

  • Fourth, write xUnit tests directly against OrderTransformer, covering each SKU format, each pricing tier boundary, and each validation rule combination - something not realistically possible against a Logic App's visual workflow.

  • Fifth, use Polly for explicit retry logic around any external call within the transformation, such as an external SKU lookup service, since Function Apps don't get this for free the way Logic Apps' connectors do.

  • Finally, on successful transformation, complete the Service Bus message; on failure, let it retry per Service Bus's own retry policy, eventually dead-lettering after max attempts for manual investigation.

Durable Functions: State Management Beyond a Single Execution

A Durable Function is a Function App extension that lets you write long-running, stateful workflows in code - workflows that can span minutes, hours, or even months, well beyond a regular function's execution time limit. Durable Functions automatically checkpoints progress to Azure Storage, meaning the workflow's state survives restarts, scaling events, or the host process recycling entirely.

Think of a video game's save file. A regular Function App is like a game with no save feature - if you close it, all progress is lost, you start over from the beginning. Durable Functions automatically saves your progress at each checkpoint, so if the process restarts, equivalent to your console crashing, the workflow resumes exactly where it left off, rather than starting over.

There are three function types in Durable Functions.

  • The Orchestrator Function coordinates the overall workflow, calling Activity Functions in sequence or in parallel, handling retries and timeouts, with its own execution state automatically checkpointed.

  • The Activity Function does the actual work - calling an external API, querying a database, running a calculation - and each one is a normal, stateless function under the hood.

  • The Client Function starts a new orchestration instance, typically an HTTP trigger that kicks off the workflow and returns an instance ID for checking status later.

// Client function - starts the workflow
[Function("StartOrderWorkflow")]
public async Task<HttpResponseData> StartWorkflow(
    [HttpTrigger] HttpRequestData req,
    [DurableClient] DurableTaskClient client)
{
    var order = await req.ReadFromJsonAsync<Order>();

    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
        "ProcessOrderOrchestrator", order);

    return client.CreateCheckStatusResponse(req, instanceId);
}

// Orchestrator function - coordinates the workflow
[Function("ProcessOrderOrchestrator")]
public static async Task RunOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var order = context.GetInput<Order>();

    // Sequential activity calls - each one checkpointed
    var validated = await context.CallActivityAsync<bool>(
        "ValidateOrder", order);

    if (!validated)
        throw new Exception("Order validation failed");

    var paymentResult = await context.CallActivityAsync<string>(
        "ChargePayment", order);

    // A durable TIMER - the orchestrator can genuinely
    // "sleep" for hours or days without consuming any
    // compute during the wait, unlike Task.Delay
    await context.CreateTimer(
        context.CurrentUtcDateTime.AddHours(24), CancellationToken.None);

    var confirmed = await context.CallActivityAsync<bool>(
        "SendConfirmationAndAwaitAck", order);

    // Fan-out/fan-in - run several activities in
    // PARALLEL, then wait for all to complete
    var tasks = new List<Task>
    {
        context.CallActivityAsync("UpdateInventory", order),
        context.CallActivityAsync("NotifyWarehouse", order),
        context.CallActivityAsync("UpdateAnalytics", order)
    };
    await Task.WhenAll(tasks);
}

// Activity function - the actual work, stateless
[Function("ValidateOrder")]
public static bool ValidateOrder([ActivityTrigger] Order order)
{
    return order.Amount > 0 && !string.IsNullOrEmpty(order.ProductCode);
}
Enter fullscreen mode Exit fullscreen mode

The checkpointing works like this: every time an orchestrator awaits an activity or a timer, its current execution state is saved to Azure Storage automatically. If the process crashes or restarts mid-workflow, the orchestrator function actually replays from the beginning when it resumes - but any activity call that already completed simply returns its already-saved result instantly instead of re-executing, so the orchestrator effectively fast-forwards back to exactly where it left off. This replay behavior is why activity functions, the actual work, must be deterministic-safe to call multiple times, or genuinely idempotent, and why non-deterministic operations like DateTime.Now or Guid.NewGuid() should be called through provided context methods rather than directly, inside an orchestrator specifically.

Durable Functions: Problem Scenario and Solving Strategy

The problem: an order approval workflow needs to send a request to a manager for approval, then wait for up to 5 business days for a response. If approved, proceed with fulfillment. If no response within 5 days, escalate to a senior manager automatically. A regular Function App's 10-minute execution limit makes this directly impossible to build as a single running function, and the system needs to survive deployments, restarts, and scaling events without losing track of orders waiting on approval.

The strategy, step by step:

  • First, recognize this is fundamentally a long-running, stateful workflow problem - the multi-day wait alone rules out a regular Function App entirely, and rules out Logic Apps too if genuinely complex branching logic around the escalation needs real code.

  • Second, use Durable Functions specifically for its durable timer capability, since the orchestrator can wait for days without consuming compute the whole time, and survives restarts because its state is checkpointed to storage.

  • Third, build a Client Function, an HTTP trigger, that starts the orchestration when an order needs approval, returning an instance ID.

  • Fourth, build the Orchestrator Function to call an Activity Function that sends the approval request via email or Teams notification to the manager, then use context.WaitForExternalEvent for an "ApprovalReceived" event combined with context.CreateTimer for the 5-day deadline, racing both with Task.WhenAny.

  • Fifth, if the ApprovalReceived external event arrives first, proceed to a ProcessApproval Activity Function.

  • Sixth, if the timer fires first, 5 days passed with no response, call an EscalateToSeniorManager Activity Function instead. Seventh, the manager's actual approval action, such as clicking a link in an email, calls a separate HTTP-triggered function that raises the external event back into the waiting orchestration instance using its instance ID - this is what wakes up the specific waiting workflow.

  • Finally, because state is checkpointed automatically, this entire multi-day wait survives app restarts, deployments, or scaling events with zero custom state-persistence code written by hand.

Logic Apps, Function Apps, or Durable Functions

Choose Logic Apps when the work is connector-heavy orchestration, the branching logic is straightforward, and visual reviewability matters. Choose regular Function Apps when the logic is complex, genuine unit testing is needed, and execution completes well within the 10-minute limit. Choose Durable Functions when the workflow needs to span minutes to months, requires genuine state that survives restarts, needs fan-out and fan-in parallelism with a final aggregation step, or needs to wait for an external event, like a human approval, without holding compute the whole time. Choose Logic Apps and Function Apps together when a Logic App can orchestrate the overall connector-heavy flow, calling out to a Function App as one action for the specific step complex enough to warrant real code - a common, often correct real-world pattern, covered in more depth in an earlier post on this blog comparing the two services directly.

The Complete Picture: Entry Points

A panel interview rarely stops at "which orchestration service" - it typically wants the whole pipeline. Here's what actually triggers this layer in real architectures.

Azure API Management as an entry point means an external caller hits an API exposed through APIM, covered in depth in earlier posts on this blog. APIM validates the caller through a subscription key or OAuth, applies rate limiting, then forwards to a Logic App or Function App as the actual backend. This fits external, internet-facing integrations needing authentication and governance at the edge.

Service Bus as an entry point means a message arrives on a queue or topic, and a Function App with a Service Bus Trigger, or a Logic App's Service Bus connector trigger, picks it up automatically. This fits internal, decoupled, asynchronous processing, where the sender doesn't need to wait for the orchestration to complete.

Event Grid as an entry point means a discrete event fires - a blob uploaded, an Azure resource changed, a custom application event - and Event Grid pushes it directly to a subscribed Function App or Logic App. This fits reacting to something happening, not processing a queued backlog of work.

Timer or Recurrence as an entry point means a scheduled trigger fires on a defined interval, independent of any external event. This fits polling an external system, running a nightly batch job, or periodic cleanup and reconciliation.

Blob Storage as an entry point means a file lands in a specific container, triggering processing directly. This fits file-based integrations, where a partner drops a CSV or an export needs processing on arrival.

The Complete Picture: Exit Points

Azure SQL or Cosmos DB as an exit point means the orchestration's result is persisted, and the actual business record now exists in a queryable store. The choice between SQL and Cosmos DB follows the reasoning covered in an earlier post on this blog - relational, consistent shape versus flexible, fast-changing document data.

Service Bus as an exit point means the orchestration hands off to the next stage rather than the pipeline ending here, publishing a message for a downstream process to pick up - common in multi-stage pipelines where each stage does one focused job.

An external API as an exit point means the orchestration calls out to a partner system - ServiceNow, Salesforce, a third-party vendor API - completing the actual business integration the whole pipeline exists to support.

Application Insights as an exit point applies always, regardless of the above. Every step logs its outcome, success, failure, duration, covered in depth in an earlier post on this blog about KQL and observability. This isn't optional in a well-built pipeline; it's how anyone, including you at 2am, actually knows what happened.

Putting the Whole Picture Together

Here's a realistic end-to-end example, tying Part 1 and Part 2 together completely. An external partner calls an API exposed through APIM, with a subscription key and OAuth validated at the gateway. APIM forwards the validated request to a Logic App. The Logic App orchestrates the flow: it calls a validation step, then hands off the complex transformation to a Function App as one action within the same workflow. The Function App applies business logic, then publishes the transformed result to a Service Bus topic from Part 1, decoupling this pipeline from whatever consumes the result next. A separate Function App, triggered by a Service Bus subscription on that topic, picks up the message, writes the final record to Azure SQL, and calls an external API to notify the partner's system the order was received. Every step along this entire chain logs to Application Insights, so the complete journey of one specific order can be traced end to end using operation_Id, exactly as covered in the KQL monitoring post on this blog.

This is the shape of answer that actually lands well in a panel interview - not naming one service in isolation, but describing how several connect into a coherent, traceable pipeline.

Key Lessons

Logic Apps and Function Apps solve overlapping but genuinely different problems - connector-heavy visual orchestration versus complex, testable custom code.

Durable Functions solve a third, distinct problem - genuine state that survives restarts, workflows spanning minutes to months, and waiting for external events without holding compute the whole time - none of which regular Function Apps or Logic Apps handle natively.

Many real production architectures use both together - a Logic App orchestrating the overall flow, calling into a Function App for the specific step complex enough to warrant real code.

A complete integration pipeline has an entry point, APIM, Service Bus, Event Grid, Timer, or Blob Storage, an orchestration layer, Logic App and/or Function App, and an exit point, SQL/Cosmos DB, Service Bus, or an external API, with Application Insights watching every step regardless.

Describing the full pipeline, not just one isolated service, is what a panel interview is actually listening for when it asks an open-ended architecture question.

The entry point shapes what's realistic downstream - a Service Bus entry implies async, decoupled processing; an APIM entry implies a synchronous external caller waiting for a response.

What's Next

Future parts of this series will cover additional Azure integration topics - deeper API Management patterns, Azure AD and Entra ID authentication flows applied specifically to service-to-service integration scenarios, and more complete end-to-end architecture walkthroughs.

Summary

Logic Apps and Function Apps are the orchestration layer sitting between Part 1's messaging services and wherever the result of an integration actually needs to go. Logic Apps excel at connector-heavy, visually reviewable workflows; Function Apps excel at complex, genuinely testable business logic, and real architectures frequently combine both. The complete picture matters as much as any individual service choice: knowing what typically triggers this layer, and where the result goes afterward, is what turns a list of memorized services into a coherent architecture a panel interviewer can follow from entry to exit.


More from TechStack Blog: Azure: https://www.techstackblog.com/category.html?cat=azure
CS Fundamentals: https://www.techstackblog.com/category.html?cat=cs-fundamentals

Top comments (0)