# Architecture Rules (ARCH-01..03) ## ARCH-01: Standard Directory Layout (CRITICAL) Every service MUST follow this directory structure: ``` / ├── domain// # 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), }, } } ```