$npx -y skills add vibeeval/vibecosystem --skill azure-patternsAzure Functions, Cosmos DB modeling, Service Bus patterns, Bicep templates
| 1 | # Azure Patterns |
| 2 | |
| 3 | ## Azure Functions |
| 4 | |
| 5 | ### HTTP Trigger (Node.js v4) |
| 6 | |
| 7 | ```typescript |
| 8 | import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions'; |
| 9 | |
| 10 | app.http('getOrder', { |
| 11 | methods: ['GET'], |
| 12 | authLevel: 'function', |
| 13 | route: 'orders/{orderId}', |
| 14 | handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => { |
| 15 | const orderId = request.params.orderId; |
| 16 | context.log(`Processing order: ${orderId}`); |
| 17 | |
| 18 | try { |
| 19 | const order = await orderService.getById(orderId); |
| 20 | if (!order) { |
| 21 | return { status: 404, jsonBody: { error: 'Order not found' } }; |
| 22 | } |
| 23 | return { status: 200, jsonBody: order }; |
| 24 | } catch (error) { |
| 25 | context.error('Failed to get order', error); |
| 26 | return { status: 500, jsonBody: { error: 'Internal server error' } }; |
| 27 | } |
| 28 | }, |
| 29 | }); |
| 30 | |
| 31 | // Service Bus trigger with retry |
| 32 | app.serviceBusTopic('processOrderEvent', { |
| 33 | topicName: 'order-events', |
| 34 | subscriptionName: 'order-processor', |
| 35 | connection: 'ServiceBusConnection', |
| 36 | handler: async (message: unknown, context: InvocationContext) => { |
| 37 | const event = message as OrderEvent; |
| 38 | context.log(`Processing event: ${event.type} for order ${event.orderId}`); |
| 39 | |
| 40 | await processEvent(event); |
| 41 | }, |
| 42 | }); |
| 43 | ``` |
| 44 | |
| 45 | ### Durable Functions (Orchestrator) |
| 46 | |
| 47 | ```typescript |
| 48 | import * as df from 'durable-functions'; |
| 49 | |
| 50 | df.app.orchestration('orderWorkflow', function* (context) { |
| 51 | const orderId = context.df.getInput() as string; |
| 52 | |
| 53 | // Step 1: Validate order |
| 54 | const order = yield context.df.callActivity('validateOrder', orderId); |
| 55 | |
| 56 | // Step 2: Reserve inventory (with retry) |
| 57 | const retryOptions = new df.RetryOptions(5000, 3); // 5s interval, 3 attempts |
| 58 | yield context.df.callActivityWithRetry('reserveInventory', retryOptions, order); |
| 59 | |
| 60 | // Step 3: Charge payment |
| 61 | yield context.df.callActivity('chargePayment', order); |
| 62 | |
| 63 | // Step 4: Wait for shipping confirmation (with timeout) |
| 64 | const deadline = new Date(context.df.currentUtcDateTime.getTime() + 24 * 60 * 60 * 1000); |
| 65 | const shippingEvent = context.df.waitForExternalEvent('shippingConfirmed'); |
| 66 | const timeout = context.df.createTimer(deadline); |
| 67 | |
| 68 | const winner = yield context.df.Task.any([shippingEvent, timeout]); |
| 69 | if (winner === timeout) { |
| 70 | yield context.df.callActivity('escalateShipping', orderId); |
| 71 | } |
| 72 | |
| 73 | return { orderId, status: 'completed' }; |
| 74 | }); |
| 75 | ``` |
| 76 | |
| 77 | ## Cosmos DB Modeling |
| 78 | |
| 79 | ```typescript |
| 80 | // Partition key design: use tenant/user ID for multi-tenant |
| 81 | interface OrderDocument { |
| 82 | id: string; // Unique document ID |
| 83 | partitionKey: string; // = tenantId (good distribution) |
| 84 | type: 'order'; // Discriminator for polymorphic container |
| 85 | customerId: string; |
| 86 | items: OrderItem[]; // Embed frequently accessed together |
| 87 | total: number; |
| 88 | status: string; |
| 89 | createdAt: string; |
| 90 | _etag?: string; // Optimistic concurrency |
| 91 | } |
| 92 | |
| 93 | // Optimistic concurrency with ETags |
| 94 | async function updateOrder(order: OrderDocument): Promise<void> { |
| 95 | const container = cosmosClient.database('shop').container('orders'); |
| 96 | try { |
| 97 | await container.item(order.id, order.partitionKey).replace(order, { |
| 98 | accessCondition: { type: 'IfMatch', condition: order._etag }, |
| 99 | }); |
| 100 | } catch (error) { |
| 101 | if (error.code === 412) { |
| 102 | throw new ConflictError('Order was modified by another process'); |
| 103 | } |
| 104 | throw error; |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | // Change feed processor for event-driven updates |
| 109 | const changeFeedProcessor = cosmosClient |
| 110 | .database('shop') |
| 111 | .container('orders') |
| 112 | .getChangeFeedProcessorBuilder('orderProcessor') |
| 113 | .setFeedProcessorOptions({ startFromBeginning: false, maxItemCount: 25 }) |
| 114 | .setLeaseContainer(leaseContainer) |
| 115 | .setProcessChanges(async (changes, context) => { |
| 116 | for (const doc of changes) { |
| 117 | await publishEvent({ type: 'order.updated', data: doc }); |
| 118 | } |
| 119 | }) |
| 120 | .build(); |
| 121 | ``` |
| 122 | |
| 123 | ## Service Bus Patterns |
| 124 | |
| 125 | ```typescript |
| 126 | import { ServiceBusClient, ServiceBusMessage } from '@azure/service-bus'; |
| 127 | |
| 128 | const client = new ServiceBusClient(process.env.SERVICEBUS_CONNECTION!); |
| 129 | |
| 130 | // Send with deduplication and scheduling |
| 131 | async function sendOrderEvent(event: OrderEvent): Promise<void> { |
| 132 | const sender = client.createSender('order-events'); |
| 133 | const message: ServiceBusMessage = { |
| 134 | body: event, |
| 135 | messageId: `${event.orderId}-${event.type}-${Date.now()}`, // Dedup key |
| 136 | subject: event.type, |
| 137 | applicationProperties: { priority: event.priority }, |
| 138 | timeToLive: 24 * 60 * 60 * 1000, // 24h TTL |
| 139 | }; |
| 140 | await sender.sendMessages(message); |
| 141 | await sender.close(); |
| 142 | } |
| 143 | |
| 144 | // Receive with sessions (ordered processing per entity) |
| 145 | async function processWithSessions(): Promise<void> { |
| 146 | const receiver = client.acceptSession('order-events', 'order-processor', 'session-1'); |
| 147 | const messages = await receiver.receiveMessages(10, { maxWaitTimeInMs: 5000 }); |
| 148 | |
| 149 | for (const msg of messages) { |
| 150 | try { |
| 151 | await handleM |