Microservices Integration Patterns on Azure Functions
Most integration estates do not fail because someone picked the wrong queue product. They fail because every project rebuilds the same verbs—file drop, route, transform, deliver—with slightly different code, different secrets handling, and no shared ops story.
Microservices integration patterns here means a small set of capability connectors—each with one clear job—that you compose into project-specific flows through configuration, not copy-paste. This post is the map: config-driven Azure Functions connectors that behave like Lego blocks (or Boomi-style connectors you own).
Engineers get contracts and named enterprise patterns. Engineering managers get one scoreboard: how many integrations share versioned artifacts versus how many are one-off snowflakes. Deep implementation of each connector lands in later posts; here we stay at the catalog level—with diagrams you can actually defend in a design review.
Config-driven means: one versioned Function artifact, many deployments, partner variance stays in config—not a fork of the C# for every SFTP partner.
Source of truth for this series: the public reference implementation at github.com/fransiscuss/integration-azure (commit a24bc48 on main when this revision shipped). Connector names, triggers, and config shapes below track that repo—not a fictional catalog. Later deep dives pull real C# and YAML from the same tree.
What microservices integration patterns means here
Stop rebuilding partner plumbing. Package integration verbs as small connectors and compose them like Lego with configuration.
If you have ever shipped a game engine plus mods, or an npm package plus appsettings, you already know the shape:
- The engine (template repo) ships the connectors once—SFTP pick-up, HTTP facade, transform, and the rest.
- Each new partner is mostly a mod config: routes, maps, schedules, Key Vault refs—not a fork of Unity.
- The message bus is the job queue between steps.
- Claim-check is sending a link to a blob instead of stuffing a 200 MB file through chat.
Enterprise integration is that pain at partner scale. If every class project reimplemented “upload CSV → parse → call REST” with different logging and secrets, you would hate senior year. Production does the same thing with higher blast radius.

Why I use config-driven Azure Functions integration
| Model | What you change per project | What you pay later |
|---|---|---|
| Bespoke per partner | Code, pipelines, logging, security reviews | N× maintenance; every CVE is a scavenger hunt |
| Central iPaaS only | Flows inside the vendor | License + lock-in; hard custom edges |
| Config-driven Functions | Config, Key Vault refs, route/map bindings | One shared release train; variance stays declarative |
Managed Azure still owns the spine: messaging, storage, identity. Connectors own the domain-shaped verbs vendors never ship exactly your way.
Architecture you can draw on a whiteboard
Reference host: .NET isolated Azure Functions—one trigger per connector. Container Apps or AKS are fine if that is already your standard; they are not my default story here.
| Piece | Role |
|---|---|
| Service Bus | Small pipeline envelopes (peek-lock, DLQ, scheduled redelivery) |
| Blob Storage | Large payloads + YAML/JSON maps (claim-check) |
| Cosmos DB | Idempotency ledger + optional ordering gate |
| Managed identity | Access Azure resources—no connection strings next to maps |
| Event Grid | Edge when ingress is event-shaped |
| APIM / Front Door | Product edge in front of Facade |
Deployment rule I keep repeating: one versioned Function artifact × many deployments with different configuration. CI builds packages from the template repo. Project pipelines should not fork C# per partner.
Config I actually ship:
- App settings — binding names, feature toggles
- Key Vault references — secrets
- Blob YAML/JSON — routes, maps, partner profiles
- Azure App Configuration later for flags if needed—not the base pattern
Claim-check and the pipeline envelope
Connectors share one contract: a small envelope on Service Bus and large content off-bus. That is the Claim-Check pattern—store the payload externally and pass a reference through messaging. In this design the reference is a blobLocation next to CloudEvents-friendly metadata.
{
"specversion": "1.0",
"id": "msg-7f3c2a1e-9b44-4d2f-a1c0-0e8f2b6d1a55",
"type": "integration.pipeline.step",
"source": "connectors/publish",
"time": "2026-08-06T01:12:44Z",
"datacontenttype": "application/json",
"data": {
"correlation": {
"business": {
"publisherId": "partner-acme",
"interfaceId": "nightly-inventory",
"fileId": "INV_20260806_0112.csv"
}
},
"blobLocation": {
"container": "pipeline-payloads",
"path": "acme/nightly-inventory/2026/08/06/msg-7f3c2a1e.csv",
"contentType": "text/csv"
},
"step": "published"
}
}
Tracing note: carry W3C traceparent on the transport (Service Bus application properties / HTTP headers)—not nested under business correlation in data. Business keys stay in data.correlation.business.
On-call tip that saves arguments: separate W3C traceparent (distributed tracing) from business correlation (publisher / interface / file id). Ops and partner support query different axes without conflating them.
Facade vs API gateway — direction matters

