モダナイゼーションの進め方
技術選定調査レポート: モダンアーキテクチャによる再構築
対象: 未来明るく電力基幹システム(VB6 + COBOL, 596プログラム, 約50万行) 調査日: 2026-06-16 前提: .NET をコアスタックとして採用(04-playbook.md Pattern A)
目次
1. バッチ処理フレームワーク
1.1 未来明るく電力のバッチ処理の特性
現行システムのバッチ処理を整理すると:
| 特性 | 値 |
|---|---|
| バッチプログラム数 | ~400本(パターンA/B/C/D) |
| 実行頻度 | 日次数回 + 月初集中 |
| データ規模 | 数万〜数十万レコード/回 |
| 処理パターン | 直列パイプライン(BAT で10-17本を順次起動) |
| 実行時間 | 要確認(ログ調査で判明予定) |
| 外部連携 | EDI受信後のトリガー実行 |
重要な特性: 未来明るく電力のバッチは「大規模分散処理」ではなく「中規模な定型処理の順次実行」である。Spark や Hadoop 級のスケーラビリティは不要。
1.2 候補技術の比較
A. Hangfire(推奨 - 第1候補)
| 項目 | 評価 |
|---|---|
| 概要 | .NET 向けバックグラウンドジョブ処理。DB永続化ダッシュボード付き |
| 学習コスト | 低 — NuGet追加 + 数行のコードで即動作 |
| .NET相性 | 最高 — ASP.NET Core にネイティブ統合 |
| 保守性 | 高 — Web ダッシュボードでジョブ状態が可視化 |
| ライセンス | OSS (LGPL) + Pro版(有償)。Proはバッチ処理に便利な機能あり |
| 適合度 | 高 |
// Hangfire でのバッチ処理例(CHP060TB.BAT の置換)
public class GridBatchOrchestrator
{
// 日次バッチ: CRY125S → CRY125 → VRY811B → ... → CRY413
public void ExecuteDailyBatch()
{
var batchId = BatchJob.StartNew(batch =>
{
var job1 = batch.Enqueue<ICro125SService>(s => s.ExecuteAsync());
var job2 = batch.ContinueJobWith<ICro125Service>(job1, s => s.ExecuteAsync());
var job3 = batch.ContinueJobWith<IVro811BService>(job2, s => s.ExecuteAsync());
// ... 順次実行
batch.ContinueJobWith<ICro413Service>(job3, s => s.ExecuteAsync());
});
}
}
未来明るく電力に適する理由:
- 1日数回のバッチ → Hangfire のジョブキューで十分
- BATの順次実行 →
ContinueJobWithで等価な依存チェーンが作れる - Web ダッシュボードで実行状況・エラーが一目で確認可能(現行のログファイル確認から大幅改善)
- .NET プロジェクト内で完結(別インフラ不要)
制約:
- Hangfire Pro(バッチ機能)は有償($500/サーバー/年〜)
- OSS版でもRecurringJobとBackgroundJob.ContinueWithで代替可能
B. Quartz.NET
| 項目 | 評価 |
|---|---|
| 概要 | Java Quartz の .NET 移植。エンタープライズ級スケジューラ |
| 学習コスト | 中 — Trigger/Job/Scheduler の概念理解が必要 |
| .NET相性 | 高 — .NET 8+ 対応、クラスタリング可能 |
| 保守性 | 中 — ダッシュボードなし(別途構築 or CrystalQuartz等のOSSアドオン) |
| ライセンス | Apache 2.0(完全OSS) |
| 適合度 | 中 |
未来明るく電力への評価: Cron式による精密なスケジューリングが強み。ただし、未来明るく電力の用途(EDI受信後のトリガー+日次定時)にはやや重厚。ダッシュボードが標準で付かない点が運用上のデメリット。
C. Azure Functions (Timer Trigger) / Azure Durable Functions
| 項目 | 評価 |
|---|---|
| 概要 | サーバーレスFaaS。Timer Triggerで定時実行、Durable Functionsで複雑なワークフロー |
| 学習コスト | 中〜高 — Azure の概念理解、Durable Functions のオーケストレーターパターン |
| .NET相性 | 高 — Microsoft 純正 |
| 保守性 | 高 — Azure Portal で監視・ログ統合 |
| コスト | 従量課金(未来明るく電力の規模なら月数千円程度) |
| 適合度 | 中(Azure採用が確定している場合は高) |
// Azure Durable Functions でのパイプライン例
[FunctionName("GridBatchOrchestrator")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
await context.CallActivityAsync("CRY125S_Execute", null);
await context.CallActivityAsync("CRY125_Execute", null);
await context.CallActivityAsync("VRY811B_Execute", null);
// ...順次実行、自動リトライ・チェックポイント付き
await context.CallActivityAsync("CRY413_Execute", null);
}
未来明るく電力への評価: インフラ管理不要で運用負荷が低い。ただし、Azure へのロックインと、ローカル開発・デバッグのハードルがある。AWS Lambda Durable Functions(2025年12月リリース)も同等機能を提供開始。
D. AWS Step Functions
| 項目 | 評価 |
|---|---|
| 概要 | AWS のワークフローオーケストレーション。JSON/YAML でステートマシン定義 |
| 学習コスト | 中〜高 — Amazon States Language の習得 |
| .NET相性 | 中 — Lambda (.NET) を呼び出す形。ネイティブ統合ではない |
| 保守性 | 高 — ビジュアルワークフロー、CloudWatch連携 |
| 適合度 | 低〜中(AWS採用の場合) |
未来明るく電力への評価: ビジュアルなワークフロー定義が直感的。ただし .NET との統合が間接的で、開発体験はAzure Durable Functionsに劣る。
E. パイプライン系(Airflow / Dagster / Prefect / Temporal)
| ツール | .NET相性 | 学習コスト | 未来明るく電力への適合度 | 備考 |
|---|---|---|---|---|
| Apache Airflow | 低(Python) | 高 | 低 | データエンジニアリング向け。Python必須。未来明るく電力には過剰 |
| Dagster | 低(Python) | 高 | 低 | データアセット管理向け。未来明るく電力のバッチには不適 |
| Prefect | 低(Python) | 中 | 低 | Python-native。シンプルだがC#エコシステムと乖離 |
| Temporal | 中(.NET SDK はベータ) | 高 | 中 | 分散ワークフローに強いが、未来明るく電力の規模には過剰。.NET SDKがまだベータ段階 |
パイプライン系の総評: これらは主にPythonエコシステムのデータパイプライン向けであり、.NETベースの電力事業バッチ処理にはオーバーキル。Airflow/Dagster/Prefect はPython必須のため、C#メインのプロジェクトでは技術スタックが分裂する。Temporalは強力だが .NET SDK がまだベータで、未来明るく電力の「日次数回のバッチ」には機能過剰。
1.3 バッチ処理の推奨
Phase 1-2
Hangfire Pro + ASP.NET Core Worker Service
既存 BAT パイプラインを Hangfire のジョブチェーンに変換 / Web ダッシュボードで運用監視 / SQL Server / PostgreSQL にジョブ状態永続化
Phase 3 以降・Azure移行時
Azure Durable Functions への移行を検討
Hangfire→Durable Functions は比較的容易(ジョブ = Activity 関数) / Azure の監視・アラート統合
2. 需要集約(系統展開)の実装パターン
2.1 未来明るく電力の需要集約の現状
仕様書と解析結果から推定される現行の需要集約の構造:
図を読み込み中…
- 系統展開は COBOL プログラム群(CJK0G1等)で実行
- 設備マスタ SB010 の親子関係を辿って需要量を計算
- COBOL の PERFORM で再帰的に展開(または反復的にレベル別処理)
2.2 系統展開のアルゴリズム
方式1: 再帰ツリー走査(Depth-First)
// 再帰的系統展開 — 直感的だが深い系統構成でスタック問題の可能性
public class GridExpansionService
{
private readonly IGridRepository _gridRepo;
public async Task<List<MaterialRequirement>> ExplodeAsync(
string pointCode, decimal requiredQty, DateTime dueDate)
{
var requirements = new List<MaterialRequirement>();
await ExplodeRecursiveAsync(pointCode, requiredQty, dueDate, 0, requirements);
return requirements;
}
private async Task ExplodeRecursiveAsync(
string parentItem, decimal parentQty, DateTime dueDate,
int level, List<MaterialRequirement> results)
{
var children = await _gridRepo.GetChildrenAsync(parentItem);
foreach (var child in children)
{
var grossReq = parentQty * child.QuantityPer; // 構成数量を乗算
var netReq = grossReq - await GetAvailableStockAsync(child.PointCode);
results.Add(new MaterialRequirement
{
PointCode = child.PointCode,
Level = level + 1,
GrossRequirement = grossReq,
NetRequirement = Math.Max(0, netReq),
DueDate = dueDate.AddDays(-child.LeadTimeDays),
});
// 子が製造品(中間品)なら再帰展開
if (child.FacilityType == FacilityType.Manufactured)
{
await ExplodeRecursiveAsync(
child.PointCode, Math.Max(0, netReq),
dueDate.AddDays(-child.LeadTimeDays),
level + 1, results);
}
}
}
}
方式2: ローレベルコード法(Level-by-Level)— 推奨
// Low-Level Code 法: 需要集約の標準アルゴリズム
// 同一設備が複数の親に使われる場合の二重計算を防ぐ
public class GridLoadService
{
/// <summary>
/// Low-Level Code 法による 需要集約 展開。
/// 各設備の「最下位レベル」を事前計算し、レベル0から順に処理する。
/// 同一設備が複数の親に使われる場合でも正確にNetRequirementを算出。
/// </summary>
public async Task<GridLoadResult> RunGridLoadAsync(
IEnumerable<DemandRecord> demands)
{
// Step 1: Low-Level Code の計算
// 設備が系統ツリーの中で出現する最も深いレベルを事前計算
var lowLevelCodes = await ComputeLowLevelCodesAsync();
var maxLevel = lowLevelCodes.Values.Max();
// Step 2: 需要を設備別に集約
var requirementsByFacility = new Dictionary<string, GrossNetRecord>();
foreach (var demand in demands)
{
AccumulateDemand(requirementsByItem, demand.PointCode, demand.Quantity, demand.DueDate);
}
// Step 3: レベル0(完成品)から順に展開
for (int level = 0; level <= maxLevel; level++)
{
var itemsAtLevel = lowLevelCodes
.Where(kv => kv.Value == level)
.Select(kv => kv.Key);
foreach (var pointCode in itemsAtLevel)
{
if (!requirementsByItem.TryGetValue(pointCode, out var record))
continue;
// Gross → Net 計算(在庫・工事予定残を差し引き)
var netReq = ComputeNetRequirement(record);
if (netReq <= 0) continue;
// 系統構成 下位設備への展開
var children = await _gridRepo.GetChildrenAsync(pointCode);
foreach (var child in children)
{
var childReq = netReq * child.QuantityPer;
AccumulateDemand(requirementsByItem,
child.PointCode, childReq,
record.DueDate.AddDays(-child.LeadTimeDays));
}
}
}
return new GridLoadResult(requirementsByItem);
}
}
方式3: フラット化テーブル(事前展開)
-- 系統構成をフラット化してDBに保持するパターン
-- 展開済みの構成をテーブルに持ち、JOINで一発取得
CREATE TABLE GRID_FLAT (
ParentFacility VARCHAR(20),
ChildFacility VARCHAR(20),
Level INT,
CumulativeQty DECIMAL(18,6), -- 累積構成数量(親からの乗算済み)
LeadTimeDays INT,
PRIMARY KEY (ParentItem, ChildItem, Level)
);
-- 系統展開がSQLの集計クエリに変換できる
SELECT ChildItem, SUM(DemandQty * CumulativeQty) AS TotalRequirement
FROM GRID_FLAT bf
JOIN Demand d ON bf.ParentFacility = d.PointCode
GROUP BY ChildItem;
2.3 方式比較と推奨
| 方式 | 利点 | 欠点 | 未来明るく電力への適合 |
|---|---|---|---|
| 再帰ツリー | 直感的、リアルタイム展開可能 | 深い系統構成でパフォーマンス劣化、同一設備の重複計算 | 中 |
| ローレベルコード法 | 需要集約の標準、重複計算なし、正確 | Low-Level Code の事前計算が必要 | 高(推奨) |
| フラット化テーブル | SQL一発で高速、参照が容易 | 系統構成変更時に再計算必要、ストレージ増加 | 中〜高(参照用に併用可) |
推奨: ローレベルコード法をメインの需要集約計算エンジンとして実装し、参照用にフラット化テーブルを併用する。
推奨構成
- 需要集約計算: Low-Level Code 法(C# で実装)
- 系統構成参照: フラット化テーブル(系統構成変更時にバッチで再計算)
- 系統構成マスタ: 再帰的な親子関係テーブル(正規化されたマスタ)
2.4 電力事業特有の考慮事項
| 考慮事項 | 対応 |
|---|---|
| 系統階層の深さ | 送配電系統は通常3-5階層。ローレベルコード法で十分な性能 |
| 代替供給地点番号 | TARIFF(料金種別)による供給地点番号バリエーション。系統構成マスタで管理 |
| 需給調整方式 | 系統展開結果 → 調整力配分計算(CSBKxx 系の処理) |
| 需要想定の不確実性 | N0TTL/N1TTL/N2TTL の3ヶ月分を段階的に確定。確定度別の計算が必要 |
3. データ変換パイプライン
3.1 未来明るく電力のデータ変換パターンの分析
596本中の主要パターン:
| パターン | 本数 | 現行実装 | データフロー |
|---|---|---|---|
| A: EDIデータ変換 | ~200 | VB6 + COBOL | 固定長ファイル → パース → DB INSERT/UPDATE |
| B: DB間変換 | ~150 | VB6 + COBOL | SELECT → 計算 → INSERT/UPDATE |
| C: 一括更新 | ~50 | COBOL | SELECT * → 同一処理 → UPDATE |
| D: 帳票生成 | ~60 | COBOL | JOIN/集計 → テキスト/CSV |
3.2 ETL vs ELT: 未来明るく電力のケース
| 基準 | ETL | ELT |
|---|---|---|
| 定義 | Extract → Transform → Load | Extract → Load → Transform |
| 変換場所 | ミドルウェア/アプリケーション | データベース内(SQL) |
| 適合するケース | 複雑なビジネスロジック変換、レガシーシステム、データクレンジング | 大量データの集計・分析、クラウドDWH活用 |
結論: 未来明るく電力には ETL が適切
理由:
- 業務ロジックの複雑さ: EDI変換は連携先ごとに異なるフォーマット解析が必要 → C# で表現すべき
- 固定長ファイル解析: バイト位置でのフィールド切り出しはアプリケーションコードの仕事
- データクレンジング: マジックバリュー変換(
"前回"/"今回"→ enum)はビジネスルール - 段階的移行: 既存ロジックの1:1変換はETLの方が追跡しやすい
- データ規模: 数万レコード → ELT のスケーラビリティ優位は不要
ただし、パターンC(一括更新)とパターンD(帳票の集計クエリ)は SQL で完結する処理が多く、部分的に ELT(SQL内で変換)を採用できる。
3.3 データ変換の設計パターン
Pipeline パターン(推奨 - メインパターン)
// Pipeline パターン: EDI変換処理の汎用化
// パターンA(EDIデータ変換)に最適
public interface IPipelineStep<T>
{
Task<T> ExecuteAsync(T input, CancellationToken ct);
}
// EDI処理パイプラインの例(VKNDD0B のモダン版)
public class EdiPipeline<TRecord>
{
private readonly List<IPipelineStep<EdiContext<TRecord>>> _steps = [];
public EdiPipeline<TRecord> AddStep(IPipelineStep<EdiContext<TRecord>> step)
{
_steps.Add(step);
return this;
}
public async Task<EdiResult> ExecuteAsync(Stream inputFile, CancellationToken ct)
{
var context = new EdiContext<TRecord> { InputStream = inputFile };
foreach (var step in _steps)
{
context = await step.ExecuteAsync(context, ct);
if (context.HasError) break; // エラー時は中断
}
return context.ToResult();
}
}
// 使用例: 料金単価データ処理パイプライン
var pipeline = new EdiPipeline<MeterDataRecord>()
.AddStep(new ValidateControlTable()) // RN920 制御テーブル確認
.AddStep(new ParseFixedLengthFile()) // 138byte 固定長解析
.AddStep(new FilterTargetRecords()) // CTL="D", ID="1", JIGYOSHA="2903"
.AddStep(new ClassifyPreviousCurrent()) // KUBUN による前回/今回分類
.AddStep(new UpsertToDatabase()) // HN050 への INSERT/UPDATE/DELETE
.AddStep(new UpdateControlStatus()) // RN920 ステータス更新
.AddStep(new WriteProcessingLog()); // ログ出力
Strategy パターン(連携先別変換に最適)
// 連携先ごとに異なるEDIフォーマットを Strategy で切り替え
public interface IEdiFormatParser
{
string CustomerCode { get; } // "01"=低圧需要家, etc.
IEnumerable<ContractRecord> Parse(Stream input);
}
public class MdmsMeterParser : IEdiFormatParser
{
public string CustomerCode => "01";
public IEnumerable<ContractRecord> Parse(Stream input)
{
// MDMSLP30: 250byte 固定長レコード
// バイト位置: 0-9=供給地点番号, 10-15=数量, ...
using var reader = new StreamReader(input, Encoding.GetEncoding("Shift_JIS"));
// ...
}
}
public class SwitchingEdiParser : IEdiFormatParser
{
public string CustomerCode => "SWITCHING";
public IEnumerable<ContractRecord> Parse(Stream input)
{
// SWREQ21: 可変長レコード
// ...
}
}
// DI で全パーサーを登録、連携先コードで自動選択
services.AddTransient<IEdiFormatParser, MdmsMeterParser>();
services.AddTransient<IEdiFormatParser, SwitchingEdiParser>();
services.AddTransient<IEdiFormatParser, TakusoEdiParser>();
// ... 連携先追加時は新クラスを追加するだけ
MediatR + CQRS パターン(契約/工事処理に適用可能)
// CQRS で契約コマンドと工事照会を分離
// 注意: MediatR は 2025年7月から商用ライセンス化
// Command: 契約データ登録
public record ProcessContractCommand(
string CustomerCode,
Stream EdiData) : IRequest<ContractResult>;
public class ProcessContractHandler : IRequestHandler<ProcessContractCommand, ContractResult>
{
private readonly IEnumerable<IEdiFormatParser> _parsers;
private readonly IContractRepository _contractRepo;
public async Task<ContractResult> Handle(
ProcessContractCommand request, CancellationToken ct)
{
var parser = _parsers.First(p => p.CustomerCode == request.CustomerCode);
var contracts = parser.Parse(request.EdiData);
await _contractRepo.BulkUpsertAsync(contracts, ct);
return new ContractResult(contracts.Count());
}
}
// Query: 契約照会
public record GetContractStatusQuery(string PointCode, DateRange Period)
: IRequest<ContractStatusResult>;
MediatR の商用化について: 2025年7月にMediatR作者のJimmy BogardがLucky Penny Softwareとして商用ライセンスを開始。OSS版は引き続き利用可能だが、商用利用にはライセンス購入が必要。代替として .NET 10 の IServiceProvider ベースで自前のCQRSディスパッチャーを構築する選択肢もある。
3.4 推奨パターンのマッピング
| 処理パターン | 推奨設計パターン | 理由 |
|---|---|---|
| A: EDI変換 | Pipeline + Strategy | フォーマット解析の多様性をStrategyで吸収、処理手順をPipelineで統一 |
| B: DB間変換 | Pipeline or CQRS | 単純な変換はPipeline、複雑な業務ロジックはCQRSで分離 |
| C: 一括更新 | 直接SQL (ELT的) | UPDATE SET ... WHERE 一文で済む処理はSQLに寄せる |
| D: 帳票生成 | Repository + 帳票エンジン | データ取得と帳票レンダリングを分離 |
4. レポート/帳票生成
4.1 未来明るく電力の帳票の特性
| 特性 | 値 |
|---|---|
| 帳票プログラム数 | ~60本(パターンD) |
| 出力形式 | テキストファイル、CSV |
| 処理内容 | DB JOIN/集計 → 固定幅テキスト整形 |
| 代表例 | 需給計画表、需給調整指令リスト、需要集約リスト、月次工事計画 |
| 日本語 | Shift_JIS(UTF-8移行が必要) |
現行の「帳票」は COBOL/VB6 でテキストファイルを生成する処理であり、GUI帳票デザイナーを使った帳票ではない。
4.2 候補技術の比較
A. QuestPDF(推奨 - 第1候補)
| 項目 | 評価 |
|---|---|
| 概要 | .NET 向け PDF 生成。Fluent API でコードファーストに帳票設計 |
| 学習コスト | 低 — C# Fluent API で直感的 |
| .NET相性 | 最高 — ネイティブ .NET ライブラリ |
| 保守性 | 高 — 帳票定義がC#コード = レビュー・バージョン管理可能 |
| ライセンス | Community(無料・OSS)/ Professional / Enterprise |
| 日本語対応 | フォント埋め込みで日本語PDF生成可能 |
| 最新版 | 2026.6.0(ARM64 対応、活発に開発中) |
| 適合度 | 高 |
// QuestPDF による需給計画表の例
Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4.Landscape());
page.Margin(1, Unit.Centimetre);
page.Header().Text("需給計画表")
.FontSize(16).Bold().AlignCenter();
page.Content().Table(table =>
{
table.ColumnsDefinition(cols =>
{
cols.ConstantColumn(80); // 供給地点番号
cols.ConstantColumn(120); // 設備名
cols.RelativeColumn(); // 当月計画
cols.RelativeColumn(); // 翌月計画
cols.RelativeColumn(); // 翌々月計画
});
// ヘッダー行
table.Header(header =>
{
header.Cell().Text("供給地点番号").Bold();
header.Cell().Text("設備名").Bold();
header.Cell().Text("N0(当月)").Bold();
header.Cell().Text("N1(翌月)").Bold();
header.Cell().Text("N2(翌々月)").Bold();
});
// データ行
foreach (var item in supplyPlan)
{
table.Cell().Text(item.PointCode);
table.Cell().Text(item.FacilityName);
table.Cell().Text(item.N0Total.ToString("#,##0"));
table.Cell().Text(item.N1Total.ToString("#,##0"));
table.Cell().Text(item.N2Total.ToString("#,##0"));
}
});
});
}).GeneratePdf("supply-plan.pdf");
未来明るく電力に適する理由:
- コードファースト → 帳票がソースコード管理・テスト可能
- テキスト帳票 → PDF化による品質向上(印刷も可能)
- 高速生成(数千ページ/秒)
- OSS Community 版は10ページ/ドキュメントまで無料。Professional は $699〜
B. FastReport .NET
| 項目 | 評価 |
|---|---|
| 概要 | ビジュアルデザイナー付きの帳票エンジン。RDLCインポート可能 |
| 学習コスト | 中 — デザイナーは直感的だが、複雑なレイアウトは学習必要 |
| .NET相性 | 高 |
| 保守性 | 中 — 帳票定義が XML/バイナリ形式のため、コードレビューしにくい |
| ライセンス | 商用($399〜/開発者) |
| 適合度 | 中 |
未来明るく電力への評価: ビジュアルデザイナーでノンプログラマーでも帳票修正可能な点は利点。ただし、未来明るく電力の帳票はプログラマーが管理するバッチ出力であり、ビジュアルデザイナーの恩恵は限定的。
C. RDLC (Microsoft Report Definition Language Client)
| 項目 | 評価 |
|---|---|
| 概要 | Visual Studio 統合の帳票定義。レガシーだが安定 |
| 学習コスト | 中 |
| .NET相性 | 中 — .NET Framework 時代の技術。.NET Core 対応は制限あり |
| 保守性 | 低 — Microsoft の積極的な開発は終了、レガシー扱い |
| ライセンス | 無料(Visual Studio 付属) |
| 適合度 | 低 |
未来明るく電力への評価: 新規プロジェクトで採用する理由がない。既存資産がない以上、他を選ぶべき。
D. CSV/テキスト出力の汎用化
現行の帳票の多くはCSV/テキスト出力であり、PDF化が必須ではないものもある。
// 汎用 CSV 出力サービス
public class ReportExportService
{
// CsvHelper で型安全なCSV出力
public async Task ExportCsvAsync<T>(
IEnumerable<T> data, Stream output, CsvConfiguration? config = null)
{
config ??= new CsvConfiguration(CultureInfo.GetCultureInfo("ja-JP"))
{
HasHeaderRecord = true,
Encoding = Encoding.UTF8, // Shift_JIS → UTF-8
};
await using var writer = new StreamWriter(output, config.Encoding);
await using var csv = new CsvWriter(writer, config);
await csv.WriteRecordsAsync(data);
}
// PDF出力が必要な場合は QuestPDF
public void ExportPdf<T>(IEnumerable<T> data, string templateName, Stream output)
{
var template = _templateRegistry.Get(templateName);
template.Render(data, output);
}
}
4.3 帳票の推奨構成
PDF帳票
QuestPDF(Community or Professional)
需給計画表、需給調整指令リスト等の定型帳票 / コードファーストで開発・テスト可能
CSV/Excel
CsvHelper + ClosedXML
データエクスポート系の帳票 / ユーザーが Excel で加工する帳票
テキスト
標準 StreamWriter
外部連携用の固定長テキスト出力(EDI送信データ等)
5. マスタメンテ画面
5.1 未来明るく電力のマスタメンテの特性
| 特性 | 値 |
|---|---|
| 画面数 | ~30本(パターンE) |
| 現行技術 | VB6 フォーム |
| 対象テーブル | 177テーブル中のマスタ系 |
| 操作 | 一覧表示、検索、新規登録、更新、削除 |
| ユーザー数 | 要確認(おそらく数名〜十数名) |
| 同時アクセス | 要確認(低〜中程度と推定) |
5.2 UI フレームワーク比較
A. Blazor Server(推奨 - 第1候補)
| 項目 | 評価 |
|---|---|
| 概要 | サーバーサイドで C# を実行、UI 差分を SignalR で配信 |
| 学習コスト | 低 — C# + Razor で完結。JavaScript 不要 |
| .NET相性 | 最高 — Microsoft 純正、.NET 10 で進化継続 |
| CRUD開発効率 | 高 — コンポーネントベースで再利用容易 |
| VB6からの移行 | 最適 — イベント駆動の画面モデルが VB6 に近い |
| デメリット | 常時接続必須(SignalR)、オフライン不可 |
| 適合度 | 高 |
未来明るく電力に適する理由:
- VB6 の「ボタン押下 → サーバー処理 → 画面更新」モデルと非常に近い
- C# 一本で完結 → JavaScript 学習不要
- サーバーサイドレンダリング → 初期表示が速い
- コードとUIが近い → VB6コードのロジック移植が容易
- 社内ツール(常時接続前提)に最適
B. Blazor WebAssembly (WASM)
| 項目 | 評価 |
|---|---|
| 概要 | .NET アセンブリをブラウザで直接実行 |
| 学習コスト | 低 — Blazor Server と同じ開発モデル |
| .NET相性 | 最高 |
| CRUD開発効率 | 高 |
| デメリット | 初期ロード重い(~1.8秒)、.NET ランタイムのダウンロード必要 |
| 適合度 | 中 |
未来明るく電力への評価: オフライン動作が必要な場合に選択。未来明るく電力のマスタメンテは常時DB接続が前提のため、Server の方が適合する。Blazor Server + WASM のハイブリッド(Interactive Server + Auto render mode)も .NET 8+ で利用可能。
C. React + ASP.NET Core API
| 項目 | 評価 |
|---|---|
| 概要 | React フロントエンド + ASP.NET Core バックエンド API |
| 学習コスト | 高 — TypeScript, React, 状態管理, ビルドツール全て習得必要 |
| .NET相性 | 中 — バックエンドのみ .NET |
| CRUD開発効率 | 中 — API設計 + フロントが二重工数 |
| エコシステム | 最大 — React のコンポーネント資産は業界最大 |
| 適合度 | 低〜中 |
未来明るく電力への評価: UIの自由度は最高だが、社内マスタメンテ画面に React のリッチなエコシステムは不要。開発工数が Blazor の1.5〜2倍になる。チームが React 経験者で構成される場合のみ検討。
D. .NET MAUI
| 項目 | 評価 |
|---|---|
| 概要 | クロスプラットフォームデスクトップ/モバイルアプリ |
| 学習コスト | 中 — XAML + C# |
| .NET相性 | 高 |
| CRUD開発効率 | 中 — DataGrid等は充実(Telerik, Syncfusion 等のサードパーティ) |
| デメリット | Web化の利点を享受できない(デプロイ配布、ブラウザ不要等) |
| 適合度 | 低 |
未来明るく電力への評価: VB6 のデスクトップ体験を忠実に再現したい場合にのみ検討。ただし「VB6 → 別のデスクトップアプリ」では近代化の恩恵が限定的。Web 化(Blazor)の方が運用・配布のメリットが大きい。
5.3 CRUD 画面の効率的構築
Radzen Blazor(推奨 - コンポーネントライブラリ)
| 項目 | 評価 |
|---|---|
| 概要 | 110+ Blazor コンポーネント + Visual Studio / Blazor Studio 統合 |
| スキャフォールディング | 強力 — DB接続 → CRUD画面自動生成 |
| コンポーネント | DataGrid, DataList, Form, Chart, Scheduler 等 |
| ライセンス | コンポーネント: OSS(MIT)、Blazor Studio: 商用($249/月〜) |
| .NET対応 | .NET 8 / 9 / 10 対応 |
| DB接続 | MSSQL, PostgreSQL, MySQL, Oracle, SQLite |
| 適合度 | 高 |
推奨ワークフロー(177テーブル分の画面効率生成):
Step 1
CRUD画面を自動生成
Radzen Blazor Studio でDBスキーマから生成
Step 2
画面をカスタマイズ
自動生成された画面をベースに調整
Step 3
業務ロジックを実装
Service 層に実装
Step 4
認証・認可を追加
Microsoft Identity
自動生成で得られるもの:
- DataGrid(ソート・フィルター・ページング付き)
- 新規作成フォーム(バリデーション付き)
- 編集フォーム
- 削除確認ダイアログ
- 検索機能
ABP Framework(大規模な場合の選択肢)
| 項目 | 評価 |
|---|---|
| 概要 | フルスタックの .NET アプリケーションフレームワーク |
| 機能 | 認証・認可、マルチテナント、監査ログ、バックグラウンドジョブ、DDD支援 |
| 学習コスト | 高 — フレームワーク全体の理解に時間がかかる |
| 適合度 | 中 |
未来明るく電力への評価: ABP は非常に包括的だが、学習コストが高い。未来明るく電力の3-5人チームでは、Blazor Server + Radzen コンポーネントで十分。ABP の全機能は過剰。
Syncfusion Blazor
| 項目 | 評価 |
|---|---|
| 概要 | 85+ Blazor コンポーネント。エンタープライズ向け |
| スキャフォールディング | Visual Studio 拡張でスキャフォールディング可能 |
| ライセンス | 商用($999/開発者/年〜)。Community版あり(年商$100万以下は無料) |
| 適合度 | 中〜高 |
未来明るく電力への評価: Radzen とほぼ同等の選択肢。コンポーネント品質は高いが、ライセンス費用を考慮すると Radzen OSS コンポーネント + Blazor Studio の方がコスパが良い。
5.4 177テーブル分の画面を効率的に作る戦略
全177テーブルにCRUD画面が必要なわけではない。優先度で分類する。
Tier 1 ・ 必須画面(約20テーブル)
マスタメンテ + よく使う照会画面
Radzen Blazor Studio で自動生成→カスタマイズ / 例: SB010(設備マスタ), HN050(単価マスタ), 連携先マスタ, 事業者マスタ
Tier 2 ・ 参照画面(約30テーブル)
読み取り専用のデータ確認
汎用 DataGrid コンポーネント(テーブル名をパラメータ渡し) / 自動生成のままで十分
Tier 3 ・ 管理者専用(約20テーブル)
設定・制御系テーブル
汎用 CRUD 画面(メタデータ駆動)
Tier 4 ・ 不要(約107テーブル)
アプリケーション内部テーブル
画面不要(バッチ処理やAPIでのみアクセス)
// メタデータ駆動の汎用CRUD画面(Tier 2-3 向け)
// テーブル名を渡すだけで CRUD 画面を動的に生成
@page "/admin/table/{TableName}"
<h3>@TableName データ管理</h3>
<RadzenDataGrid Data="@data" TItem="Dictionary<string, object>"
AllowFiltering="true" AllowSorting="true" AllowPaging="true">
@foreach (var col in columns)
{
<RadzenDataGridColumn Property="@col.Name" Title="@col.DisplayName"
Type="@col.DataType" />
}
</RadzenDataGrid>
@code {
[Parameter] public string TableName { get; set; } = "";
// メタデータからカラム定義を動的取得
private List<ColumnMetadata> columns = [];
private IEnumerable<Dictionary<string, object>> data = [];
protected override async Task OnParametersSetAsync()
{
columns = await _metadataService.GetColumnsAsync(TableName);
data = await _dataService.GetAllAsync(TableName);
}
}
6. アーキテクチャパターン
6.1 候補の比較
A. Clean Architecture
図を読み込み中…
依存方向: 外側 → 内側のみ(Presentation → Application → Domain ← Infrastructure)
| 項目 | 評価 |
|---|---|
| 利点 | 依存方向が明確、テスタビリティ高、長期保守に強い |
| 欠点 | クラス・ファイル爆発、小さなチームには過剰な抽象化 |
| 学習コスト | 高 — レイヤー間の責務分離の理解に時間がかかる |
| 3-5人チームへの適合 | 低〜中 |
正直な評価: Clean Architecture は「正しい」が、596プログラムを3-5人で移行するプロジェクトでは抽象化のオーバーヘッドが大きい。1つの機能追加に4レイヤー分のファイルを作成する必要があり、開発速度が低下する。
B. Vertical Slice Architecture (VSA)
Feature: 契約データ処理
ProcessOrder.Command.cs
ProcessOrder.Handler.cs
ProcessOrder.Validator.cs
ProcessOrder.Endpoint.cs
Feature: 設備データリセット
ResetFacility.Command.cs
ResetFacility.Handler.cs
ResetFacility.Endpoint.cs
Feature: 設備マスタCRUD
GetItems.Query.cs
CreateItem.Command.cs
UpdateItem.Command.cs
Items.Endpoint.cs
各機能(Slice)が独立。機能間の依存を最小化。
| 項目 | 評価 |
|---|---|
| 利点 | 機能単位で独立、変更影響が局所化、並行開発しやすい |
| 欠点 | 共通処理の重複リスク、大規模化すると構造が見えにくい |
| 学習コスト | 低 — 「1機能1フォルダ」の直感的な構造 |
| 3-5人チームへの適合 | 高 |
正直な評価: VSA は「各プログラムが独立した処理」である未来明るく電力の現行構造と親和性が高い。VB6/COBOLの1プログラムが1 Vertical Slice に自然にマッピングされる。ただし、共通ドメインロジック(系統展開、価格計算等)の配置に別途設計が必要。
C. Modular Monolith(推奨)
Power.Web (Host)
共通認証・認可
共通ミドルウェア
モジュール登録
Power.Cis (料金CIS)
Contracts/
LowVolt/
HighVolt/
Takuso/
Charging/
Masters/
Reports/
Power.Grid (設備管理)
DemandPlan/
MonthlyReset/
GridExpansion/
WorkManagement/
DemandResponsePlan/
Reports/
Power.SharedKernel
Domain/ (共通Entity, ValueObject)
Infrastructure/ (DB, EDI共通)
Common/ (ExeOdbc後継のDB接続層)
モジュール間通信: インプロセスイベント / 共有DB(Phase 1-2)。将来: API / メッセージキュー(Phase 3 で分離可能)。
| 項目 | 評価 |
|---|---|
| 利点 | サブシステム境界が明確、デプロイは1つ、将来の分離が容易 |
| 欠点 | モジュール境界の設計が重要、間違えると巨大モノリスに回帰 |
| 学習コスト | 中 — モジュール分割の設計判断が必要 |
| 3-5人チームへの適合 | 高 |
6.2 DDD は適切か?
結論: フルDDDは過剰。DDDの一部要素を実用的に採用する。
| DDD の要素 | 採用 | 理由 |
|---|---|---|
| Bounded Context | 採用 | 料金CIS と 設備管理のサブシステム境界がそのまま Bounded Context になる |
| Entity / Value Object | 部分採用 | マスタテーブルは Entity、供給地点番号等の複合値は Value Object |
| Aggregate | 不採用 | バッチ処理主体でトランザクション整合性の複雑なパターンが少ない |
| Domain Event | 将来採用 | Phase 3 でモジュール分離する際にイベント駆動を導入 |
| Repository Pattern | 採用 | EF Core の DbContext をラップして DB アクセスを抽象化 |
| Ubiquitous Language | 採用 | テーブル名の暗号性(HN050, KY400)を業務用語に変換する用語辞書 |
// 実用的なDDD: フルDDDのセレモニーを避け、必要な要素だけ使う
// Value Object: 供給地点番号(VB6の ITMNO1+ITMNO2+TARIFF に対応)
public record PointCode
{
public string Prefix { get; } // ITMNO1 (前半5桁)
public string Suffix { get; } // ITMNO2 (後半5桁)
public string TariffCode { get; } // TARIFF (料金種別2桁)
public PointCode(string prefix, string suffix, string tariffCode)
{
// バリデーション: 桁数チェック等
Prefix = prefix;
Suffix = suffix;
TariffCode = tariffCode;
}
public override string ToString() => $"{Prefix}-{Suffix}-{TariffCode}";
}
// Entity: 設備マスタ (SB010 → わかりやすい名前に)
public class Facility // ← "SB010Record" ではなく業務用語で
{
public PointCode Code { get; private set; }
public string Name { get; private set; }
public FacilityType Type { get; private set; } // 製品/中間品/購入品
public int LeadTimeDays { get; private set; }
// ... ビジネスロジックをここに集約
}
6.3 アーキテクチャの推奨
推奨: Modular Monolith + Vertical Slice Architecture(各モジュール内)
理由:
- 料金CIS / 設備管理 のサブシステム境界が明確(共有テーブル11件、うち双方向書込みは1件のみ)
- 3-5人チームでマイクロサービスは運用負荷が高すぎる
- 各モジュール内は VSA で機能単位に整理 → VB6/COBOLプログラムと1:1対応
- 将来的にモジュールを分離してマイクロサービス化する道が残る
- 1つのデプロイ単位 → CI/CD・運用がシンプル
7. 総合推奨
7.1 技術スタック全体像
UI層
Blazor Server(.NET 10)
+ Radzen Blazor Components(OSS) / + Radzen Blazor Studio(CRUD自動生成)
API層
ASP.NET Core Minimal APIs
バッチ処理
Hangfire(Pro) / Worker Service
ジョブスケジューリング・チェーン実行 / 長時間バッチ処理
ビジネスロジック
Vertical Slice Architecture
Pipeline パターン(EDI変換) / Strategy パターン(連携先別処理) / 系統展開: Low-Level Code 法
データアクセス
Entity Framework Core(ORM)
Dapper(パフォーマンスクリティカルなクエリ)
帳票
QuestPDF / CsvHelper / ClosedXML
PDF生成 / CSV出力 / Excel出力
インフラ
SQL Server or PostgreSQL
認証: Microsoft Identity / ログ: Serilog + Seq or Application Insights / CI/CD: GitHub Actions or Azure DevOps
アーキテクチャ
Modular Monolith
料金CIS / 設備管理 をモジュール分離 / 各モジュール内: Vertical Slice Architecture / DDD要素: Bounded Context, Entity, Value Object, Repository
未来明るく電力 モダンスタック(層別)
7.2 決定根拠のまとめ
| 領域 | 選定 | 次点 | 不採用理由 |
|---|---|---|---|
| バッチ処理 | Hangfire Pro | Azure Durable Functions | Airflow/Dagster は Python 必須で技術スタック分裂 |
| 系統展開 | Low-Level Code 法 (C#自前実装) | 再帰ツリー | フラット化テーブルは参照用に併用 |
| データ変換 | Pipeline + Strategy | CQRS (MediatR) | MediatR 商用化のリスク。自前 Pipeline で十分 |
| ETL/ELT | ETL (アプリ側で変換) | 部分的ELT | ELT のスケーラビリティは未来明るく電力規模では不要 |
| 帳票 | QuestPDF + CsvHelper | FastReport | RDLC はレガシー化。FastReport は帳票デザイナー不要なら過剰 |
| UI | Blazor Server + Radzen | React + API | React は学習コスト高。MAUI はWeb化の恩恵なし |
| アーキテクチャ | Modular Monolith + VSA | Clean Architecture | Clean Architecture は3-5人チームにはファイル爆発が負担 |
| DDD | 実用的部分採用 | フルDDD | Aggregate/Domain Event はバッチ主体のシステムには過剰 |
7.3 学習コストの見積もり
| 技術 | 習得期間(経験者前提) | 習得期間(初学者) |
|---|---|---|
| C# / .NET 10 | — | 2-3ヶ月 |
| Blazor Server | 1-2週間 | 1ヶ月 |
| Entity Framework Core | 1-2週間 | 1ヶ月 |
| Hangfire | 数日 | 1-2週間 |
| QuestPDF | 数日 | 1週間 |
| Radzen Components | 1週間 | 2-3週間 |
| Modular Monolith + VSA | 2-3週間 | 1-2ヶ月 |
| 需要集約アルゴリズム | 業務知識依存 | 業務知識依存 |
7.4 コスト見積もり(ライセンス)
| ツール | ライセンス | 年間コスト(概算) |
|---|---|---|
| .NET 10 | 無料 (MIT) | $0 |
| Hangfire Pro | $500/サーバー/年 | $500〜$1,000 |
| QuestPDF Professional | $699/開発者 | $2,100〜$3,500 (3-5名) |
| Radzen Blazor Components | 無料 (MIT) | $0 |
| Radzen Blazor Studio | $249/月/開発者 | $8,964〜$14,940 (3-5名) |
| Syncfusion (代替案) | $999/開発者/年 | $2,997〜$4,995 |
| 合計(推奨構成) | $11,564〜$19,440/年 |
注: Radzen Blazor Studio は初期のCRUD自動生成フェーズ(3-6ヶ月)のみ契約し、その後は解約可能。自動生成後のカスタマイズはOSSのコンポーネントだけで行える。その場合の年間コストは$2,600〜$4,500程度。
参考資料
バッチ処理フレームワーク
- Building a Production-Ready Job Scheduler: Hangfire vs Quartz.NET vs Azure Functions
- Hangfire vs Quartz vs Azure Functions in .NET
- Background Jobs in .NET: Hangfire vs Quartz vs Worker Services
- AWS Lambda announces durable functions
需要集約 / 系統展開
データ変換 / CQRS
- Build Your Own CQRS Dispatcher in .NET 10 (No MediatR)
- CQRS Pattern With MediatR
- ETL vs ELT: Speed, Cost & Cloud Advantage Compared
帳票
UI フレームワーク
- React vs. Blazor: Which Is Better in 2026?
- Blazor vs React (2026)
- Radzen Blazor Studio
- .NET MAUI in 2025: Navigating Production Readiness
アーキテクチャ
- N-Layered vs Clean vs Vertical Slice Architecture (2025)
- Where Vertical Slices Fit Inside the Modular Monolith Architecture
- Modular Monolith Architecture with .NET (ABP.IO)
- .NET 10 Modular Monolith + DDD + VSA boilerplate
- Modular Monolith with DDD (Kamil Grzybek)