Azure Functions は、インフラの運用から解放されつつイベント駆動の処理を実装できるサーバーレス基盤として、業務システムの一部を担う場面が増えています。一方で、2024年から2025年にかけてホスティングモデルとプランの構成が大きく変わり、以前の記事や社内資料の前提が通用しなくなっている点が少なくありません。本稿では、2026年時点の Azure Functions を .NET で運用する際に押さえておきたい設計と実装の指針を、実務の観点から整理します。
実行モデルは分離ワーカーが標準
まず前提として、.NET の関数アプリはインプロセスモデルのサポートが終了し、分離ワーカーモデル(isolated worker)へ完全に移行しました。分離ワーカーでは、関数コードがホストとは別のプロセスで動作します。ホストのランタイムに依存しないため、.NET 8、.NET 9、.NET 10 といった新しいランタイムを Functions ホストの更新を待たずに採用でき、依存関係の衝突も避けやすくなります。新規開発はもちろん、既存のインプロセス関数も移行の対象です。
分離ワーカーでは、ミドルウェアや依存性注入を Program.cs で構成します。属性は FunctionName から Function に変わり、HTTP の戻り値も HttpRequestData と HttpResponseData を基本とします。
var builder = FunctionsApplication.CreateBuilder(args);
builder.ConfigureFunctionsWebApplication();
builder.Services
.AddSingleton<IInventoryService, InventoryService>()
.AddApplicationInsightsTelemetryWorkerService()
.ConfigureFunctionsApplicationInsights();
builder.Build().Run();
public class OrderFunctions(ILogger<OrderFunctions> logger, IInventoryService inventory)
{
[Function("ReceiveOrder")]
public async Task<HttpResponseData> ReceiveOrder(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
var order = await req.ReadFromJsonAsync<Order>();
logger.LogInformation("Received order {OrderId}", order?.Id);
var response = req.CreateResponse(HttpStatusCode.Accepted);
await response.WriteAsJsonAsync(new { accepted = true, order?.Id });
return response;
}
}
コンストラクタ経由で依存を受け取り、関数クラス自体は状態を持たない構成にしておくと、テストとスケールの双方で扱いやすくなります。

