3.9 KiB
3.9 KiB
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:
- Glob for these directories relative to the service root
- Flag any missing standard directories
- Flag any non-standard directories at the same level (e.g.,
controllers/,models/,handlers/) - 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:
- For every
.gofile indomain/, scan import statements - Flag any import that references
app/,ports/,adapters/, or the service's own non-domain packages - For every
.gofile inapp/, scan imports forports/oradapters/ - 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.Applicationstruct - Is the ONLY place that knows about concrete adapter types
Check procedure:
- Look for
service/directory andapplication.goor similar - Verify it returns
app.Application - Check that adapter constructors are NOT called outside
service/
Reference:
// 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),
},
}
}