threedotslab/references/rules-architecture.md

111 lines
3.9 KiB
Markdown

# Architecture Rules (ARCH-01..03)
## ARCH-01: Standard Directory Layout (CRITICAL)
Every service MUST follow this directory structure:
```
<service>/
├── domain/<aggregate>/ # Pure business logic, entities, value objects, repository interfaces
├── app/ # Application struct (app.go) with Commands + Queries
│ ├── command/ # Write use cases (command handlers)
│ └── query/ # Read use cases (query handlers + read model interfaces)
├── ports/ # Inbound adapters: HTTP handlers, gRPC servers, CLI
├── adapters/ # Outbound adapters: repository implementations, external clients
└── service/ # Composition root: wires all dependencies together
```
**Check procedure:**
1. Glob for these directories relative to the service root
2. Flag any missing standard directories
3. Flag any non-standard directories at the same level (e.g., `controllers/`, `models/`, `handlers/`)
4. Multiple aggregates can exist under `domain/` as sub-packages (e.g., `domain/hour/`, `domain/training/`)
**Reference (wild-workouts):**
```
internal/trainer/
├── domain/hour/
├── app/
│ ├── command/
│ └── query/
├── ports/
├── adapters/
└── service/
```
---
## ARCH-02: Dependency Direction (CRITICAL)
Dependencies MUST flow inward only: `ports/adapters → app → domain`
The domain layer MUST NOT import from:
- `app/`, `app/command/`, `app/query/`
- `ports/`
- `adapters/`
- Any external infrastructure package (database drivers, HTTP frameworks, etc.)
The app layer MUST NOT import from:
- `ports/`
- `adapters/`
**Check procedure:**
1. For every `.go` file in `domain/`, scan import statements
2. Flag any import that references `app/`, `ports/`, `adapters/`, or the service's own non-domain packages
3. For every `.go` file in `app/`, scan imports for `ports/` or `adapters/`
4. Domain MAY import standard library and pure utility packages
**Allowed domain imports:**
- Standard library (`context`, `time`, `errors`, `fmt`, `strings`, etc.)
- Pure value libraries (e.g., `github.com/google/uuid`)
- NOT: database drivers, HTTP routers, gRPC, logging libraries
---
## ARCH-03: Composition Root in service/ (WARNING)
All dependency wiring MUST happen in `service/application.go` (or equivalent in `service/`).
This file:
- Creates infrastructure clients (database connections, external service clients)
- Instantiates adapters (repositories, gRPC clients)
- Instantiates command/query handlers with their dependencies
- Returns a fully-wired `app.Application` struct
- Is the ONLY place that knows about concrete adapter types
**Check procedure:**
1. Look for `service/` directory and `application.go` or similar
2. Verify it returns `app.Application`
3. Check that adapter constructors are NOT called outside `service/`
**Reference:**
```go
// service/application.go
func NewApplication(ctx context.Context) app.Application {
// Create infrastructure
firestoreClient, err := firestore.NewClient(ctx, os.Getenv("GCP_PROJECT"))
// ...
// Create domain factories
hourFactory, err := hour.NewFactory(hour.FactoryConfig{...})
// Create adapters
hourRepository := adapters.NewFirestoreHourRepository(firestoreClient, hourFactory)
// Wire application
logger := logrus.NewEntry(logrus.StandardLogger())
metricsClient := metrics.NoOp{}
return app.Application{
Commands: app.Commands{
CancelTraining: command.NewCancelTrainingHandler(hourRepository, logger, metricsClient),
ScheduleTraining: command.NewScheduleTrainingHandler(hourRepository, logger, metricsClient),
},
Queries: app.Queries{
HourAvailability: query.NewHourAvailabilityHandler(hourRepository, logger, metricsClient),
TrainerAvailableHours: query.NewAvailableHoursHandler(datesRepository, logger, metricsClient),
},
}
}
```