ホスティングプランの選び方
プランの選択は、コスト特性とコールドスタートの許容度に直結します。2026年時点の主な選択肢は次の三つです。
- Flex Consumption — 実行時間に応じた従量課金を基本としつつ、常時起動インスタンス数(always-ready)を指定してコールドスタートを抑えられる新しいプラン。仮想ネットワーク統合や柔軟なインスタンスサイズ指定に対応し、多くの新規ワークロードの第一候補になります。
- Premium (Elastic Premium) — 事前ウォーム済みインスタンスと VNET 統合を備えた従来型の選択肢。Flex Consumption が要件を満たさない特定のシナリオで検討します。
- Dedicated (App Service プラン) — 既存の App Service リソースを共有したい場合や、常時稼働の予測可能な負荷に向く構成。
コールドスタートが利用者体験に影響する API では、Flex Consumption の always-ready インスタンスを最小限確保する構成が現実的です。バッチやイベント処理中心で遅延に寛容な用途では、純粋な従量課金に寄せてコストを最適化します。
トリガーとバインディング
Azure Functions の設計の中心は、トリガーとバインディングです。トリガーは関数を起動するイベントを表し、HTTP、キュー、タイマー、Event Grid、Event Hubs などが用意されています。バインディングは Blob、Queue、Cosmos DB といった外部リソースとの入出力を宣言的に記述する仕組みで、接続やシリアライズの定型コードを削減できます。
public class TimerFunctions(ILogger<TimerFunctions> logger)
{
[Function("DailyReport")]
public async Task Run(
[TimerTrigger("0 0 9 * * *")] TimerInfo timer,
[BlobInput("reports")] BlobContainerClient container)
{
logger.LogInformation("Generating daily report");
// 集計処理とレポートの書き出し
}
}
バインディングは記述量を抑えられる反面、複雑な条件分岐やトランザクション制御には向きません。標準的な入出力はバインディングに任せ、込み入った制御が必要な箇所は SDK クライアントを直接使う、という切り分けが保守性を高めます。
Durable Functions によるワークフロー
複数の関数にまたがる長時間処理や状態を持つワークフローには、Durable Functions が適しています。オーケストレーター関数が処理の流れを記述し、アクティビティ関数が個々の作業を担います。オーケストレーターの状態は自動的に永続化されるため、途中で中断しても再開でき、明示的な状態管理を書かずに済みます。
代表的なパターンがファンアウト・ファンインです。複数の処理を並列に起動し、すべての完了を待って結果を集約します。
[Function("ProcessOrderOrchestrator")]
public async Task<OrderResult> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var order = context.GetInput<Order>();
// ファンアウト: 各明細の在庫確認を並列実行
var tasks = order.Items
.Select(item => context.CallActivityAsync<bool>("CheckStock", item))
.ToList();
// ファンイン: すべての完了を待って集約
bool[] results = await Task.WhenAll(tasks);
if (results.All(ok => ok))
{
await context.CallActivityAsync("ChargePayment", order);
return new OrderResult(order.Id, "Completed");
}
return new OrderResult(order.Id, "OutOfStock");
}
オーケストレーター関数はリプレイによって実行されるため、決定的でなければならない点に注意します。現在時刻や乱数、直接的な I/O をオーケストレーター内で使うと再実行時に結果がずれます。時刻は context.CurrentUtcDateTime を、外部呼び出しはアクティビティ関数を経由してください。
信頼性を支える冪等性とリトライ
キューや Event Grid のトリガーは、同一メッセージが二度以上配信され得ることを前提に設計します。少なくとも一度は配信される(at-least-once)保証のもとでは、二重実行しても結果が変わらない冪等な処理が不可欠です。注文 ID などの一意キーで処理済みかどうかを判定し、重複を吸収します。
[Function("ProcessPayment")]
public async Task Run(
[QueueTrigger("payments")] PaymentMessage message)
{
// 冪等性キーで重複処理を防ぐ
if (await store.IsProcessedAsync(message.PaymentId))
{
logger.LogInformation("Skipping duplicate {PaymentId}", message.PaymentId);
return;
}
await gateway.ChargeAsync(message);
await store.MarkProcessedAsync(message.PaymentId);
}
リトライは、トリガー側の設定と、外部呼び出しに対するアプリケーション側の再試行を分けて考えます。一時的な失敗が続くメッセージは、規定回数を超えるとポイズンメッセージとしてデッドレターキューへ退避されます。デッドレターは放置せず、定期的に監視し、再投入や原因調査の運用フローを用意しておきます。処理の順序や重複除去が重要な場合は、Service Bus のセッションや重複検出機能の利用も検討します。
セキュリティと可観測性
接続文字列や API キーをアプリ設定に直書きする運用は避け、マネージド ID を基本とします。関数アプリにシステム割り当て ID を付与し、Storage や Cosmos DB、Key Vault へのアクセスを Azure RBAC で許可すれば、シークレットそのものを保持せずに認証できます。どうしても値の保管が必要な場合は Key Vault に格納し、アプリ設定から Key Vault 参照で解決します。
var credential = new DefaultAzureCredential();
var secretClient = new SecretClient(
new Uri("https://my-vault.vault.azure.net/"), credential);
KeyVaultSecret secret = await secretClient.GetSecretAsync("ExternalApiKey");
可観測性は Application Insights を通じて確保します。分離ワーカーでは前述のとおりワーカー向けのテレメトリを明示的に登録します。ログには注文 ID や相関 ID などの構造化プロパティを付与しておくと、失敗した要求の追跡や特定ユーザーの挙動調査が容易になります。分散トレースを有効にしておけば、トリガーからバインディング、外部呼び出しまでの経路を一連の流れとして把握できます。
設計上の指針
最後に、個々の技術要素を貫く設計方針をまとめます。関数は小さく、単一の責務に絞ります。一つの関数に複数の処理を詰め込むより、キューや Durable Functions で疎結合につなぐほうが、変更容易性とスケール特性の両面で有利です。関数はステートレスに保ち、状態はストレージやデータベース、Durable Functions のオーケストレーションに委ねます。
また、コールドスタート対策は依存関係の軽量化から始めます。HttpClient などの重いオブジェクトは依存性注入でシングルトンとして共有し、起動時の初期化を最小化します。それでも遅延が問題になる場合に、always-ready インスタンスの確保を検討する、という順序が費用対効果に見合います。関数のタイムアウトやログレベルはプランと用途に応じて host.json で明示的に設定し、想定外の長時間実行や過剰なログ出力を防ぎます。
エンハンスド株式会社では、Azure Functions を用いたサーバーレスアーキテクチャの設計から、既存システムの分離ワーカーモデルへの移行、Durable Functions によるワークフロー実装、可観測性とコスト最適化までを一貫して支援しています。サーバーレス化の適否の見極めや実装方針の検討でお困りの際は、お気軽にご相談ください。
このテーマの全体像は .NET モダナイゼーション完全ガイド にまとめています。
この記事をシェア
関連記事

【Ver1.0到達🎉】ReactネイティブエンジンJsxCoreが正式版をリリース!同時にAstroにC#を持ち込むAstroSharpを発表
先日、 ASP.NET Core のビューを JSX/TSX で書き、React/Preact でレンダリングする ビューエンジン JsxCore を 前回の記事 で紹介しました。…

【リリース間近】Node.js不要でASP.NET CoreのReactフロント開発が可能に?JsxCoreとは一体なんなのか
フロントに React や TypeScript を使いたいが、そのために Node.js や npm、バンドラーの一式を .NET プロジェクトへ持ち込むのは重い——多くの ASP.NET Core…

G# とは — Go・Kotlin・Swift の書き味を .NET に持ち込むモダン言語
「モダンで簡潔な構文が欲しい。でも .NET のランタイムや資産は手放したくない」——その両方を狙う新しい言語 G#(ジー・シャープ) が登場しました。作者は David Obando 氏で、…
