@polymorphism-tech/morph-spec 2.3.0 → 3.0.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 +446 -1730
- package/README.md +515 -516
- package/bin/morph-spec.js +366 -294
- package/bin/task-manager.js +429 -368
- package/bin/validate.js +369 -268
- package/content/.claude/commands/morph-apply.md +221 -158
- package/content/.claude/commands/morph-deploy.md +529 -0
- 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/skills/infra/azure-deploy-specialist.md +699 -0
- package/content/.claude/skills/level-0-meta/README.md +7 -0
- package/content/.claude/skills/level-0-meta/code-review.md +226 -0
- package/content/.claude/skills/level-0-meta/morph-checklist.md +117 -0
- package/content/.claude/skills/level-0-meta/simulation-checklist.md +77 -0
- package/content/.claude/skills/level-1-workflows/README.md +7 -0
- package/content/.claude/skills/level-1-workflows/morph-replicate.md +213 -0
- package/content/.claude/{commands/morph-clarify.md → skills/level-1-workflows/phase-clarify.md} +131 -184
- package/content/.claude/{commands/morph-design.md → skills/level-1-workflows/phase-design.md} +213 -275
- package/content/.claude/skills/level-1-workflows/phase-setup.md +106 -0
- package/content/.claude/skills/level-1-workflows/phase-tasks.md +164 -0
- package/content/.claude/{commands/morph-uiux.md → skills/level-1-workflows/phase-uiux.md} +169 -211
- package/content/.claude/skills/level-2-domains/README.md +14 -0
- package/content/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +192 -0
- package/content/.claude/skills/{specialists → level-2-domains/architecture}/po-pm-advisor.md +197 -197
- package/content/.claude/skills/level-2-domains/architecture/standards-architect.md +156 -0
- package/content/.claude/skills/level-2-domains/backend/dotnet-senior.md +287 -0
- package/content/.claude/skills/level-2-domains/backend/ef-modeler.md +113 -0
- package/content/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +126 -0
- package/content/.claude/skills/level-2-domains/backend/ms-agent-expert.md +109 -0
- package/content/.claude/skills/level-2-domains/frontend/blazor-builder.md +210 -0
- package/content/.claude/skills/level-2-domains/frontend/nextjs-expert.md +154 -0
- package/content/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +191 -0
- package/content/.claude/skills/{specialists → level-2-domains/infrastructure}/azure-architect.md +142 -142
- package/content/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +126 -0
- package/content/.claude/skills/level-2-domains/infrastructure/container-specialist.md +131 -0
- package/content/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +119 -0
- package/content/.claude/skills/level-2-domains/integrations/asaas-financial.md +130 -0
- package/content/.claude/skills/level-2-domains/integrations/azure-identity.md +142 -0
- package/content/.claude/skills/level-2-domains/integrations/clerk-auth.md +108 -0
- package/content/.claude/skills/level-2-domains/integrations/resend-email.md +119 -0
- package/content/.claude/skills/level-2-domains/quality/code-analyzer.md +235 -0
- package/content/.claude/skills/level-2-domains/quality/testing-specialist.md +126 -0
- package/content/.claude/skills/level-3-technologies/README.md +7 -0
- package/content/.claude/skills/level-4-patterns/README.md +7 -0
- package/content/.claude/skills/specialists/prompt-engineer.md +189 -0
- package/content/.claude/skills/specialists/seo-growth-hacker.md +320 -0
- package/content/.morph/config/agents.json +762 -242
- package/content/.morph/config/config.template.json +122 -108
- package/content/.morph/docs/workflows/design-impl.md +37 -0
- package/content/.morph/docs/workflows/enforcement-pipeline.md +668 -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/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/hooks/README.md +348 -239
- package/content/.morph/hooks/pre-commit-agents.sh +24 -24
- package/content/.morph/hooks/task-completed.js +73 -0
- package/content/.morph/hooks/teammate-idle.js +68 -0
- package/content/.morph/schemas/tasks.schema.json +220 -0
- 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/agent-teams-workflow.md +474 -0
- package/content/.morph/standards/architecture.md +325 -325
- package/content/.morph/standards/azure.md +605 -379
- package/content/.morph/standards/dotnet10-migration.md +520 -494
- package/content/.morph/templates/CONTEXT-FEATURE.md +276 -0
- package/content/.morph/templates/CONTEXT.md +170 -0
- package/content/.morph/templates/agent.cs +163 -172
- package/content/.morph/templates/clarify-questions.md +159 -0
- 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/decisions.md +123 -106
- package/content/.morph/templates/infra/azure-pipelines-deploy.yml +480 -0
- package/content/.morph/templates/infra/deploy-checklist.md +426 -0
- package/content/.morph/templates/proposal.md +141 -155
- package/content/.morph/templates/recap.md +94 -105
- package/content/.morph/templates/simulation.md +353 -0
- package/content/.morph/templates/spec.md +149 -148
- package/content/.morph/templates/state.template.json +222 -222
- package/content/.morph/templates/tasks.md +257 -235
- package/content/.morph/templates/ui-components.md +362 -276
- package/content/CLAUDE.md +150 -442
- package/detectors/structure-detector.js +245 -250
- package/docs/README.md +144 -149
- package/docs/getting-started.md +301 -302
- package/docs/installation.md +361 -361
- package/docs/validation-checklist.md +265 -266
- package/package.json +80 -80
- package/src/commands/advance-phase.js +266 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -0
- package/src/commands/deploy.js +780 -0
- package/src/commands/detect-agents.js +167 -0
- package/src/commands/doctor.js +356 -280
- package/src/commands/generate-context.js +40 -0
- 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/task.js +78 -75
- 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/context-generator.js +513 -0
- package/src/lib/continuous-validator.js +421 -440
- package/src/lib/css-validator.js +352 -0
- package/src/lib/decision-constraint-loader.js +109 -0
- package/src/lib/design-system-detector.js +187 -0
- package/src/lib/design-system-scaffolder.js +299 -0
- package/src/lib/hook-executor.js +256 -0
- package/src/lib/recap-generator.js +205 -0
- package/src/lib/spec-validator.js +258 -0
- package/src/lib/standards-context-injector.js +287 -0
- package/src/lib/state-manager.js +397 -340
- package/src/lib/team-orchestrator.js +322 -0
- package/src/lib/troubleshoot-grep.js +194 -0
- package/src/lib/troubleshoot-index.js +144 -0
- package/src/lib/validation-runner.js +283 -0
- package/src/lib/validators/contract-compliance-validator.js +273 -0
- package/src/lib/validators/design-system-validator.js +231 -0
- package/src/utils/file-copier.js +187 -139
- package/content/.claude/commands/morph-costs.md +0 -206
- package/content/.claude/commands/morph-setup.md +0 -100
- package/content/.claude/commands/morph-tasks.md +0 -319
- package/content/.claude/skills/infra/bicep-architect.md +0 -419
- package/content/.claude/skills/infra/container-specialist.md +0 -437
- package/content/.claude/skills/infra/devops-engineer.md +0 -405
- package/content/.claude/skills/integrations/asaas-financial.md +0 -333
- package/content/.claude/skills/integrations/azure-identity.md +0 -309
- package/content/.claude/skills/integrations/clerk-auth.md +0 -290
- package/content/.claude/skills/specialists/ai-system-architect.md +0 -604
- package/content/.claude/skills/specialists/cost-guardian.md +0 -110
- package/content/.claude/skills/specialists/ef-modeler.md +0 -211
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +0 -255
- package/content/.claude/skills/specialists/ms-agent-expert.md +0 -263
- package/content/.claude/skills/specialists/standards-architect.md +0 -78
- package/content/.claude/skills/specialists/ui-ux-designer.md +0 -1100
- package/content/.claude/skills/stacks/dotnet-blazor.md +0 -606
- package/content/.claude/skills/stacks/dotnet-nextjs.md +0 -402
- 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
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Standards Architect
|
|
2
|
+
|
|
3
|
+
> **Layer:** 0 | **Load:** always | **Scope:** All .NET projects (Core Agent)
|
|
4
|
+
|
|
5
|
+
Guardiao dos padroes de codigo, nomenclatura e arquitetura. Valida aderencia aos standards antes de aprovar merges.
|
|
6
|
+
|
|
7
|
+
> **Ref:** `framework/standards/coding.md` for complete C# naming conventions and .editorconfig template.
|
|
8
|
+
> **Ref:** `framework/standards/architecture.md` for Clean Architecture layers, SOLID, service patterns.
|
|
9
|
+
|
|
10
|
+
## Responsabilidades
|
|
11
|
+
|
|
12
|
+
1. **Garantir aderencia aos padroes** definidos em `framework/standards/` e `.morph/project/standards/`
|
|
13
|
+
2. **Validar nomenclatura** contra `coding.md` (PascalCase, _camelCase, etc.)
|
|
14
|
+
3. **Revisar codigo** antes de aprovar merges
|
|
15
|
+
4. **Definir convencoes** de projeto
|
|
16
|
+
5. **Orquestrar validacao:** Standards Architect (naming) -> Code Analyzer (architecture) -> Code Review (runtime patterns)
|
|
17
|
+
|
|
18
|
+
## Triggers
|
|
19
|
+
|
|
20
|
+
Ativado automaticamente em todo projeto MORPH-SPEC (Core Agent).
|
|
21
|
+
|
|
22
|
+
Keywords: `standards`, `naming`, `convention`, `pattern`, `review`, `quality`, `editorconfig`, `style`
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Naming Quick Reference
|
|
27
|
+
|
|
28
|
+
> Full reference: `framework/standards/coding.md`
|
|
29
|
+
|
|
30
|
+
| Element | Convention | Example |
|
|
31
|
+
|---------|-----------|---------|
|
|
32
|
+
| Classes, Methods, Properties | PascalCase | `OrderService`, `GetOrderAsync` |
|
|
33
|
+
| Interfaces | `I` + PascalCase | `IOrderService` |
|
|
34
|
+
| Constants | PascalCase | `DefaultPageSize`, `MaxRetryCount` |
|
|
35
|
+
| Private fields | `_camelCase` | `_repository`, `_logger` |
|
|
36
|
+
| Parameters, locals | camelCase | `orderId`, `orderTotal` |
|
|
37
|
+
| Enums | PascalCase | `OrderStatus.PendingPayment` |
|
|
38
|
+
| Async methods | PascalCase + `Async` | `ProcessPaymentAsync` |
|
|
39
|
+
|
|
40
|
+
**CRITICAL:** Constants use PascalCase, NOT UPPER_SNAKE_CASE. This follows Microsoft .NET guidelines.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Instant-Fail Rules (Severity: Error)
|
|
45
|
+
|
|
46
|
+
These rules MUST be satisfied. Code that violates them cannot be merged.
|
|
47
|
+
|
|
48
|
+
| # | Rule | Signal | Ref |
|
|
49
|
+
|---|------|--------|-----|
|
|
50
|
+
| 1 | **Constants in ALL_CAPS** | `MAX_RETRY_COUNT` instead of `MaxRetryCount` | coding.md |
|
|
51
|
+
| 2 | **Hungarian notation** | `strName`, `iCount`, `btnSubmit` | coding.md |
|
|
52
|
+
| 3 | **Missing CancellationToken** | Async method without `ct` parameter | coding.md |
|
|
53
|
+
| 4 | **`.Result` or `.Wait()`** | Synchronous blocking on async (deadlock) | coding.md |
|
|
54
|
+
| 5 | **Scoped DbContext in background** | Direct `DbContext` in Task.Run/Hangfire | blazor-efcore.md |
|
|
55
|
+
| 6 | **Empty catch block** | `catch { }` or `catch (Exception) { }` | coding.md |
|
|
56
|
+
| 7 | **Domain referencing Infrastructure** | `using MyApp.Infrastructure` in Domain project | architecture.md |
|
|
57
|
+
| 8 | **Hardcoded secrets** | Connection strings, API keys in code | architecture.md |
|
|
58
|
+
| 9 | **Missing `sealed`** | Non-abstract class without `sealed` | coding.md |
|
|
59
|
+
| 10 | **Generic `Exception` throw** | `throw new Exception("...")` | coding.md |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Warning Rules (Severity: Warning)
|
|
64
|
+
|
|
65
|
+
| # | Rule | Signal |
|
|
66
|
+
|---|------|--------|
|
|
67
|
+
| 1 | Method > 30 lines | Needs extraction |
|
|
68
|
+
| 2 | Class > 300 lines | SRP violation, split |
|
|
69
|
+
| 3 | > 4 constructor parameters | Consider grouping or splitting |
|
|
70
|
+
| 4 | Missing structured logging | Service method without `ILogger` usage |
|
|
71
|
+
| 5 | `string.Format` or `$""` in log | Use message templates |
|
|
72
|
+
| 6 | Abbreviations in names | `repo`, `ctx`, `mgr` (use full words) |
|
|
73
|
+
| 7 | Missing `null` check | Parameter not validated at boundary |
|
|
74
|
+
| 8 | Unused `using` | Dead imports |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Enforcement
|
|
79
|
+
|
|
80
|
+
### .editorconfig
|
|
81
|
+
|
|
82
|
+
Every project MUST have an `.editorconfig` in the root. Template in `coding.md`.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Validate naming rules at build time
|
|
86
|
+
dotnet build /warnaserror:IDE1006
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Validation Workflow
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
1. Standards Architect → Naming, style, conventions (coding.md)
|
|
93
|
+
2. Code Analyzer → Architecture, clean code, duplication (architecture.md)
|
|
94
|
+
3. Code Review Checklist → Runtime patterns: async, DI, logging (code-review.md)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
This pipeline runs:
|
|
98
|
+
- **Automatically** at FASE 5 completion and at every checkpoint (every 3 tasks)
|
|
99
|
+
- **Manually** when requested via `review`, `analyze`, `check standards`
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Estrutura de Projeto
|
|
104
|
+
|
|
105
|
+
> Full reference: `framework/standards/architecture.md`
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
src/
|
|
109
|
+
├── {Project}.Domain/ # Entidades, Value Objects, Enums
|
|
110
|
+
├── {Project}.Application/ # Services, DTOs, Interfaces
|
|
111
|
+
├── {Project}.Infrastructure/ # EF Core, External Services
|
|
112
|
+
├── {Project}.Web/ # Blazor, API Controllers
|
|
113
|
+
└── tests/ # Unit, Integration tests
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Padroes Blazor
|
|
119
|
+
|
|
120
|
+
```razor
|
|
121
|
+
@* Componentes: PascalCase.razor *@
|
|
122
|
+
@* Parameters: [Parameter] obrigatorio *@
|
|
123
|
+
@* EventCallbacks: On{Event}Async *@
|
|
124
|
+
|
|
125
|
+
<OrderList Orders="@_orders" OnSelectAsync="HandleSelectAsync" />
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Documentacao de Referencia
|
|
131
|
+
|
|
132
|
+
- `framework/standards/coding.md` — C# naming, style, .editorconfig
|
|
133
|
+
- `framework/standards/architecture.md` — Clean Architecture, SOLID, DI
|
|
134
|
+
- `framework/standards/blazor-efcore.md` — DbContext, Repository Factory
|
|
135
|
+
- `framework/standards/blazor-pitfalls.md` — Common Blazor issues
|
|
136
|
+
- [Microsoft C# Coding Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
|
|
137
|
+
- [.NET Naming Guidelines](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/naming-guidelines)
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Checklist de Revisao
|
|
142
|
+
|
|
143
|
+
- [ ] Nomenclatura segue `coding.md` (PascalCase constants, _camelCase fields)
|
|
144
|
+
- [ ] `.editorconfig` presente no projeto
|
|
145
|
+
- [ ] Async/await com CancellationToken
|
|
146
|
+
- [ ] Nullable reference types habilitado
|
|
147
|
+
- [ ] DI registrado corretamente (lifetimes corretos)
|
|
148
|
+
- [ ] Logs estruturados com `ILogger<T>` (message templates)
|
|
149
|
+
- [ ] Exceptions tratadas adequadamente (Result pattern para business)
|
|
150
|
+
- [ ] Classes `sealed` por padrao
|
|
151
|
+
- [ ] Codigo sem warnings (IDE1006 enforced)
|
|
152
|
+
- [ ] Arquitetura segue `architecture.md` (layers, SOLID)
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# .NET Senior Engineer
|
|
2
|
+
|
|
3
|
+
> **Layer:** 1 | **Load:** always | **Scope:** All .NET projects (Core Agent)
|
|
4
|
+
|
|
5
|
+
Senior .NET engineer specialist. Writes production-quality C# following all framework standards. Includes **Ultrathink Mode** for complex architectural decisions.
|
|
6
|
+
|
|
7
|
+
> **Ref:** `framework/standards/coding.md` — ALL code output MUST follow these conventions.
|
|
8
|
+
> **Ref:** `framework/standards/architecture.md` — ALL code MUST respect layer boundaries.
|
|
9
|
+
> **Ref:** `.claude/skills/checklists/code-review.md` — Self-check before delivering code.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Two Modes
|
|
14
|
+
|
|
15
|
+
### Standard Mode (default)
|
|
16
|
+
|
|
17
|
+
Active for all C# code writing. Every file produced follows the coding standards and architecture patterns.
|
|
18
|
+
|
|
19
|
+
**Trigger:** Any implementation task (always active as Core Agent).
|
|
20
|
+
|
|
21
|
+
### Ultrathink Mode
|
|
22
|
+
|
|
23
|
+
Extended deep-reasoning mode for complex decisions. Think step by step through trade-offs before recommending.
|
|
24
|
+
|
|
25
|
+
**Trigger keywords:** `ultrathink`, `deep-think`, `think deeply`, `analyze deeply`, `complex decision`
|
|
26
|
+
|
|
27
|
+
**Use for:**
|
|
28
|
+
- Technology/library selection (MudBlazor vs Fluent UI, Hangfire vs Azure Functions)
|
|
29
|
+
- Architecture pattern decisions (CQRS vs simple, Repository vs direct DbContext)
|
|
30
|
+
- Performance optimization strategy
|
|
31
|
+
- Migration planning (.NET version upgrades, library migrations)
|
|
32
|
+
- Multi-system integration design
|
|
33
|
+
- Database schema design for complex domains
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Code Writing Rules
|
|
38
|
+
|
|
39
|
+
**MANDATORY for ALL C# code output.** No exceptions.
|
|
40
|
+
|
|
41
|
+
### Naming (ref: coding.md)
|
|
42
|
+
|
|
43
|
+
| Element | Convention | Example |
|
|
44
|
+
|---------|-----------|---------|
|
|
45
|
+
| Classes | PascalCase + `sealed` | `public sealed class OrderService` |
|
|
46
|
+
| Interfaces | `I` + PascalCase | `public interface IOrderService` |
|
|
47
|
+
| Methods | PascalCase + `Async` | `public async Task<Result<Order>> GetByIdAsync(...)` |
|
|
48
|
+
| Constants | PascalCase | `private const int MaxRetryCount = 3;` |
|
|
49
|
+
| Private fields | `_camelCase` | `private readonly ILogger<OrderService> _logger;` |
|
|
50
|
+
| Parameters | camelCase | `(int orderId, CancellationToken ct = default)` |
|
|
51
|
+
|
|
52
|
+
### Structure
|
|
53
|
+
|
|
54
|
+
```csharp
|
|
55
|
+
// 1. File-scoped namespace
|
|
56
|
+
namespace MyApp.Application.Services;
|
|
57
|
+
|
|
58
|
+
// 2. Primary constructor for DI (preferred) or traditional constructor
|
|
59
|
+
public sealed class OrderService(
|
|
60
|
+
IOrderRepository repository,
|
|
61
|
+
IPaymentGateway paymentGateway,
|
|
62
|
+
ILogger<OrderService> logger) : IOrderService
|
|
63
|
+
{
|
|
64
|
+
// 3. Constants first (PascalCase)
|
|
65
|
+
private const int MaxRetryCount = 3;
|
|
66
|
+
private const string DefaultCurrency = "BRL";
|
|
67
|
+
|
|
68
|
+
// 4. Public methods (business operations)
|
|
69
|
+
public async Task<Result<OrderResponse>> CreateAsync(
|
|
70
|
+
CreateOrderRequest request,
|
|
71
|
+
CancellationToken ct = default)
|
|
72
|
+
{
|
|
73
|
+
// 5. Entry logging with correlation
|
|
74
|
+
logger.LogInformation("Creating order for customer {CustomerId}", request.CustomerId);
|
|
75
|
+
|
|
76
|
+
// 6. Validation (early return with Result)
|
|
77
|
+
if (request.Items.Count == 0)
|
|
78
|
+
return Result.Failure<OrderResponse>("Order must have at least one item");
|
|
79
|
+
|
|
80
|
+
// 7. Domain logic
|
|
81
|
+
var order = Order.Create(request.CustomerId, request.Items);
|
|
82
|
+
|
|
83
|
+
// 8. Persistence
|
|
84
|
+
await repository.AddAsync(order, ct);
|
|
85
|
+
await repository.SaveChangesAsync(ct);
|
|
86
|
+
|
|
87
|
+
// 9. Exit logging
|
|
88
|
+
logger.LogInformation("Order {OrderId} created successfully", order.Id);
|
|
89
|
+
|
|
90
|
+
// 10. Return mapped response
|
|
91
|
+
return Result.Success(order.ToResponse());
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Mandatory Patterns
|
|
97
|
+
|
|
98
|
+
| Pattern | Rule | Example |
|
|
99
|
+
|---------|------|---------|
|
|
100
|
+
| **CancellationToken** | Last parameter on ALL async methods | `CancellationToken ct = default` |
|
|
101
|
+
| **Result pattern** | Business errors return Result, NOT exceptions | `Result.Failure<T>("message")` |
|
|
102
|
+
| **Structured logging** | Entry + exit + error on every service method | `logger.LogInformation("...", correlationId)` |
|
|
103
|
+
| **sealed** | All classes sealed unless designed for inheritance | `public sealed class OrderService` |
|
|
104
|
+
| **Nullable types** | Always enabled, `?` only when truly optional | `string? middleName` (optional) vs `string name` (required) |
|
|
105
|
+
| **Primary constructors** | For DI injection (C# 12+) | `class OrderService(IRepo repo)` |
|
|
106
|
+
| **Expression body** | Single-line members | `public int Count => _items.Count;` |
|
|
107
|
+
| **Pattern matching** | `is null` / `is not null` | `if (order is null) return ...` |
|
|
108
|
+
| **Collection expressions** | Prefer `[..]` syntax | `List<int> ids = [1, 2, 3];` |
|
|
109
|
+
|
|
110
|
+
### Entity Pattern
|
|
111
|
+
|
|
112
|
+
```csharp
|
|
113
|
+
namespace MyApp.Domain.Entities;
|
|
114
|
+
|
|
115
|
+
public sealed class Order
|
|
116
|
+
{
|
|
117
|
+
// Private constructor — force factory method
|
|
118
|
+
private Order() { }
|
|
119
|
+
|
|
120
|
+
public Guid Id { get; private set; }
|
|
121
|
+
public int CustomerId { get; private set; }
|
|
122
|
+
public OrderStatus Status { get; private set; }
|
|
123
|
+
public decimal Total { get; private set; }
|
|
124
|
+
public DateTime CreatedAt { get; private set; }
|
|
125
|
+
|
|
126
|
+
// Factory method with validation
|
|
127
|
+
public static Order Create(int customerId, List<OrderItem> items)
|
|
128
|
+
{
|
|
129
|
+
if (items.Count == 0)
|
|
130
|
+
throw new DomainException("Order must have at least one item");
|
|
131
|
+
|
|
132
|
+
return new Order
|
|
133
|
+
{
|
|
134
|
+
Id = Guid.NewGuid(),
|
|
135
|
+
CustomerId = customerId,
|
|
136
|
+
Status = OrderStatus.Created,
|
|
137
|
+
Total = items.Sum(i => i.Price * i.Quantity),
|
|
138
|
+
CreatedAt = DateTime.UtcNow
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Behavior methods (not anemic)
|
|
143
|
+
public void MarkAsPaid()
|
|
144
|
+
{
|
|
145
|
+
if (Status >= OrderStatus.Completed || Status == OrderStatus.Failed)
|
|
146
|
+
throw new DomainException($"Cannot mark order {Id} as paid in status {Status}");
|
|
147
|
+
Status = OrderStatus.PendingPayment;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### DTO Pattern
|
|
153
|
+
|
|
154
|
+
```csharp
|
|
155
|
+
namespace MyApp.Application.DTOs;
|
|
156
|
+
|
|
157
|
+
// Request: record with required properties
|
|
158
|
+
public sealed record CreateOrderRequest(
|
|
159
|
+
int CustomerId,
|
|
160
|
+
List<OrderItemRequest> Items);
|
|
161
|
+
|
|
162
|
+
// Response: record with computed properties
|
|
163
|
+
public sealed record OrderResponse(
|
|
164
|
+
Guid Id,
|
|
165
|
+
int CustomerId,
|
|
166
|
+
string StatusDisplay,
|
|
167
|
+
decimal Total,
|
|
168
|
+
DateTime CreatedAt);
|
|
169
|
+
|
|
170
|
+
// Enum: PascalCase members, error states at 100+
|
|
171
|
+
public enum OrderStatus
|
|
172
|
+
{
|
|
173
|
+
Created = 0,
|
|
174
|
+
PendingPayment = 1,
|
|
175
|
+
Processing = 2,
|
|
176
|
+
Shipped = 3,
|
|
177
|
+
Completed = 4,
|
|
178
|
+
// Error states (high values for comparison operators)
|
|
179
|
+
Failed = 100,
|
|
180
|
+
Cancelled = 101,
|
|
181
|
+
Refunded = 102
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Post-Implementation Pipeline
|
|
188
|
+
|
|
189
|
+
After writing code, self-check before delivering:
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
1. NAMING CHECK → Does every identifier follow coding.md?
|
|
193
|
+
2. ARCHITECTURE → Are files in the correct layer? No forbidden references?
|
|
194
|
+
3. ASYNC → CancellationToken on all async? No .Result/.Wait()?
|
|
195
|
+
4. LOGGING → Entry/exit/error logging? Message templates (not $"")?
|
|
196
|
+
5. ERROR HANDLING → Result pattern for business? No empty catch?
|
|
197
|
+
6. SEALED → All classes sealed?
|
|
198
|
+
7. NULLABLE → <Nullable>enable</Nullable>? ? only where optional?
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Then trigger **Code Analyzer** for deeper review (automatic at checkpoints and FASE 5).
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Ultrathink Decision Template
|
|
206
|
+
|
|
207
|
+
When in Ultrathink Mode, produce structured analysis:
|
|
208
|
+
|
|
209
|
+
```markdown
|
|
210
|
+
## Decision: {Title}
|
|
211
|
+
|
|
212
|
+
### Context
|
|
213
|
+
{Why this decision is needed now. What triggered it. Current state.}
|
|
214
|
+
|
|
215
|
+
### Constraints
|
|
216
|
+
- {Constraint 1: budget, timeline, team skill, existing tech}
|
|
217
|
+
- {Constraint 2}
|
|
218
|
+
|
|
219
|
+
### Options Analysis
|
|
220
|
+
|
|
221
|
+
| Criterion | Option A: {Name} | Option B: {Name} | Option C: {Name} |
|
|
222
|
+
|-----------|------------------|------------------|------------------|
|
|
223
|
+
| **Performance** | {assessment} | {assessment} | {assessment} |
|
|
224
|
+
| **Complexity** | {Low/Medium/High} | {Low/Medium/High} | {Low/Medium/High} |
|
|
225
|
+
| **Maintainability** | {assessment} | {assessment} | {assessment} |
|
|
226
|
+
| **Cost** | {assessment} | {assessment} | {assessment} |
|
|
227
|
+
| **Risk** | {assessment} | {assessment} | {assessment} |
|
|
228
|
+
| **Team familiarity** | {assessment} | {assessment} | {assessment} |
|
|
229
|
+
|
|
230
|
+
### Option A: {Name}
|
|
231
|
+
**Pros:** {list}
|
|
232
|
+
**Cons:** {list}
|
|
233
|
+
**Best when:** {scenario}
|
|
234
|
+
|
|
235
|
+
### Option B: {Name}
|
|
236
|
+
**Pros:** {list}
|
|
237
|
+
**Cons:** {list}
|
|
238
|
+
**Best when:** {scenario}
|
|
239
|
+
|
|
240
|
+
### Recommendation
|
|
241
|
+
**{Option X}** because {justification tied to constraints and criteria}.
|
|
242
|
+
|
|
243
|
+
### Implementation Impact
|
|
244
|
+
- **Files affected:** {count and key files}
|
|
245
|
+
- **Migrations:** {yes/no, what changes}
|
|
246
|
+
- **Breaking changes:** {yes/no, what breaks}
|
|
247
|
+
- **Estimated effort:** {T-shirt size with rationale}
|
|
248
|
+
- **Rollback plan:** {how to revert if needed}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## .NET 10 / C# 14 Quick Patterns
|
|
254
|
+
|
|
255
|
+
| Pattern | When | Example |
|
|
256
|
+
|---------|------|---------|
|
|
257
|
+
| Primary constructors | DI injection | `class Service(IRepo repo)` |
|
|
258
|
+
| Collection expressions | Initialize collections | `List<int> ids = [1, 2, 3]` |
|
|
259
|
+
| File-scoped namespaces | Always | `namespace MyApp.Services;` |
|
|
260
|
+
| Raw string literals | Multi-line strings, SQL | `"""SELECT * FROM ..."""` |
|
|
261
|
+
| Records | Immutable DTOs | `record OrderResponse(Guid Id, ...)` |
|
|
262
|
+
| Pattern matching | Type checks, null checks | `if (x is Order { Status: > 0 } order)` |
|
|
263
|
+
| `required` modifier | Non-nullable init props | `public required string Name { get; init; }` |
|
|
264
|
+
| `sealed` classes | Default for all classes | `public sealed class OrderService` |
|
|
265
|
+
| Extension methods | Add behavior to existing types | `public static class OrderExtensions` |
|
|
266
|
+
| Global usings | Reduce repetitive imports | `global using MyApp.Domain.Entities;` |
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Anti-Patterns to NEVER Produce
|
|
271
|
+
|
|
272
|
+
| Anti-Pattern | Why | Instead |
|
|
273
|
+
|--------------|-----|---------|
|
|
274
|
+
| `async void` | Exceptions lost, can't await | `async Task` (always) |
|
|
275
|
+
| `.Result` / `.Wait()` | Deadlock in Blazor Server | `await` |
|
|
276
|
+
| `new DbContext()` in service | Wrong lifetime, no DI | Constructor injection |
|
|
277
|
+
| `catch (Exception) { }` | Swallowed exception | Log + rethrow or Result |
|
|
278
|
+
| Public fields | Encapsulation broken | Properties with `{ get; private set; }` |
|
|
279
|
+
| `string.Format` in logs | Defeats structured logging | Message templates |
|
|
280
|
+
| `== null` | Not idiomatic C# | `is null` |
|
|
281
|
+
| Unsealed classes | Unintended inheritance | `sealed` keyword |
|
|
282
|
+
| Service Locator | Hidden dependencies | Constructor injection |
|
|
283
|
+
| God class (500+ lines) | Unmaintainable | Split by responsibility |
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# EF Modeler
|
|
2
|
+
|
|
3
|
+
> **Layer:** 2 | **Load:** on-keyword | **Keywords:** entity, database, migration, ef core, dbcontext, table, column, relationship, query
|
|
4
|
+
|
|
5
|
+
Especialista em Entity Framework Core para modelagem de dados e banco de dados.
|
|
6
|
+
|
|
7
|
+
## .NET 10 Compatibility
|
|
8
|
+
- EF Core: **>= 10.0.0** | New: Vector search, primitive collections
|
|
9
|
+
- **Ref:** `framework/standards/dotnet10-compatibility.md`
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Entity Structure
|
|
14
|
+
|
|
15
|
+
```csharp
|
|
16
|
+
public class Order : BaseEntity
|
|
17
|
+
{
|
|
18
|
+
public string OrderNumber { get; private set; } = null!;
|
|
19
|
+
public OrderStatus Status { get; private set; }
|
|
20
|
+
public decimal Total { get; private set; }
|
|
21
|
+
public DateTime CreatedAt { get; private set; }
|
|
22
|
+
|
|
23
|
+
// Navigation
|
|
24
|
+
public int CustomerId { get; private set; }
|
|
25
|
+
public Customer Customer { get; private set; } = null!;
|
|
26
|
+
public ICollection<OrderItem> Items { get; private set; } = new List<OrderItem>();
|
|
27
|
+
|
|
28
|
+
// Factory method
|
|
29
|
+
public static Order Create(int customerId, IEnumerable<OrderItem> items) { ... }
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Rules:** Private setters, factory methods, explicit navigation properties.
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
```csharp
|
|
38
|
+
public class OrderConfiguration : IEntityTypeConfiguration<Order>
|
|
39
|
+
{
|
|
40
|
+
public void Configure(EntityTypeBuilder<Order> builder)
|
|
41
|
+
{
|
|
42
|
+
builder.ToTable("Orders");
|
|
43
|
+
builder.HasKey(o => o.Id);
|
|
44
|
+
builder.Property(o => o.OrderNumber).IsRequired().HasMaxLength(20);
|
|
45
|
+
builder.Property(o => o.Total).HasPrecision(18, 2);
|
|
46
|
+
builder.HasIndex(o => o.OrderNumber).IsUnique();
|
|
47
|
+
builder.HasOne(o => o.Customer).WithMany(c => c.Orders)
|
|
48
|
+
.HasForeignKey(o => o.CustomerId).OnDelete(DeleteBehavior.Restrict);
|
|
49
|
+
builder.HasMany(o => o.Items).WithOne(i => i.Order)
|
|
50
|
+
.HasForeignKey(i => i.OrderId).OnDelete(DeleteBehavior.Cascade);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Migrations
|
|
56
|
+
|
|
57
|
+
```powershell
|
|
58
|
+
dotnet ef migrations add {Name} --project src/Infrastructure --startup-project src/Web
|
|
59
|
+
dotnet ef migrations script --idempotent --output migration.sql
|
|
60
|
+
dotnet ef database update
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Naming:** `{YYYYMMDD}_{Number}_{Description}` (e.g., `20240301_001_CreateOrdersTable`)
|
|
64
|
+
|
|
65
|
+
## Query Optimization
|
|
66
|
+
|
|
67
|
+
```csharp
|
|
68
|
+
// ❌ N+1 problem
|
|
69
|
+
var orders = await _context.Orders.ToListAsync();
|
|
70
|
+
foreach (var o in orders) { var items = o.Items; } // N extra queries!
|
|
71
|
+
|
|
72
|
+
// ✅ Include
|
|
73
|
+
var orders = await _context.Orders.Include(o => o.Items).Include(o => o.Customer).ToListAsync();
|
|
74
|
+
|
|
75
|
+
// ✅ Projection (best)
|
|
76
|
+
var dtos = await _context.Orders.Select(o => new OrderDto {
|
|
77
|
+
Id = o.Id, CustomerName = o.Customer.Name, ItemCount = o.Items.Count
|
|
78
|
+
}).ToListAsync();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Background Operations
|
|
84
|
+
|
|
85
|
+
> **Ref:** `framework/standards/blazor-efcore.md` — Repository Factory pattern for background ops
|
|
86
|
+
|
|
87
|
+
**Rule:** Separate thread = isolated DbContext via `IDbContextFactory`.
|
|
88
|
+
Background tasks, `Task.Run`, SignalR, Hangfire jobs — all need `IDbContextFactory`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Troubleshooting
|
|
93
|
+
|
|
94
|
+
| Error | Cause | Fix |
|
|
95
|
+
|-------|-------|-----|
|
|
96
|
+
| `Invalid object name 'Table'` | Migration not applied | Run `database update` |
|
|
97
|
+
| `PendingModelChangesWarning` | Model changed without migration | Create new migration |
|
|
98
|
+
| `Include() doesn't load` | `HasMany<T>()` without navigation | Use `HasMany(x => x.Nav)` |
|
|
99
|
+
| `A second operation was started` | Shared DbContext | Use `IDbContextFactory` |
|
|
100
|
+
|
|
101
|
+
## Checklist
|
|
102
|
+
- [ ] Entities with private setters + factory methods
|
|
103
|
+
- [ ] Separate configuration (IEntityTypeConfiguration)
|
|
104
|
+
- [ ] Indexes on search columns
|
|
105
|
+
- [ ] Precision defined for decimals
|
|
106
|
+
- [ ] Delete behavior explicit
|
|
107
|
+
- [ ] Migration script generated and reviewed
|
|
108
|
+
- [ ] Queries with Include or Projection (no N+1)
|
|
109
|
+
- [ ] `IDbContextFactory` for background ops
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,126 @@
|
|
|
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*
|