@polymorphism-tech/morph-spec 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +314 -1673
- package/LICENSE +72 -72
- package/README.md +515 -516
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +358 -173
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/task-manager.js +429 -0
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/bin/validate.js +369 -0
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-apply.md +221 -158
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -0
- package/content/.claude/commands/morph-proposal.md +122 -101
- package/content/.claude/commands/morph-status.md +86 -86
- package/content/.claude/commands/morph-troubleshoot.md +122 -0
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/checklists/code-review.md +226 -0
- package/content/.claude/skills/checklists/morph-checklist.md +117 -0
- package/content/.claude/skills/checklists/simulation-checklist.md +77 -0
- package/content/.claude/skills/infra/bicep-architect.md +126 -419
- package/content/.claude/skills/infra/container-specialist.md +131 -437
- package/content/.claude/skills/infra/devops-engineer.md +119 -405
- package/content/.claude/skills/integrations/asaas-financial.md +130 -333
- package/content/.claude/skills/integrations/azure-identity.md +142 -309
- package/content/.claude/skills/integrations/clerk-auth.md +108 -290
- package/content/.claude/skills/integrations/resend-email.md +119 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +192 -604
- package/content/.claude/skills/specialists/azure-architect.md +142 -142
- package/content/.claude/skills/specialists/code-analyzer.md +235 -0
- package/content/.claude/skills/specialists/dotnet-senior.md +287 -0
- package/content/.claude/skills/specialists/ef-modeler.md +113 -200
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +126 -245
- package/content/.claude/skills/specialists/ms-agent-expert.md +109 -263
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -197
- package/content/.claude/skills/specialists/standards-architect.md +156 -78
- package/content/.claude/skills/specialists/testing-specialist.md +126 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +191 -1060
- package/content/.claude/skills/stacks/dotnet-blazor.md +210 -588
- package/content/.claude/skills/stacks/dotnet-nextjs.md +154 -402
- package/content/.claude/skills/workflows/morph-replicate.md +213 -0
- package/content/.claude/{commands/morph-clarify.md → skills/workflows/phase-clarify.md} +5 -58
- package/content/.claude/{commands/morph-design.md → skills/workflows/phase-design.md} +16 -86
- package/content/.claude/{commands/morph-setup.md → skills/workflows/phase-setup.md} +9 -17
- package/content/.claude/skills/workflows/phase-tasks.md +164 -0
- package/content/.claude/{commands/morph-uiux.md → skills/workflows/phase-uiux.md} +15 -88
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +378 -242
- package/content/.morph/config/config.template.json +89 -108
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/design-impl.md +37 -0
- package/content/.morph/docs/workflows/fast-track.md +29 -0
- package/content/.morph/docs/workflows/full-morph.md +76 -0
- package/content/.morph/docs/workflows/standard.md +44 -0
- package/content/.morph/docs/workflows/ui-refresh.md +39 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -0
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -0
- package/content/.morph/examples/scheduled-reports/spec.md +267 -0
- package/content/.morph/examples/state-v3.json +188 -0
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +190 -239
- package/content/.morph/hooks/pre-commit-agents.sh +24 -24
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -0
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-framework-blazor-ui.md +359 -0
- package/content/.morph/standards/agent-framework-production.md +410 -0
- package/content/.morph/standards/agent-framework-setup.md +413 -453
- package/content/.morph/standards/agent-framework-workflows.md +349 -0
- package/content/.morph/standards/architecture.md +325 -325
- package/content/.morph/standards/azure.md +605 -379
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/dotnet10-migration.md +520 -494
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/agent.cs +163 -172
- package/content/.morph/templates/clarify-questions.md +159 -0
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -0
- package/content/.morph/templates/contracts/Entities.cs +25 -0
- package/content/.morph/templates/contracts/Queries.cs +74 -0
- package/content/.morph/templates/contracts/README.md +74 -0
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/decisions.md +123 -106
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -0
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/proposal.md +141 -155
- package/content/.morph/templates/recap.md +94 -105
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/simulation.md +353 -0
- package/content/.morph/templates/spec.md +149 -148
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/state.template.json +222 -222
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/tasks.md +257 -235
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-components.md +362 -276
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/CLAUDE.md +150 -442
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/detectors/structure-detector.js +245 -250
- package/docs/README.md +144 -149
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/getting-started.md +301 -302
- package/docs/installation.md +361 -361
- package/docs/templates.md +418 -418
- package/docs/validation-checklist.md +265 -266
- package/package.json +80 -80
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +183 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -0
- package/src/commands/create-story.js +351 -351
- package/src/commands/detect-agents.js +139 -0
- package/src/commands/detect.js +104 -104
- package/src/commands/doctor.js +356 -280
- package/src/commands/generate.js +149 -149
- package/src/commands/init.js +258 -245
- package/src/commands/lint-fluent.js +352 -0
- package/src/commands/rollback-phase.js +185 -0
- package/src/commands/session-summary.js +291 -0
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/task.js +78 -0
- package/src/commands/troubleshoot.js +222 -0
- package/src/commands/update.js +192 -159
- package/src/commands/validate-blazor-state.js +210 -0
- package/src/commands/validate-blazor.js +156 -0
- package/src/commands/validate-css.js +84 -0
- package/src/commands/validate-phase.js +221 -0
- package/src/lib/blazor-concurrency-analyzer.js +288 -0
- package/src/lib/blazor-state-validator.js +291 -0
- package/src/lib/blazor-validator.js +374 -0
- package/src/lib/complexity-analyzer.js +441 -292
- package/src/lib/continuous-validator.js +421 -0
- package/src/lib/css-validator.js +352 -0
- package/src/lib/decision-constraint-loader.js +109 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/learning-system.js +520 -0
- package/src/lib/mockup-generator.js +366 -0
- package/src/lib/recap-generator.js +205 -0
- package/src/lib/state-manager.js +397 -340
- package/src/lib/troubleshoot-grep.js +194 -0
- package/src/lib/troubleshoot-index.js +144 -0
- package/src/lib/ui-detector.js +350 -0
- package/src/lib/validation-runner.js +231 -0
- package/src/lib/validators/architecture-validator.js +387 -0
- package/src/lib/validators/contract-compliance-validator.js +273 -0
- package/src/lib/validators/package-validator.js +360 -0
- package/src/lib/validators/ui-contrast-validator.js +422 -0
- package/src/utils/file-copier.js +179 -139
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- package/content/.claude/commands/morph-costs.md +0 -206
- package/content/.claude/commands/morph-tasks.md +0 -319
- package/content/.claude/skills/specialists/cost-guardian.md +0 -110
- package/content/.claude/skills/stacks/shopify.md +0 -445
- package/content/.morph/config/azure-pricing.json +0 -70
- package/content/.morph/config/azure-pricing.schema.json +0 -50
- package/content/.morph/hooks/pre-commit-costs.sh +0 -91
- package/docs/api/cost-calculator.js.html +0 -513
- package/docs/api/design-system-generator.js.html +0 -382
- package/docs/api/global.html +0 -5263
- package/docs/api/index.html +0 -96
- package/docs/api/state-manager.js.html +0 -423
- package/src/commands/cost.js +0 -181
- package/src/commands/update-pricing.js +0 -206
- package/src/lib/cost-calculator.js +0 -429
|
@@ -1,245 +1,126 @@
|
|
|
1
|
-
# Hangfire Orchestrator
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
batch.Enqueue(() => _reportService.GenerateBatchReportAsync(batchId));
|
|
128
|
-
});
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Estrutura de Job Service
|
|
132
|
-
|
|
133
|
-
```csharp
|
|
134
|
-
// Jobs/DailySalesReportJob.cs
|
|
135
|
-
public interface IDailySalesReportJob
|
|
136
|
-
{
|
|
137
|
-
Task ExecuteAsync(CancellationToken ct = default);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
public class DailySalesReportJob : IDailySalesReportJob
|
|
141
|
-
{
|
|
142
|
-
private readonly IOrderRepository _orderRepository;
|
|
143
|
-
private readonly IReportGenerator _reportGenerator;
|
|
144
|
-
private readonly IEmailService _emailService;
|
|
145
|
-
private readonly ILogger<DailySalesReportJob> _logger;
|
|
146
|
-
|
|
147
|
-
public DailySalesReportJob(
|
|
148
|
-
IOrderRepository orderRepository,
|
|
149
|
-
IReportGenerator reportGenerator,
|
|
150
|
-
IEmailService emailService,
|
|
151
|
-
ILogger<DailySalesReportJob> logger)
|
|
152
|
-
{
|
|
153
|
-
_orderRepository = orderRepository;
|
|
154
|
-
_reportGenerator = reportGenerator;
|
|
155
|
-
_emailService = emailService;
|
|
156
|
-
_logger = logger;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
[AutomaticRetry(Attempts = 3)]
|
|
160
|
-
[Queue("default")]
|
|
161
|
-
public async Task ExecuteAsync(CancellationToken ct = default)
|
|
162
|
-
{
|
|
163
|
-
_logger.LogInformation("Starting daily sales report generation");
|
|
164
|
-
|
|
165
|
-
var yesterday = DateTime.Today.AddDays(-1);
|
|
166
|
-
var orders = await _orderRepository.GetByDateRangeAsync(
|
|
167
|
-
yesterday, yesterday.AddDays(1), ct);
|
|
168
|
-
|
|
169
|
-
var report = await _reportGenerator.GenerateAsync(orders, ct);
|
|
170
|
-
|
|
171
|
-
await _emailService.SendReportAsync(report, ct);
|
|
172
|
-
|
|
173
|
-
_logger.LogInformation("Daily sales report completed. Orders: {Count}", orders.Count);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Registro
|
|
178
|
-
RecurringJob.AddOrUpdate<IDailySalesReportJob>(
|
|
179
|
-
"daily-sales-report",
|
|
180
|
-
job => job.ExecuteAsync(default),
|
|
181
|
-
"0 8 * * *");
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
## Retry e Error Handling
|
|
185
|
-
|
|
186
|
-
```csharp
|
|
187
|
-
[AutomaticRetry(Attempts = 5, DelaysInSeconds = new[] { 60, 300, 900, 3600, 7200 })]
|
|
188
|
-
[Queue("critical")]
|
|
189
|
-
public async Task ProcessPaymentAsync(int orderId)
|
|
190
|
-
{
|
|
191
|
-
try
|
|
192
|
-
{
|
|
193
|
-
await _paymentService.ProcessAsync(orderId);
|
|
194
|
-
}
|
|
195
|
-
catch (PaymentGatewayException ex)
|
|
196
|
-
{
|
|
197
|
-
_logger.LogWarning(ex, "Payment failed for order {OrderId}, will retry", orderId);
|
|
198
|
-
throw; // Rethrow para Hangfire retry
|
|
199
|
-
}
|
|
200
|
-
catch (Exception ex)
|
|
201
|
-
{
|
|
202
|
-
_logger.LogError(ex, "Unexpected error processing order {OrderId}", orderId);
|
|
203
|
-
throw;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
## Dashboard Authorization
|
|
209
|
-
|
|
210
|
-
```csharp
|
|
211
|
-
public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter
|
|
212
|
-
{
|
|
213
|
-
public bool Authorize(DashboardContext context)
|
|
214
|
-
{
|
|
215
|
-
var httpContext = context.GetHttpContext();
|
|
216
|
-
|
|
217
|
-
// Em produção, verificar autenticação/autorização
|
|
218
|
-
if (!httpContext.User.Identity?.IsAuthenticated ?? true)
|
|
219
|
-
return false;
|
|
220
|
-
|
|
221
|
-
return httpContext.User.IsInRole("Admin");
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
## Documentação de Referência
|
|
227
|
-
|
|
228
|
-
- [Hangfire Documentation](https://docs.hangfire.io/en/latest/)
|
|
229
|
-
- [Cron Expressions](https://crontab.guru/)
|
|
230
|
-
- [SQL Server Storage](https://docs.hangfire.io/en/latest/configuration/using-sql-server.html)
|
|
231
|
-
|
|
232
|
-
## Checklist de Jobs
|
|
233
|
-
|
|
234
|
-
- [ ] Job service com interface
|
|
235
|
-
- [ ] Injeção de dependência correta
|
|
236
|
-
- [ ] AutomaticRetry configurado
|
|
237
|
-
- [ ] Queue definida (critical/default/low)
|
|
238
|
-
- [ ] Logging estruturado
|
|
239
|
-
- [ ] CancellationToken propagado
|
|
240
|
-
- [ ] Dashboard protegido
|
|
241
|
-
- [ ] Timezone configurado para recorrentes
|
|
242
|
-
|
|
243
|
-
---
|
|
244
|
-
|
|
245
|
-
*MORPH-SPEC by Polymorphism Tech*
|
|
1
|
+
# Hangfire Orchestrator
|
|
2
|
+
|
|
3
|
+
> **Layer:** 2 | **Load:** on-keyword | **Keywords:** scheduled, job, background, cron, recurring, batch, queue, hangfire
|
|
4
|
+
|
|
5
|
+
Especialista em background jobs, tarefas agendadas e processamento assíncrono com Hangfire.
|
|
6
|
+
|
|
7
|
+
## .NET 10 Compatibility
|
|
8
|
+
- Hangfire.AspNetCore: **>= 1.8.22** (required for .NET 10)
|
|
9
|
+
|
|
10
|
+
## Why Hangfire (not Azure Functions)
|
|
11
|
+
|
|
12
|
+
| Aspect | Hangfire | Azure Functions |
|
|
13
|
+
|--------|----------|-----------------|
|
|
14
|
+
| Cost | Free (in-process) | Pay per execution |
|
|
15
|
+
| Dashboard | Built-in | App Insights separate |
|
|
16
|
+
| Debugging | Local easy | Emulator needed |
|
|
17
|
+
| Complexity | Low | Medium-High |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
```csharp
|
|
24
|
+
// Program.cs
|
|
25
|
+
builder.Services.AddHangfire(config => config
|
|
26
|
+
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
|
|
27
|
+
.UseSimpleAssemblyNameTypeSerializer()
|
|
28
|
+
.UseRecommendedSerializerSettings()
|
|
29
|
+
.UseSqlServerStorage(connString, new SqlServerStorageOptions
|
|
30
|
+
{
|
|
31
|
+
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
|
|
32
|
+
QueuePollInterval = TimeSpan.Zero,
|
|
33
|
+
UseRecommendedIsolationLevel = true,
|
|
34
|
+
DisableGlobalLocks = true
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
builder.Services.AddHangfireServer(options =>
|
|
38
|
+
{
|
|
39
|
+
options.WorkerCount = Environment.ProcessorCount * 2;
|
|
40
|
+
options.Queues = new[] { "critical", "default", "low" };
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
app.UseHangfireDashboard("/hangfire", new DashboardOptions
|
|
44
|
+
{
|
|
45
|
+
Authorization = new[] { new HangfireAuthorizationFilter() }
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Job Types
|
|
50
|
+
|
|
51
|
+
| Type | API | Example |
|
|
52
|
+
|------|-----|---------|
|
|
53
|
+
| **Fire-and-Forget** | `BackgroundJob.Enqueue<T>(...)` | Send email |
|
|
54
|
+
| **Delayed** | `BackgroundJob.Schedule<T>(..., TimeSpan)` | Abandoned cart check |
|
|
55
|
+
| **Recurring** | `RecurringJob.AddOrUpdate<T>(..., cron)` | Daily report |
|
|
56
|
+
| **Continuation** | `BackgroundJob.ContinueJobWith(id, ...)` | Post-import notify |
|
|
57
|
+
|
|
58
|
+
### Common Cron Expressions
|
|
59
|
+
`"0 * * * *"` hourly | `"*/5 * * * *"` every 5m | `"0 8 * * 1-5"` 8am weekdays | `"0 0 1 * *"` monthly
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## CRITICAL: Scoped Repository Pattern
|
|
64
|
+
|
|
65
|
+
> **Ref:** `framework/standards/blazor-efcore.md` — Repository Factory pattern
|
|
66
|
+
|
|
67
|
+
**Hangfire jobs run OUTSIDE the HTTP request.** The scoped DbContext does NOT exist.
|
|
68
|
+
|
|
69
|
+
```csharp
|
|
70
|
+
// ✅ ALWAYS use Factory
|
|
71
|
+
public class OrderProcessorJob(IOrderRepositoryFactory repoFactory, ILogger<OrderProcessorJob> logger)
|
|
72
|
+
{
|
|
73
|
+
[AutomaticRetry(Attempts = 3, DelaysInSeconds = new[] { 60, 300, 900 })]
|
|
74
|
+
public async Task ProcessAsync(Guid orderId, CancellationToken ct)
|
|
75
|
+
{
|
|
76
|
+
await using var repo = repoFactory.CreateScoped(); // Isolated DbContext
|
|
77
|
+
var order = await repo.GetByIdAsync(orderId, ct);
|
|
78
|
+
if (order == null || order.Status >= OrderStatus.Completed) return; // Idempotent
|
|
79
|
+
order.MarkAsProcessed();
|
|
80
|
+
await repo.UpdateAsync(order, ct);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ❌ NEVER inject repository directly — DbContext will be disposed
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Retry & Error Handling
|
|
88
|
+
|
|
89
|
+
```csharp
|
|
90
|
+
[AutomaticRetry(Attempts = 5, DelaysInSeconds = new[] { 60, 300, 900, 3600, 7200 })]
|
|
91
|
+
[Queue("critical")]
|
|
92
|
+
public async Task ProcessPaymentAsync(int orderId)
|
|
93
|
+
{
|
|
94
|
+
try { await _paymentService.ProcessAsync(orderId); }
|
|
95
|
+
catch (PaymentGatewayException ex) { _logger.LogWarning(ex, "..."); throw; } // Rethrow for retry
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Dashboard Authorization
|
|
100
|
+
|
|
101
|
+
```csharp
|
|
102
|
+
public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter
|
|
103
|
+
{
|
|
104
|
+
public bool Authorize(DashboardContext context)
|
|
105
|
+
{
|
|
106
|
+
var http = context.GetHttpContext();
|
|
107
|
+
return http.User.Identity?.IsAuthenticated == true && http.User.IsInRole("Admin");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Checklist
|
|
115
|
+
- [ ] Uses `IRepositoryFactory` (NOT direct repository)
|
|
116
|
+
- [ ] `await using` for dispose
|
|
117
|
+
- [ ] Job is idempotent (safe to retry)
|
|
118
|
+
- [ ] AutomaticRetry configured
|
|
119
|
+
- [ ] Queue defined (critical/default/low)
|
|
120
|
+
- [ ] CancellationToken propagated
|
|
121
|
+
- [ ] Dashboard protected with auth
|
|
122
|
+
- [ ] Timezone set for recurring jobs
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
*MORPH-SPEC by Polymorphism Tech*
|