Files
ForgeLM/.agents/rules/code-organization-principles.md

3.7 KiB

trigger
trigger
always_on

Code Organization Principles

  • Generate small, focused functions with clear single purposes (typically 10-50 lines)
  • Keep cognitive complexity low (cyclomatic complexity < 10 for most functions)
  • Maintain clear boundaries between different layers (presentation, business logic, data access)
  • Design for testability from the start, avoiding tight coupling that prevents testing
  • Apply consistent naming conventions that reveal intent without requiring comments

Module Boundaries

Problem: Cross-module coupling makes changes ripple across codebase.

Solution: Feature-based organization with clear public interfaces:

  • One feature = one directory
  • Each module exposes a public API (exported functions/classes)
  • Internal implementation details are private
  • Cross-module calls only through public API

Directory Structure (Language-Agnostic):

Paths below are illustrative examples following project-structure.md — the single source of truth for project layout. Note: The generic filenames below are illustrative. Always use language-specific naming conventions from the relevant project-structure-* file (e.g., storage.go in Go, task.api.ts in TypeScript, task_repository.dart in Dart).

/task

- public_api.{ext}      # Exported interface
- business.{ext}        # Pure logic
- store.{ext}           # I/O abstraction (interface)
- postgres.{ext}        # I/O implementation
- mock.{ext}            # Test implementation
- test.{ext}            # Unit tests (mocked I/O)
- integration.test.{ext} # Integration tests (real I/O)

Go Example:

apps/backend/internal/features/task/

- service.go            # Public API of this feature (Service struct)
- handler.go            # HTTP handlers
- logic.go              # Pure domain logic
- storage.go            # interface Storage
- storage_pg.go         # implements Storage (PostgreSQL)
- storage_mock.go       # Mock implementation for testing
- logic_test.go         # Unit tests with MockStorage
- postgres_integration_test.go # Integration test with Testcontainers

Vue Example:

/apps/frontend/src/features/task

- index.ts              # Public exports
- task.service.ts       # Business logic
- task.api.ts           # interface TaskAPI
- task.api.backend.ts   # implements TaskAPI
- task.store.ts         # Pinia store (uses TaskAPI)
- task.service.spec.ts  # Unit tests (mock API)

Feature Interaction Patterns

Direct Import

When a feature needs another feature's capabilities, import its Service directly:

// In features/order/logic.go
import "yourapp/internal/features/task"

type Logic struct {
    taskService *task.Service  // Direct dependency injection
}

func (l *Logic) CreateOrder(ctx context.Context, req CreateOrderRequest) error {
    // Use task service directly
    task, err := l.taskService.GetTask(ctx, req.TaskID)
    // ... rest of logic
}

Rules

  • Only import Service (public API), never internal files like logic.go or storage.go
  • Declare dependency in the dependent feature's Service constructor
  • Wire dependencies in cmd/api/main.go

Wiring Example

// cmd/api/main.go
taskService := task.NewService(taskStorage)
orderService := order.NewService(orderStorage, taskService) // Pass task service

Avoid Circular Dependencies

Problem: Module A imports B, B imports A

  • Causes build failures, initialization issues
  • Indicates poor module boundaries

Solution:

  • Extract shared code to third module
  • Restructure dependencies (A→C, B→C)
  • Use dependency injection
  • Core Design Principles @core-design-principles.md
  • Project Structure @project-structure.md
  • Architectural Patterns — Testability-First Design @architectural-pattern.md