threedotslab/references/rules-architecture.md

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:

  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:

// 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),
        },
    }
}