@edupia-tutor/spec-driven-docs 0.14.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/bin/build.js +230 -0
- package/bin/index.js +598 -0
- package/commands/debug.md +830 -0
- package/commands/debug.tmpl +257 -0
- package/commands/define-product.md +652 -0
- package/commands/define-product.tmpl +158 -0
- package/commands/dev-gen-test.md +1010 -0
- package/commands/dev-gen-test.tmpl +490 -0
- package/commands/dev-run-test.md +744 -0
- package/commands/dev-run-test.tmpl +224 -0
- package/commands/dev-smoke-test.md +711 -0
- package/commands/dev-smoke-test.tmpl +217 -0
- package/commands/fix-bug.md +744 -0
- package/commands/fix-bug.tmpl +171 -0
- package/commands/generate-bdd.md +1054 -0
- package/commands/generate-bdd.tmpl +534 -0
- package/commands/generate-code.md +869 -0
- package/commands/generate-code.tmpl +349 -0
- package/commands/generate-design-spec.md +958 -0
- package/commands/generate-design-spec.tmpl +464 -0
- package/commands/generate-prd.md +748 -0
- package/commands/generate-prd.tmpl +254 -0
- package/commands/generate-spec-manifest.md +658 -0
- package/commands/generate-spec-manifest.tmpl +164 -0
- package/commands/generate-tech-docs.md +849 -0
- package/commands/generate-tech-docs.tmpl +355 -0
- package/commands/learn.md +636 -0
- package/commands/learn.tmpl +63 -0
- package/commands/map-testids.md +575 -0
- package/commands/map-testids.tmpl +81 -0
- package/commands/propose-scenario.md +623 -0
- package/commands/propose-scenario.tmpl +129 -0
- package/commands/qc-analyze.md +580 -0
- package/commands/qc-analyze.tmpl +86 -0
- package/commands/qc-design-test.md +562 -0
- package/commands/qc-design-test.tmpl +68 -0
- package/commands/qc-plan.md +543 -0
- package/commands/qc-plan.tmpl +49 -0
- package/commands/qc-report.md +554 -0
- package/commands/qc-report.tmpl +60 -0
- package/commands/qc-review.md +547 -0
- package/commands/qc-review.tmpl +53 -0
- package/commands/qc-run-test.md +604 -0
- package/commands/qc-run-test.tmpl +84 -0
- package/commands/refine-prd.md +772 -0
- package/commands/refine-prd.tmpl +140 -0
- package/commands/report-bug.md +639 -0
- package/commands/report-bug.tmpl +145 -0
- package/commands/review-code.md +677 -0
- package/commands/review-code.tmpl +104 -0
- package/commands/review-context.md +1047 -0
- package/commands/review-context.tmpl +415 -0
- package/commands/review-tech-docs.md +811 -0
- package/commands/review-tech-docs.tmpl +317 -0
- package/commands/setup-ai-first.md +545 -0
- package/commands/setup-ai-first.tmpl +358 -0
- package/commands/sync.md +451 -0
- package/commands/sync.tmpl +351 -0
- package/commands/update-framework.md +251 -0
- package/commands/update-framework.tmpl +151 -0
- package/commands/validate-traces.md +842 -0
- package/commands/validate-traces.tmpl +348 -0
- package/core/FRAMEWORK_VERSION +1 -0
- package/core/commands/debug.md +830 -0
- package/core/commands/define-product.md +652 -0
- package/core/commands/dev-gen-test.md +1010 -0
- package/core/commands/dev-run-test.md +744 -0
- package/core/commands/dev-smoke-test.md +711 -0
- package/core/commands/fix-bug.md +744 -0
- package/core/commands/generate-bdd.md +1054 -0
- package/core/commands/generate-code.md +869 -0
- package/core/commands/generate-design-spec.md +958 -0
- package/core/commands/generate-prd.md +748 -0
- package/core/commands/generate-spec-manifest.md +658 -0
- package/core/commands/generate-tech-docs.md +849 -0
- package/core/commands/learn.md +636 -0
- package/core/commands/map-testids.md +575 -0
- package/core/commands/propose-scenario.md +623 -0
- package/core/commands/qc-analyze.md +580 -0
- package/core/commands/qc-design-test.md +562 -0
- package/core/commands/qc-plan.md +543 -0
- package/core/commands/qc-report.md +554 -0
- package/core/commands/qc-review.md +547 -0
- package/core/commands/qc-run-test.md +604 -0
- package/core/commands/refine-prd.md +772 -0
- package/core/commands/report-bug.md +639 -0
- package/core/commands/review-code.md +677 -0
- package/core/commands/review-context.md +1047 -0
- package/core/commands/review-tech-docs.md +811 -0
- package/core/commands/setup-ai-first.md +545 -0
- package/core/commands/sync.md +451 -0
- package/core/commands/update-framework.md +251 -0
- package/core/commands/validate-traces.md +842 -0
- package/core/hooks/data-guard.js +141 -0
- package/core/hooks/settings.json +18 -0
- package/core/modules/android-compose/module.yaml +13 -0
- package/core/modules/android-compose/stack-profile.yaml +57 -0
- package/core/modules/angular/architecture-snippets/component-patterns.md +187 -0
- package/core/modules/angular/module.yaml +6 -0
- package/core/modules/angular/stack-profile.yaml +38 -0
- package/core/modules/context-engineering/architecture-snippets/context-design.md +119 -0
- package/core/modules/context-engineering/module.yaml +9 -0
- package/core/modules/context-engineering/stack-profile.yaml +61 -0
- package/core/modules/dotnet/architecture-snippets/clean-arch.md +160 -0
- package/core/modules/dotnet/module.yaml +6 -0
- package/core/modules/dotnet/stack-profile.yaml +50 -0
- package/core/modules/flutter/module.yaml +14 -0
- package/core/modules/flutter/stack-profile.yaml +59 -0
- package/core/modules/golang/architecture-snippets/domain-layout.md +283 -0
- package/core/modules/golang/module.yaml +6 -0
- package/core/modules/golang/stack-profile.yaml +40 -0
- package/core/modules/ios-swiftui/module.yaml +13 -0
- package/core/modules/ios-swiftui/stack-profile.yaml +55 -0
- package/core/modules/java-spring/architecture-snippets/layered-arch.md +201 -0
- package/core/modules/java-spring/module.yaml +15 -0
- package/core/modules/java-spring/stack-profile.yaml +28 -0
- package/core/modules/nextjs/architecture-snippets/app-router-patterns.md +269 -0
- package/core/modules/nextjs/module.yaml +14 -0
- package/core/modules/nextjs/stack-profile.yaml +74 -0
- package/core/modules/nuxt/module.yaml +14 -0
- package/core/modules/nuxt/stack-profile.yaml +58 -0
- package/core/modules/php-laravel/architecture-snippets/service-repository.md +302 -0
- package/core/modules/php-laravel/module.yaml +15 -0
- package/core/modules/php-laravel/stack-profile.yaml +56 -0
- package/core/modules/qc-playwright/stack-profile.yaml +66 -0
- package/core/modules/react/architecture-snippets/hooks-query-patterns.md +254 -0
- package/core/modules/react/module.yaml +14 -0
- package/core/modules/react/stack-profile.yaml +63 -0
- package/core/modules/react-native/module.yaml +14 -0
- package/core/modules/react-native/stack-profile.yaml +56 -0
- package/core/modules/vue/module.yaml +14 -0
- package/core/modules/vue/stack-profile.yaml +65 -0
- package/core/rules/data-protection.md +80 -0
- package/core/rules/workflow.md +44 -0
- package/core/skills/code/SKILL.md +770 -0
- package/core/skills/debug/SKILL.md +869 -0
- package/core/skills/design-spec/SKILL.md +589 -0
- package/core/skills/discovery/SKILL.md +554 -0
- package/core/skills/prd/SKILL.md +562 -0
- package/core/skills/qc/qa-analyst/DOC_GAPS.template.md +63 -0
- package/core/skills/qc/qa-analyst/acceptance-criteria.md +60 -0
- package/core/skills/qc/qa-analyst/business-rules.md +59 -0
- package/core/skills/qc/qa-analyst/data-flow.md +64 -0
- package/core/skills/qc/qa-analyst/spec-breakdown.md +61 -0
- package/core/skills/qc/qa-designer/e2e/journey.md +41 -0
- package/core/skills/qc/qa-designer/exploratory/charter.md +68 -0
- package/core/skills/qc/qa-designer/exploratory/explore-to-functional.md +43 -0
- package/core/skills/qc/qa-designer/functional/api.md +45 -0
- package/core/skills/qc/qa-designer/functional/gui-feature.md +46 -0
- package/core/skills/qc/qa-designer/functional/gui-screen.md +52 -0
- package/core/skills/qc/qa-designer/integration/api.md +42 -0
- package/core/skills/qc/qa-designer/integration/db.md +39 -0
- package/core/skills/qc/qa-designer/integration/gui.md +40 -0
- package/core/skills/qc/qa-designer/integration/kafka.md +40 -0
- package/core/skills/qc/qa-designer/non-functional.md +40 -0
- package/core/skills/qc/qa-planner/test-plan.md +120 -0
- package/core/skills/qc/qa-reviewer/script/e2e.md +87 -0
- package/core/skills/qc/qa-reviewer/script/exploratory.md +45 -0
- package/core/skills/qc/qa-reviewer/script/functional.md +101 -0
- package/core/skills/qc/qa-reviewer/script/integration.md +91 -0
- package/core/skills/qc/qa-reviewer/script/non-functional.md +126 -0
- package/core/skills/qc/qa-reviewer/test-case/e2e.md +73 -0
- package/core/skills/qc/qa-reviewer/test-case/exploratory.md +43 -0
- package/core/skills/qc/qa-reviewer/test-case/functional.md +76 -0
- package/core/skills/qc/qa-reviewer/test-case/integration.md +69 -0
- package/core/skills/qc/qa-reviewer/test-case/non-functional.md +73 -0
- package/core/skills/qc/qa-runner/e2e.md +49 -0
- package/core/skills/qc/qa-runner/exploratory/session.md +36 -0
- package/core/skills/qc/qa-runner/functional/api.md +35 -0
- package/core/skills/qc/qa-runner/functional/gui-feature.md +51 -0
- package/core/skills/qc/qa-runner/functional/gui-screen.md +55 -0
- package/core/skills/qc/qa-runner/integration.md +47 -0
- package/core/skills/qc/qa-runner/non-functional.md +49 -0
- package/core/skills/qc/qa-runner/report/report.md +37 -0
- package/core/skills/setup-ai-first/SKILL.md +216 -0
- package/core/skills/spec/SKILL.md +461 -0
- package/core/skills/test/SKILL.md +1297 -0
- package/core/steps/capture-lesson.md +79 -0
- package/core/steps/context-loader.md +307 -0
- package/core/steps/gate.md +87 -0
- package/core/steps/report-footer.md +100 -0
- package/core/steps/review-fanout.md +138 -0
- package/core/steps/spawn-agent.md +124 -0
- package/core/steps/trace-mirror.md +26 -0
- package/core/templates/architecture.template.md +113 -0
- package/core/templates/design-spec.template.md +217 -0
- package/core/templates/feature.template +259 -0
- package/core/templates/platform-guide.template.md +145 -0
- package/core/templates/prd.template.md +327 -0
- package/core/templates/product-definition.template.md +168 -0
- package/core/templates/project-context.yaml +161 -0
- package/docs/01-getting-started/README.md +19 -0
- package/docs/01-getting-started/core-concepts.md +102 -0
- package/docs/01-getting-started/installation.md +156 -0
- package/docs/01-getting-started/quickstart.md +85 -0
- package/docs/02-guides/README.md +26 -0
- package/docs/02-guides/developer/README.md +46 -0
- package/docs/02-guides/developer/bdd-and-trace.md +125 -0
- package/docs/02-guides/developer/commands.md +76 -0
- package/docs/02-guides/developer/pr-checklist.md +15 -0
- package/docs/02-guides/developer/scenarios.md +460 -0
- package/docs/02-guides/developer/workflow.md +121 -0
- package/docs/02-guides/product-owner/README.md +79 -0
- package/docs/02-guides/product-owner/commands.md +30 -0
- package/docs/02-guides/product-owner/handoff-checklist.md +42 -0
- package/docs/02-guides/product-owner/prd-writing-rules.md +45 -0
- package/docs/02-guides/product-owner/scenarios.md +436 -0
- package/docs/02-guides/tester/README.md +75 -0
- package/docs/02-guides/tester/bug-reporting.md +117 -0
- package/docs/02-guides/tester/qc-automation.md +165 -0
- package/docs/02-guides/tester/reading-specs.md +79 -0
- package/docs/02-guides/tester/scenarios.md +186 -0
- package/docs/02-guides/tester/spec-manifest.md +130 -0
- package/docs/02-guides/tester/test-checklist.md +31 -0
- package/docs/02-guides/tester/workflow.md +77 -0
- package/docs/03-concepts/README.md +19 -0
- package/docs/03-concepts/architecture.md +248 -0
- package/docs/03-concepts/pipeline.md +274 -0
- package/docs/03-concepts/traceability.md +149 -0
- package/docs/04-operations/README.md +33 -0
- package/docs/04-operations/bug-flow.md +362 -0
- package/docs/04-operations/publishing.md +137 -0
- package/docs/04-operations/sync-and-update.md +522 -0
- package/docs/05-reference/README.md +32 -0
- package/docs/05-reference/command-cheatsheet.md +147 -0
- package/docs/05-reference/commands.md +232 -0
- package/docs/05-reference/modules.md +110 -0
- package/docs/05-reference/trace-schema.md +153 -0
- package/docs/README.md +49 -0
- package/hooks/data-guard.js +141 -0
- package/hooks/settings.json +18 -0
- package/modules/android-compose/module.yaml +13 -0
- package/modules/android-compose/stack-profile.yaml +57 -0
- package/modules/angular/architecture-snippets/component-patterns.md +187 -0
- package/modules/angular/module.yaml +6 -0
- package/modules/angular/stack-profile.yaml +38 -0
- package/modules/context-engineering/architecture-snippets/context-design.md +119 -0
- package/modules/context-engineering/module.yaml +9 -0
- package/modules/context-engineering/stack-profile.yaml +61 -0
- package/modules/dotnet/architecture-snippets/clean-arch.md +160 -0
- package/modules/dotnet/module.yaml +6 -0
- package/modules/dotnet/stack-profile.yaml +50 -0
- package/modules/flutter/module.yaml +14 -0
- package/modules/flutter/stack-profile.yaml +59 -0
- package/modules/golang/architecture-snippets/domain-layout.md +283 -0
- package/modules/golang/module.yaml +6 -0
- package/modules/golang/stack-profile.yaml +40 -0
- package/modules/ios-swiftui/module.yaml +13 -0
- package/modules/ios-swiftui/stack-profile.yaml +55 -0
- package/modules/java-spring/architecture-snippets/layered-arch.md +201 -0
- package/modules/java-spring/module.yaml +15 -0
- package/modules/java-spring/stack-profile.yaml +28 -0
- package/modules/nextjs/architecture-snippets/app-router-patterns.md +269 -0
- package/modules/nextjs/module.yaml +14 -0
- package/modules/nextjs/stack-profile.yaml +74 -0
- package/modules/nuxt/module.yaml +14 -0
- package/modules/nuxt/stack-profile.yaml +58 -0
- package/modules/php-laravel/architecture-snippets/service-repository.md +302 -0
- package/modules/php-laravel/module.yaml +15 -0
- package/modules/php-laravel/stack-profile.yaml +56 -0
- package/modules/qc-playwright/stack-profile.yaml +66 -0
- package/modules/react/architecture-snippets/hooks-query-patterns.md +254 -0
- package/modules/react/module.yaml +14 -0
- package/modules/react/stack-profile.yaml +63 -0
- package/modules/react-native/module.yaml +14 -0
- package/modules/react-native/stack-profile.yaml +56 -0
- package/modules/vue/module.yaml +14 -0
- package/modules/vue/stack-profile.yaml +65 -0
- package/package.json +49 -0
- package/rules/data-protection.md +80 -0
- package/rules/workflow.md +44 -0
- package/scripts/init.sh +49 -0
- package/scripts/migrate-specs.js +256 -0
- package/scripts/upgrade.sh +94 -0
- package/skills/code/SKILL.md +770 -0
- package/skills/code/SKILL.tmpl +176 -0
- package/skills/debug/SKILL.md +869 -0
- package/skills/debug/SKILL.tmpl +262 -0
- package/skills/design-spec/SKILL.md +589 -0
- package/skills/design-spec/SKILL.tmpl +95 -0
- package/skills/discovery/SKILL.md +554 -0
- package/skills/discovery/SKILL.tmpl +147 -0
- package/skills/prd/SKILL.md +562 -0
- package/skills/prd/SKILL.tmpl +188 -0
- package/skills/qc/qa-analyst/DOC_GAPS.template.md +63 -0
- package/skills/qc/qa-analyst/acceptance-criteria.md +60 -0
- package/skills/qc/qa-analyst/business-rules.md +59 -0
- package/skills/qc/qa-analyst/data-flow.md +64 -0
- package/skills/qc/qa-analyst/spec-breakdown.md +61 -0
- package/skills/qc/qa-designer/e2e/journey.md +41 -0
- package/skills/qc/qa-designer/exploratory/charter.md +68 -0
- package/skills/qc/qa-designer/exploratory/explore-to-functional.md +43 -0
- package/skills/qc/qa-designer/functional/api.md +45 -0
- package/skills/qc/qa-designer/functional/gui-feature.md +46 -0
- package/skills/qc/qa-designer/functional/gui-screen.md +52 -0
- package/skills/qc/qa-designer/integration/api.md +42 -0
- package/skills/qc/qa-designer/integration/db.md +39 -0
- package/skills/qc/qa-designer/integration/gui.md +40 -0
- package/skills/qc/qa-designer/integration/kafka.md +40 -0
- package/skills/qc/qa-designer/non-functional.md +40 -0
- package/skills/qc/qa-planner/test-plan.md +120 -0
- package/skills/qc/qa-reviewer/script/e2e.md +87 -0
- package/skills/qc/qa-reviewer/script/exploratory.md +45 -0
- package/skills/qc/qa-reviewer/script/functional.md +101 -0
- package/skills/qc/qa-reviewer/script/integration.md +91 -0
- package/skills/qc/qa-reviewer/script/non-functional.md +126 -0
- package/skills/qc/qa-reviewer/test-case/e2e.md +73 -0
- package/skills/qc/qa-reviewer/test-case/exploratory.md +43 -0
- package/skills/qc/qa-reviewer/test-case/functional.md +76 -0
- package/skills/qc/qa-reviewer/test-case/integration.md +69 -0
- package/skills/qc/qa-reviewer/test-case/non-functional.md +73 -0
- package/skills/qc/qa-runner/e2e.md +49 -0
- package/skills/qc/qa-runner/exploratory/session.md +36 -0
- package/skills/qc/qa-runner/functional/api.md +35 -0
- package/skills/qc/qa-runner/functional/gui-feature.md +51 -0
- package/skills/qc/qa-runner/functional/gui-screen.md +55 -0
- package/skills/qc/qa-runner/integration.md +47 -0
- package/skills/qc/qa-runner/non-functional.md +49 -0
- package/skills/qc/qa-runner/report/report.md +37 -0
- package/skills/setup-ai-first/SKILL.md +216 -0
- package/skills/setup-ai-first/SKILL.tmpl +116 -0
- package/skills/spec/SKILL.md +461 -0
- package/skills/spec/SKILL.tmpl +174 -0
- package/skills/test/SKILL.md +1297 -0
- package/skills/test/SKILL.tmpl +296 -0
- package/steps/capture-lesson.md +79 -0
- package/steps/context-loader.md +307 -0
- package/steps/gate.md +87 -0
- package/steps/report-footer.md +100 -0
- package/steps/review-fanout.md +138 -0
- package/steps/spawn-agent.md +124 -0
- package/steps/trace-mirror.md +26 -0
- package/templates/architecture.template.md +113 -0
- package/templates/design-spec.template.md +217 -0
- package/templates/feature.template +259 -0
- package/templates/platform-guide.template.md +145 -0
- package/templates/prd.template.md +327 -0
- package/templates/product-definition.template.md +168 -0
- package/templates/project-context.yaml +161 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# .NET Clean Architecture — Code Patterns
|
|
2
|
+
|
|
3
|
+
## Command and Query with MediatR
|
|
4
|
+
|
|
5
|
+
```csharp
|
|
6
|
+
// Application/Orders/Commands/CreateOrder/CreateOrderCommand.cs
|
|
7
|
+
// @trace.implements=ORDER-UC1-SC1
|
|
8
|
+
public record CreateOrderCommand(
|
|
9
|
+
long CustomerId,
|
|
10
|
+
List<OrderItemDto> Items,
|
|
11
|
+
string ShippingAddress
|
|
12
|
+
) : IRequest<CreateOrderResult>;
|
|
13
|
+
|
|
14
|
+
// Application/Orders/Commands/CreateOrder/CreateOrderCommandHandler.cs
|
|
15
|
+
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, CreateOrderResult>
|
|
16
|
+
{
|
|
17
|
+
private readonly IOrderRepository _orderRepository;
|
|
18
|
+
private readonly IUnitOfWork _unitOfWork;
|
|
19
|
+
|
|
20
|
+
public CreateOrderCommandHandler(IOrderRepository orderRepository, IUnitOfWork unitOfWork)
|
|
21
|
+
{
|
|
22
|
+
_orderRepository = orderRepository;
|
|
23
|
+
_unitOfWork = unitOfWork;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public async Task<CreateOrderResult> Handle(CreateOrderCommand command, CancellationToken ct)
|
|
27
|
+
{
|
|
28
|
+
var order = Order.Create(command.CustomerId, command.Items, command.ShippingAddress);
|
|
29
|
+
await _orderRepository.AddAsync(order, ct);
|
|
30
|
+
await _unitOfWork.SaveChangesAsync(ct);
|
|
31
|
+
return new CreateOrderResult(order.Id, order.Status.ToString());
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Application/Orders/Queries/GetOrder/GetOrderQuery.cs
|
|
36
|
+
// @trace.implements=ORDER-UC1-SC2
|
|
37
|
+
public record GetOrderQuery(long OrderId) : IRequest<OrderDto>;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## FluentValidation for Commands
|
|
41
|
+
|
|
42
|
+
```csharp
|
|
43
|
+
// Application/Orders/Commands/CreateOrder/CreateOrderCommandValidator.cs
|
|
44
|
+
public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
|
|
45
|
+
{
|
|
46
|
+
public CreateOrderCommandValidator()
|
|
47
|
+
{
|
|
48
|
+
RuleFor(x => x.CustomerId).GreaterThan(0);
|
|
49
|
+
RuleFor(x => x.Items).NotEmpty().WithMessage("Order must have at least one item.");
|
|
50
|
+
RuleFor(x => x.ShippingAddress).NotEmpty().MaximumLength(500);
|
|
51
|
+
RuleForEach(x => x.Items).ChildRules(item =>
|
|
52
|
+
{
|
|
53
|
+
item.RuleFor(i => i.ProductId).GreaterThan(0);
|
|
54
|
+
item.RuleFor(i => i.Quantity).InclusiveBetween(1, 999);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Domain Entity Pattern
|
|
61
|
+
|
|
62
|
+
```csharp
|
|
63
|
+
// Domain/Entities/Order.cs
|
|
64
|
+
public class Order : BaseEntity
|
|
65
|
+
{
|
|
66
|
+
private readonly List<OrderItem> _items = new();
|
|
67
|
+
|
|
68
|
+
public long CustomerId { get; private set; }
|
|
69
|
+
public OrderStatus Status { get; private set; }
|
|
70
|
+
public string ShippingAddress { get; private set; } = string.Empty;
|
|
71
|
+
public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
|
|
72
|
+
public decimal TotalAmount => _items.Sum(i => i.Subtotal);
|
|
73
|
+
|
|
74
|
+
private Order() { } // EF Core constructor
|
|
75
|
+
|
|
76
|
+
public static Order Create(long customerId, IEnumerable<OrderItemDto> items, string shippingAddress)
|
|
77
|
+
{
|
|
78
|
+
var order = new Order
|
|
79
|
+
{
|
|
80
|
+
CustomerId = customerId,
|
|
81
|
+
ShippingAddress = shippingAddress,
|
|
82
|
+
Status = OrderStatus.Pending,
|
|
83
|
+
CreatedAt = DateTime.UtcNow
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
foreach (var item in items)
|
|
87
|
+
order._items.Add(OrderItem.Create(item.ProductId, item.Quantity, item.UnitPrice));
|
|
88
|
+
|
|
89
|
+
order.AddDomainEvent(new OrderCreatedEvent(order.Id, order.CustomerId));
|
|
90
|
+
return order;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public void Cancel()
|
|
94
|
+
{
|
|
95
|
+
if (Status == OrderStatus.Shipped)
|
|
96
|
+
throw new DomainException("Cannot cancel an order that has already been shipped.");
|
|
97
|
+
Status = OrderStatus.Cancelled;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Repository Implementation with EF Core
|
|
103
|
+
|
|
104
|
+
```csharp
|
|
105
|
+
// Infrastructure/Persistence/Repositories/OrderRepository.cs
|
|
106
|
+
public class OrderRepository : IOrderRepository
|
|
107
|
+
{
|
|
108
|
+
private readonly AppDbContext _context;
|
|
109
|
+
|
|
110
|
+
public OrderRepository(AppDbContext context) => _context = context;
|
|
111
|
+
|
|
112
|
+
public async Task<Order?> GetByIdAsync(long id, CancellationToken ct = default)
|
|
113
|
+
=> await _context.Orders
|
|
114
|
+
.Include(o => o.Items)
|
|
115
|
+
.FirstOrDefaultAsync(o => o.Id == id, ct);
|
|
116
|
+
|
|
117
|
+
public async Task AddAsync(Order order, CancellationToken ct = default)
|
|
118
|
+
=> await _context.Orders.AddAsync(order, ct);
|
|
119
|
+
|
|
120
|
+
public async Task<IReadOnlyList<Order>> GetByCustomerAsync(long customerId, CancellationToken ct = default)
|
|
121
|
+
=> await _context.Orders
|
|
122
|
+
.Where(o => o.CustomerId == customerId)
|
|
123
|
+
.OrderByDescending(o => o.CreatedAt)
|
|
124
|
+
.ToListAsync(ct);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Minimal API Controller Pattern
|
|
129
|
+
|
|
130
|
+
```csharp
|
|
131
|
+
// Presentation/Endpoints/OrderEndpoints.cs
|
|
132
|
+
// @trace.implements=ORDER-UC1
|
|
133
|
+
public static class OrderEndpoints
|
|
134
|
+
{
|
|
135
|
+
public static void MapOrderEndpoints(this IEndpointRouteBuilder app)
|
|
136
|
+
{
|
|
137
|
+
var group = app.MapGroup("/v1/orders").RequireAuthorization();
|
|
138
|
+
|
|
139
|
+
// @trace.implements=ORDER-UC1-SC1
|
|
140
|
+
group.MapPost("/", async (CreateOrderCommand command, IMediator mediator, CancellationToken ct) =>
|
|
141
|
+
{
|
|
142
|
+
var result = await mediator.Send(command, ct);
|
|
143
|
+
return Results.Created($"/v1/orders/{result.Id}", result);
|
|
144
|
+
})
|
|
145
|
+
.WithName("CreateOrder")
|
|
146
|
+
.Produces<CreateOrderResult>(StatusCodes.Status201Created)
|
|
147
|
+
.ProducesValidationProblem();
|
|
148
|
+
|
|
149
|
+
// @trace.implements=ORDER-UC1-SC2
|
|
150
|
+
group.MapGet("/{id:long}", async (long id, IMediator mediator, CancellationToken ct) =>
|
|
151
|
+
{
|
|
152
|
+
var result = await mediator.Send(new GetOrderQuery(id), ct);
|
|
153
|
+
return result is null ? Results.NotFound() : Results.Ok(result);
|
|
154
|
+
})
|
|
155
|
+
.WithName("GetOrder")
|
|
156
|
+
.Produces<OrderDto>()
|
|
157
|
+
.Produces(StatusCodes.Status404NotFound);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
build:
|
|
2
|
+
compile: "dotnet build"
|
|
3
|
+
test: "dotnet test"
|
|
4
|
+
run: "dotnet run --project src/{ProjectName}.Api"
|
|
5
|
+
|
|
6
|
+
architecture:
|
|
7
|
+
style: "Clean Architecture"
|
|
8
|
+
layers:
|
|
9
|
+
- name: "Domain"
|
|
10
|
+
contains: "Entities, Value Objects, Domain Events, Interfaces"
|
|
11
|
+
rules:
|
|
12
|
+
- "No dependencies on outer layers"
|
|
13
|
+
- "Pure business logic only"
|
|
14
|
+
- name: "Application"
|
|
15
|
+
contains: "Commands, Queries, Handlers (MediatR), DTOs, Validators"
|
|
16
|
+
rules:
|
|
17
|
+
- "Depends only on Domain"
|
|
18
|
+
- "All use cases are Commands or Queries"
|
|
19
|
+
- "Validation via FluentValidation"
|
|
20
|
+
- name: "Infrastructure"
|
|
21
|
+
contains: "EF Core DbContext, Repository implementations, External service adapters"
|
|
22
|
+
rules:
|
|
23
|
+
- "Implements interfaces from Application and Domain"
|
|
24
|
+
- "No business logic"
|
|
25
|
+
- name: "Presentation"
|
|
26
|
+
contains: "Controllers (or Minimal API endpoints), Middleware"
|
|
27
|
+
rules:
|
|
28
|
+
- "Dispatches to MediatR only"
|
|
29
|
+
- "No direct service injection (only IMediator)"
|
|
30
|
+
|
|
31
|
+
coding_standards:
|
|
32
|
+
naming:
|
|
33
|
+
classes: "PascalCase"
|
|
34
|
+
methods: "PascalCase"
|
|
35
|
+
interfaces: "IPascalCase (with I prefix)"
|
|
36
|
+
async_methods: "PascalCase + Async suffix (e.g., GetOrderAsync)"
|
|
37
|
+
patterns:
|
|
38
|
+
cqrs: "MediatR — IRequest<T> for commands/queries, IRequestHandler<T,R> for handlers"
|
|
39
|
+
validation: "FluentValidation — one AbstractValidator<T> per command/query"
|
|
40
|
+
error_handling: "Result pattern or domain exceptions, caught by global middleware"
|
|
41
|
+
response_type: "IActionResult with ProblemDetails for errors (RFC 7807)"
|
|
42
|
+
|
|
43
|
+
testing:
|
|
44
|
+
unit: "xUnit + Moq + FluentAssertions"
|
|
45
|
+
integration: "WebApplicationFactory<Program> + Testcontainers"
|
|
46
|
+
patterns:
|
|
47
|
+
- "Arrange / Act / Assert structure"
|
|
48
|
+
- "Method names: MethodName_WhenCondition_ShouldExpectation"
|
|
49
|
+
- "Use FluentAssertions for readable assertions"
|
|
50
|
+
- "Mock only the direct dependencies of the unit under test"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
name: "Flutter"
|
|
2
|
+
version: "1.0.0"
|
|
3
|
+
description: "Cross-platform mobile (iOS + Android) with Flutter/Dart"
|
|
4
|
+
language: "Dart"
|
|
5
|
+
framework: "Flutter"
|
|
6
|
+
stack_type: "mobile"
|
|
7
|
+
default_layer_order:
|
|
8
|
+
- Models / entities (domain)
|
|
9
|
+
- Repositories (data layer)
|
|
10
|
+
- Use cases (domain logic)
|
|
11
|
+
- BLoC / Cubit or Riverpod providers (state)
|
|
12
|
+
- Widgets (presentation)
|
|
13
|
+
- Pages / screens
|
|
14
|
+
test_framework: "flutter_test + mocktail"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
build:
|
|
2
|
+
compile: "flutter build apk / flutter build ios"
|
|
3
|
+
test: "flutter test"
|
|
4
|
+
run: "flutter run"
|
|
5
|
+
lint: "flutter analyze"
|
|
6
|
+
|
|
7
|
+
architecture:
|
|
8
|
+
style: "Feature-based (presentation → domain → data)"
|
|
9
|
+
key_rules:
|
|
10
|
+
- "Widget tree is pure UI — business logic lives in BLoC/Cubit or Riverpod providers"
|
|
11
|
+
- "Repositories abstract data sources; services contain domain logic"
|
|
12
|
+
- "State management: BLoC (complex flows) or Riverpod (simple/medium)"
|
|
13
|
+
- "Never call API directly inside a Widget build() method"
|
|
14
|
+
- "Shared UI widgets in lib/shared/widgets/, feature widgets in lib/features/{name}/presentation/"
|
|
15
|
+
- "Use const constructors wherever possible for performance"
|
|
16
|
+
folder_structure: |
|
|
17
|
+
lib/
|
|
18
|
+
├── core/
|
|
19
|
+
│ ├── theme/ ← AppTheme, colors, text styles
|
|
20
|
+
│ ├── router/ ← GoRouter or auto_route
|
|
21
|
+
│ └── utils/
|
|
22
|
+
├── shared/
|
|
23
|
+
│ └── widgets/ ← reusable UI widgets (AppButton, AppInput...)
|
|
24
|
+
├── features/
|
|
25
|
+
│ └── {domain}/
|
|
26
|
+
│ ├── data/ ← repositories, data sources, models
|
|
27
|
+
│ ├── domain/ ← entities, use cases, repo interfaces
|
|
28
|
+
│ └── presentation/ ← pages, widgets, BLoC/Cubit
|
|
29
|
+
└── main.dart
|
|
30
|
+
|
|
31
|
+
coding_standards:
|
|
32
|
+
naming:
|
|
33
|
+
widgets: "PascalCase (e.g., OrderListPage, CreateOrderForm)"
|
|
34
|
+
blocs: "PascalCase + Bloc/Cubit suffix (e.g., OrderListCubit)"
|
|
35
|
+
repositories: "PascalCase + Repository suffix (e.g., OrderRepository)"
|
|
36
|
+
files:
|
|
37
|
+
page: "{feature}_page.dart"
|
|
38
|
+
widget: "{feature}_widget.dart"
|
|
39
|
+
cubit: "{feature}_cubit.dart"
|
|
40
|
+
repository: "{feature}_repository.dart"
|
|
41
|
+
model: "{feature}_model.dart"
|
|
42
|
+
patterns:
|
|
43
|
+
state_management: "BLoC / Cubit (flutter_bloc) or Riverpod"
|
|
44
|
+
navigation: "GoRouter or auto_route"
|
|
45
|
+
networking: "Dio with interceptors"
|
|
46
|
+
serialization: "json_serializable / freezed"
|
|
47
|
+
|
|
48
|
+
testing:
|
|
49
|
+
unit: "flutter_test + mocktail"
|
|
50
|
+
widget: "flutter_test WidgetTester"
|
|
51
|
+
integration: "integration_test package"
|
|
52
|
+
patterns:
|
|
53
|
+
- "Test BLoC/Cubit in isolation with fake repositories"
|
|
54
|
+
- "Widget tests pump the widget tree and find by key or semantics label"
|
|
55
|
+
- "Use goldenFileComparator for snapshot tests of complex widgets"
|
|
56
|
+
|
|
57
|
+
trace_tags:
|
|
58
|
+
implements: "// @trace.implements={UC-ID}-SC{N}"
|
|
59
|
+
source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# Go — Domain-Driven Layout Code Patterns
|
|
2
|
+
|
|
3
|
+
## Domain Model
|
|
4
|
+
|
|
5
|
+
```go
|
|
6
|
+
// internal/domain/order.go
|
|
7
|
+
package domain
|
|
8
|
+
|
|
9
|
+
import (
|
|
10
|
+
"errors"
|
|
11
|
+
"time"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// OrderStatus represents the lifecycle state of an order.
|
|
15
|
+
type OrderStatus string
|
|
16
|
+
|
|
17
|
+
const (
|
|
18
|
+
OrderStatusPending OrderStatus = "PENDING"
|
|
19
|
+
OrderStatusConfirmed OrderStatus = "CONFIRMED"
|
|
20
|
+
OrderStatusShipped OrderStatus = "SHIPPED"
|
|
21
|
+
OrderStatusCancelled OrderStatus = "CANCELLED"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
// Order is the aggregate root for the order domain.
|
|
25
|
+
type Order struct {
|
|
26
|
+
ID int64
|
|
27
|
+
CustomerID int64
|
|
28
|
+
Status OrderStatus
|
|
29
|
+
Items []OrderItem
|
|
30
|
+
ShippingAddress string
|
|
31
|
+
CreatedAt time.Time
|
|
32
|
+
UpdatedAt time.Time
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// OrderItem represents a single line item within an order.
|
|
36
|
+
type OrderItem struct {
|
|
37
|
+
ProductID int64
|
|
38
|
+
Quantity int
|
|
39
|
+
UnitPrice float64
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// TotalAmount calculates the total order value.
|
|
43
|
+
func (o *Order) TotalAmount() float64 {
|
|
44
|
+
var total float64
|
|
45
|
+
for _, item := range o.Items {
|
|
46
|
+
total += float64(item.Quantity) * item.UnitPrice
|
|
47
|
+
}
|
|
48
|
+
return total
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Cancel transitions the order to CANCELLED status.
|
|
52
|
+
func (o *Order) Cancel() error {
|
|
53
|
+
if o.Status == OrderStatusShipped {
|
|
54
|
+
return errors.New("cannot cancel an order that has already been shipped")
|
|
55
|
+
}
|
|
56
|
+
o.Status = OrderStatusCancelled
|
|
57
|
+
return nil
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Repository Interface (defined in domain)
|
|
62
|
+
|
|
63
|
+
```go
|
|
64
|
+
// internal/domain/order_repository.go
|
|
65
|
+
package domain
|
|
66
|
+
|
|
67
|
+
import "context"
|
|
68
|
+
|
|
69
|
+
// OrderRepository defines persistence operations for orders.
|
|
70
|
+
// Implementations live in internal/repo/.
|
|
71
|
+
type OrderRepository interface {
|
|
72
|
+
GetByID(ctx context.Context, id int64) (*Order, error)
|
|
73
|
+
GetByCustomerID(ctx context.Context, customerID int64) ([]*Order, error)
|
|
74
|
+
Create(ctx context.Context, order *Order) error
|
|
75
|
+
Update(ctx context.Context, order *Order) error
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Use Case (business logic)
|
|
80
|
+
|
|
81
|
+
```go
|
|
82
|
+
// internal/usecase/order_usecase.go
|
|
83
|
+
// @trace.implements=ORDER-UC1
|
|
84
|
+
package usecase
|
|
85
|
+
|
|
86
|
+
import (
|
|
87
|
+
"context"
|
|
88
|
+
"fmt"
|
|
89
|
+
"myapp/internal/domain"
|
|
90
|
+
"time"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
// OrderUseCase handles all order-related business operations.
|
|
94
|
+
type OrderUseCase struct {
|
|
95
|
+
repo domain.OrderRepository
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// NewOrderUseCase creates a new OrderUseCase with required dependencies.
|
|
99
|
+
func NewOrderUseCase(repo domain.OrderRepository) *OrderUseCase {
|
|
100
|
+
return &OrderUseCase{repo: repo}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// CreateOrderRequest holds input data for creating an order.
|
|
104
|
+
type CreateOrderRequest struct {
|
|
105
|
+
CustomerID int64
|
|
106
|
+
Items []domain.OrderItem
|
|
107
|
+
ShippingAddress string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// CreateOrder creates a new order for a customer.
|
|
111
|
+
// @trace.implements=ORDER-UC1-SC1
|
|
112
|
+
func (uc *OrderUseCase) CreateOrder(ctx context.Context, req CreateOrderRequest) (*domain.Order, error) {
|
|
113
|
+
if len(req.Items) == 0 {
|
|
114
|
+
return nil, fmt.Errorf("order must contain at least one item")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
order := &domain.Order{
|
|
118
|
+
CustomerID: req.CustomerID,
|
|
119
|
+
Status: domain.OrderStatusPending,
|
|
120
|
+
Items: req.Items,
|
|
121
|
+
ShippingAddress: req.ShippingAddress,
|
|
122
|
+
CreatedAt: time.Now(),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if err := uc.repo.Create(ctx, order); err != nil {
|
|
126
|
+
return nil, fmt.Errorf("create order: %w", err)
|
|
127
|
+
}
|
|
128
|
+
return order, nil
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// GetOrder retrieves an order by ID.
|
|
132
|
+
// @trace.implements=ORDER-UC1-SC2
|
|
133
|
+
func (uc *OrderUseCase) GetOrder(ctx context.Context, id int64) (*domain.Order, error) {
|
|
134
|
+
order, err := uc.repo.GetByID(ctx, id)
|
|
135
|
+
if err != nil {
|
|
136
|
+
return nil, fmt.Errorf("get order %d: %w", id, err)
|
|
137
|
+
}
|
|
138
|
+
if order == nil {
|
|
139
|
+
return nil, domain.ErrNotFound
|
|
140
|
+
}
|
|
141
|
+
return order, nil
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## HTTP Handler (Gin)
|
|
146
|
+
|
|
147
|
+
```go
|
|
148
|
+
// internal/handler/order_handler.go
|
|
149
|
+
// @trace.implements=ORDER-UC1
|
|
150
|
+
package handler
|
|
151
|
+
|
|
152
|
+
import (
|
|
153
|
+
"errors"
|
|
154
|
+
"net/http"
|
|
155
|
+
"strconv"
|
|
156
|
+
|
|
157
|
+
"github.com/gin-gonic/gin"
|
|
158
|
+
"myapp/internal/domain"
|
|
159
|
+
"myapp/internal/usecase"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
// OrderHandler handles HTTP requests for order operations.
|
|
163
|
+
type OrderHandler struct {
|
|
164
|
+
uc *usecase.OrderUseCase
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// NewOrderHandler creates a new OrderHandler.
|
|
168
|
+
func NewOrderHandler(uc *usecase.OrderUseCase) *OrderHandler {
|
|
169
|
+
return &OrderHandler{uc: uc}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// RegisterRoutes registers all order endpoints on the given router group.
|
|
173
|
+
func (h *OrderHandler) RegisterRoutes(r *gin.RouterGroup) {
|
|
174
|
+
r.POST("/orders", h.CreateOrder) // @trace.implements=ORDER-UC1-SC1
|
|
175
|
+
r.GET("/orders/:id", h.GetOrder) // @trace.implements=ORDER-UC1-SC2
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// CreateOrder handles POST /v1/orders.
|
|
179
|
+
func (h *OrderHandler) CreateOrder(c *gin.Context) {
|
|
180
|
+
var req usecase.CreateOrderRequest
|
|
181
|
+
if err := c.ShouldBindJSON(&req); err != nil {
|
|
182
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
order, err := h.uc.CreateOrder(c.Request.Context(), req)
|
|
186
|
+
if err != nil {
|
|
187
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
c.JSON(http.StatusCreated, order)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// GetOrder handles GET /v1/orders/:id.
|
|
194
|
+
func (h *OrderHandler) GetOrder(c *gin.Context) {
|
|
195
|
+
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
196
|
+
if err != nil {
|
|
197
|
+
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid order id"})
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
order, err := h.uc.GetOrder(c.Request.Context(), id)
|
|
201
|
+
if errors.Is(err, domain.ErrNotFound) {
|
|
202
|
+
c.JSON(http.StatusNotFound, gin.H{"error": "order not found"})
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
if err != nil {
|
|
206
|
+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
c.JSON(http.StatusOK, order)
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Table-Driven Test Pattern
|
|
214
|
+
|
|
215
|
+
```go
|
|
216
|
+
// internal/usecase/order_usecase_test.go
|
|
217
|
+
// @trace.verifies=ORDER-UC1
|
|
218
|
+
// @trace.test_type=unit
|
|
219
|
+
package usecase_test
|
|
220
|
+
|
|
221
|
+
import (
|
|
222
|
+
"context"
|
|
223
|
+
"testing"
|
|
224
|
+
|
|
225
|
+
"github.com/stretchr/testify/assert"
|
|
226
|
+
"github.com/stretchr/testify/mock"
|
|
227
|
+
"myapp/internal/domain"
|
|
228
|
+
"myapp/internal/usecase"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
func TestOrderUseCase_CreateOrder(t *testing.T) {
|
|
232
|
+
tests := []struct {
|
|
233
|
+
name string
|
|
234
|
+
request usecase.CreateOrderRequest
|
|
235
|
+
setupMock func(*MockOrderRepository)
|
|
236
|
+
expectError bool
|
|
237
|
+
expectOrder bool
|
|
238
|
+
}{
|
|
239
|
+
{
|
|
240
|
+
name: "valid order with items should create successfully",
|
|
241
|
+
request: usecase.CreateOrderRequest{
|
|
242
|
+
CustomerID: 1,
|
|
243
|
+
Items: []domain.OrderItem{{ProductID: 10, Quantity: 2, UnitPrice: 99.9}},
|
|
244
|
+
ShippingAddress: "123 Main St",
|
|
245
|
+
},
|
|
246
|
+
setupMock: func(m *MockOrderRepository) {
|
|
247
|
+
m.On("Create", mock.Anything, mock.AnythingOfType("*domain.Order")).Return(nil)
|
|
248
|
+
},
|
|
249
|
+
expectError: false,
|
|
250
|
+
expectOrder: true,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "empty items should return validation error",
|
|
254
|
+
request: usecase.CreateOrderRequest{
|
|
255
|
+
CustomerID: 1,
|
|
256
|
+
Items: []domain.OrderItem{},
|
|
257
|
+
},
|
|
258
|
+
setupMock: func(m *MockOrderRepository) {},
|
|
259
|
+
expectError: true,
|
|
260
|
+
expectOrder: false,
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for _, tt := range tests {
|
|
265
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
266
|
+
mockRepo := new(MockOrderRepository)
|
|
267
|
+
tt.setupMock(mockRepo)
|
|
268
|
+
uc := usecase.NewOrderUseCase(mockRepo)
|
|
269
|
+
|
|
270
|
+
order, err := uc.CreateOrder(context.Background(), tt.request)
|
|
271
|
+
|
|
272
|
+
if tt.expectError {
|
|
273
|
+
assert.Error(t, err)
|
|
274
|
+
assert.Nil(t, order)
|
|
275
|
+
} else {
|
|
276
|
+
assert.NoError(t, err)
|
|
277
|
+
assert.NotNil(t, order)
|
|
278
|
+
}
|
|
279
|
+
mockRepo.AssertExpectations(t)
|
|
280
|
+
})
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
build:
|
|
2
|
+
compile: "go build ./..."
|
|
3
|
+
test: "go test ./..."
|
|
4
|
+
run: "go run ./cmd/{service-name}"
|
|
5
|
+
|
|
6
|
+
architecture:
|
|
7
|
+
style: "Domain-driven layout"
|
|
8
|
+
directory_structure:
|
|
9
|
+
cmd/: "Main entry points, one per binary (e.g., cmd/api/main.go)"
|
|
10
|
+
internal/domain/: "Domain entities, interfaces, and business rules"
|
|
11
|
+
internal/repo/: "Repository implementations (DB, cache)"
|
|
12
|
+
internal/handler/: "HTTP handlers (Gin/Echo) — thin, delegate to use cases"
|
|
13
|
+
internal/usecase/: "Use case implementations (business logic)"
|
|
14
|
+
pkg/: "Reusable packages safe for external use"
|
|
15
|
+
config/: "Configuration loading (env vars, YAML)"
|
|
16
|
+
key_rules:
|
|
17
|
+
- "Define interfaces in the consumer package (dependency inversion)"
|
|
18
|
+
- "internal/ packages cannot be imported outside the module"
|
|
19
|
+
- "Handlers must not contain business logic — only parse, call use case, respond"
|
|
20
|
+
- "Use cases own business rules and call repository interfaces"
|
|
21
|
+
- "Repositories implement persistence — never call use cases"
|
|
22
|
+
|
|
23
|
+
coding_standards:
|
|
24
|
+
naming:
|
|
25
|
+
packages: "lowercase single word (e.g., order, handler, repo)"
|
|
26
|
+
interfaces: "Describe behavior, no 'I' prefix (e.g., OrderRepository, not IOrderRepository)"
|
|
27
|
+
structs: "PascalCase for exported, camelCase for unexported"
|
|
28
|
+
methods: "PascalCase for exported, camelCase for unexported"
|
|
29
|
+
patterns:
|
|
30
|
+
error_handling: "Return error as last return value; use errors.Is/As; wrap with fmt.Errorf(\"...: %w\", err)"
|
|
31
|
+
interfaces: "Define small interfaces at point of use; prefer 1-2 method interfaces"
|
|
32
|
+
constructors: "NewXxx(deps...) *Xxx — always use constructor functions"
|
|
33
|
+
|
|
34
|
+
testing:
|
|
35
|
+
framework: "Standard library testing package + testify"
|
|
36
|
+
patterns:
|
|
37
|
+
- "Table-driven tests for all cases"
|
|
38
|
+
- "Use testify/assert and testify/mock"
|
|
39
|
+
- "Test file: {file}_test.go in same package (white-box) or {pkg}_test package (black-box)"
|
|
40
|
+
- "Integration tests in internal/{pkg}/integration_test.go with build tag //go:build integration"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
name: "iOS (SwiftUI)"
|
|
2
|
+
version: "1.0.0"
|
|
3
|
+
description: "Native iOS development with SwiftUI + Swift"
|
|
4
|
+
language: "Swift"
|
|
5
|
+
framework: "SwiftUI"
|
|
6
|
+
stack_type: "mobile"
|
|
7
|
+
default_layer_order:
|
|
8
|
+
- Models / entities
|
|
9
|
+
- Repository (data sources)
|
|
10
|
+
- UseCase (business operations)
|
|
11
|
+
- ViewModel (ObservableObject state)
|
|
12
|
+
- View (SwiftUI presentation)
|
|
13
|
+
test_framework: "XCTest + Quick/Nimble"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
build:
|
|
2
|
+
compile: "xcodebuild -scheme {AppName} -configuration Release"
|
|
3
|
+
test: "xcodebuild test -scheme {AppName}"
|
|
4
|
+
run: "Open in Xcode → Run (⌘R)"
|
|
5
|
+
lint: "swiftlint"
|
|
6
|
+
|
|
7
|
+
architecture:
|
|
8
|
+
style: "MVVM + Clean Architecture (View → ViewModel → UseCase → Repository)"
|
|
9
|
+
key_rules:
|
|
10
|
+
- "Views are pure SwiftUI — no business logic, no direct API calls"
|
|
11
|
+
- "ViewModels are ObservableObject classes; bind via @StateObject / @ObservedObject"
|
|
12
|
+
- "UseCases encapsulate single business operations"
|
|
13
|
+
- "Repositories abstract data sources (network, cache, local DB)"
|
|
14
|
+
- "Dependency injection via environment or constructor injection"
|
|
15
|
+
- "Use Combine or async/await for async flows — never DispatchQueue in ViewModel"
|
|
16
|
+
folder_structure: |
|
|
17
|
+
Sources/
|
|
18
|
+
├── Core/
|
|
19
|
+
│ ├── Network/ ← URLSession wrapper, interceptors
|
|
20
|
+
│ ├── Theme/ ← Color, Font, Spacing tokens
|
|
21
|
+
│ └── Utils/
|
|
22
|
+
├── Shared/
|
|
23
|
+
│ └── Components/ ← reusable SwiftUI views (AppButton, AppTextField...)
|
|
24
|
+
├── Features/
|
|
25
|
+
│ └── {Domain}/
|
|
26
|
+
│ ├── Views/
|
|
27
|
+
│ ├── ViewModels/
|
|
28
|
+
│ ├── UseCases/
|
|
29
|
+
│ └── Repository/
|
|
30
|
+
|
|
31
|
+
coding_standards:
|
|
32
|
+
naming:
|
|
33
|
+
views: "PascalCase + View suffix (e.g., OrderListView, CreateOrderView)"
|
|
34
|
+
viewmodels: "PascalCase + ViewModel suffix (e.g., OrderListViewModel)"
|
|
35
|
+
usecases: "PascalCase + UseCase suffix (e.g., CreateOrderUseCase)"
|
|
36
|
+
files:
|
|
37
|
+
view: "{Feature}View.swift"
|
|
38
|
+
viewmodel: "{Feature}ViewModel.swift"
|
|
39
|
+
usecase: "{Feature}UseCase.swift"
|
|
40
|
+
patterns:
|
|
41
|
+
async: "async/await + Combine"
|
|
42
|
+
navigation: "NavigationStack (iOS 16+)"
|
|
43
|
+
persistence: "Core Data or SwiftData"
|
|
44
|
+
networking: "URLSession or Alamofire"
|
|
45
|
+
|
|
46
|
+
testing:
|
|
47
|
+
unit: "XCTest + Quick/Nimble"
|
|
48
|
+
ui: "XCUITest"
|
|
49
|
+
patterns:
|
|
50
|
+
- "Test ViewModel by injecting mock repositories"
|
|
51
|
+
- "UI tests use accessibility identifiers — set .accessibilityIdentifier on Views"
|
|
52
|
+
|
|
53
|
+
trace_tags:
|
|
54
|
+
implements: "// @trace.implements={UC-ID}-SC{N}"
|
|
55
|
+
source: "// @trace.source=specs/{domain}/{prd-slug}/bdd/{UC-ID}.feature"
|