| Layer | Owns |
|---|---|
| APIM / Front Door | Throttling, quotas, product policies |
| Facade (HTTP Function) | Route match, auth handoff, schema validation, envelope mapping, response shape |
When Event Grid wins vs Service Bus after Facade: choose Event Grid for event-shaped fan-out to many Azure-native subscribers. Choose Service Bus when you need a work queue—peek-lock, competing consumers, scheduled redelivery, and a first-class dead-letter queue. Config picks the path; exclusive OR per interface, not always both.
Facade is the config-driven application edge. Do not invert the mental model into “Facade talks outward to APIM.” For the deep dive on that edge, see Building a Config-Driven API Facade on Azure Functions.
routes:
- name: submit-order
match: { method: POST, path: /v1/orders }
auth: { requiredScopes: ["orders.submit"] }
validate: { schemaRef: blob://config/schemas/order-submit.v1.json }
map:
cloudEventType: integration.order.submitted
busEntity: sbq-orders-in
idempotency: { header: Idempotency-Key, ttlHours: 24 }
Enterprise patterns → eight families, thirteen connectors
Managers hear a short list of reusable verbs. Engineers ship eight capability families as thirteen versioned Function apps—verbs for slides, catalog IDs for repos.
| Family | Connectors | Job |
|---|---|---|
| API ingress | facade |
HTTP edge: validate, map, emit envelope |
| SFTP ingress | publish |
Poll SFTP, stage blob, publish envelope |
| Routing | subscription |
Content-based when/then/otherwise |
| Inbound prep | preprocessing, decompress |
Normalize / unpack |
| Transformation | transformation |
CSV / JSON / XSLT / HTTP maps |
| Outbound prep | compress, postprocessing |
Pack / partner finishers |
| SFTP egress | sender, sequence, delivery |
Ordered push + completion |
| HTTP egress | apidelivery, webhookdelivery |
Paged REST + webhooks |
PGP and archive steps sit as adapters on prepare/transform edges. Resist inventing connector fourteen when a config profile on transformation or apidelivery would do.
Example compositions
- Inbound file:
publish→ decompress → transformation → subscription → domain - Inbound HTTP: Client → APIM/FD → facade → Service Bus → subscription → …
- Outbound: domain → transformation → compress → (sequence?) → sender or apidelivery
The chain is not sacred. The invariant is the same versioned artifact with different configuration across projects.
Enterprise integration patterns → repo realization
Classic EIP names, not proprietary product terms—mapped to the connectors in this catalog:
| Integration pattern | Repository realization |
|---|---|
| API Gateway / Facade | Config-driven HTTP ingress in facade |
| Canonical message model | Pipeline envelope + CloudEvents metadata |
| Claim-check | Service Bus carries blobLocation; payloads stay in Blob |
| Polling consumer | Scheduled SFTP pickup in publish |
| Content-based router | Declarative when/then/otherwise in subscription |
| Message translator | CSV/JSON/XSLT/HTTP in transformation |
| Idempotent receiver | Stable message IDs, Cosmos ledger, Facade keys |
| Dead-letter channel | Native Service Bus dead-lettering |
| Resequencer | Cosmos-backed ordering gate in sequence |
| Protocol adapter | SFTP, REST, webhook, PGP, archive connectors |
Reliability language for design reviews
| Term | Meaning here |
|---|---|
| Delivery | At-least-once from Service Bus and Function triggers—not exactly-once transport |
| Effect | Effectively-once business outcome via deterministic IDs, idempotent writes, Cosmos ledger |
| Retries | Broker-scheduled redelivery—not Thread.Sleep while holding a peek-lock |
| Poison | After the retry budget → Service Bus dead-letter queue with a filterable reason |
If you market “exactly-once” to stakeholders, you lose trust the first time a network blip redelivers a message. Market the ledger and the DLQ playbook instead.
Ordinary connectors scale as competing consumers. Ordering is a feature flag on an interface—you pay concurrency and head-of-line blocking only when the partner contract requires it.
Scoreboard: is this a platform or a folder of repos?
| Role | Healthy signal |
|---|---|
| Product | Next partner is mostly config + UAT, not greenfield Functions |
| Eng manager | One template repo; SLOs; DLQ runbooks; kill switches per connector |
| Engineer | Project logic lives in versioned config and small extension points—not forks |
Metrics I actually care about:
- Percent of partner onboardings with zero new connector code
- Mean time to roll a security patch across deployments
- Dead-letter rate by connector and config profile
- Count of forked repos still claiming to be “the SFTP service”
Trade-offs I accept on purpose
| Choice | You gain | You accept |
|---|---|---|
| At-least-once | Simple brokers, horizontal scale, honest failures | You must design idempotency; duplicates happen |
| Config compatibility | Many projects on one artifact train | Schema breaks need versioning discipline |
| Ordering | Partner sequence contracts | Lower concurrency; head-of-line risk |
| Shared releases | One CVE fix lifts many interfaces | Bad shared release has blast radius—canary and config freeze matter |
This is not a full ESB revival. Domain services stay domain services. It is not “Functions for everything”—three quiet integrations a year may win with a smaller shared library. It is not a substitute for security review; shared crypto and edge connectors concentrate risk usefully, and you still rotate keys and classify data.
Rules that keep the model honest
- Partner endpoints, maps, schedules, crypto profiles → config change
- New capability or shared fix → code change
- Config is PR’d and promoted like code
- Secrets never sit beside maps
- A “just this once” fork is a platform failure—extract an extension point
- Envelope or map schema changes are versioned events, not silent renames
Structural mapping lives in transformation. Pricing, eligibility, and workflow decisions live in domain services. Connectors fail closed on contract and security errors; they should not silently “fix” business outcomes.
FAQ
What is config-driven Azure Functions integration in plain language?
Reusable integration capabilities—ingress, route, transform, deliver—deployed as separate Function apps and composed with messaging and configuration, instead of rebuilding the same plumbing in every project.
How is this different from Boomi or another iPaaS?
Same connector-and-map product metaphor. You own the code, runtime, and data path on Azure. Some teams mix both: iPaaS for commodity SaaS glue, owned connectors for regulated or high-volume edges.
Why Azure Functions instead of Container Apps or AKS?
Functions match one-trigger connectors, competing consumers on Service Bus, and sparse or bursty partner traffic without forcing a full cluster ops model. Use AKS or Container Apps when the organisation already runs everything there.
Is delivery exactly-once?
No. The platform is at-least-once. Exactly-once effects come from deterministic IDs, idempotent handlers, and the Cosmos ledger—not from pretending the broker never redelivers.
Where should business rules live?
Not inside an SFTP sender or a blind transformer. Keep structural mapping in transformation; keep pricing and eligibility in domain services.
Where is the reference code?
In fransiscuss/integration-azure on GitHub: one folder per connector under functions/, shared libraries under shared/, and composition via integrations/*.yaml. This post is the map; the repo is the buildable template.
Closing
Strong microservices integration patterns on Azure are less about drawing more boxes and more about refusing to rewrite SFTP, routing, transform, and HTTP egress for the fifteenth time. Start with the eight capability families and thirteen connectors. Publish the envelope contract. Separate traces from business correlation. Put claim-check on the bus. Design for at-least-once with a ledger and a DLQ. Promote config like code.
When you need the HTTP edge in depth, start with the Facade API microservice on Azure. For reliable messaging mechanics, see the Service Bus peek-lock guide.