# pkg/storage - Data Access Layer

Repository interfaces, MySQL storage, etcd CRDs, and migrations.

## OVERVIEW

Data persistence layer implementing repository pattern. MySQL for relational data, etcd for K8s CRDs. All business logic accesses data through `repo` interfaces.

## STRUCTURE

```
storage/
├── repo/                  # Repository interfaces (source of truth for contracts)
│   ├── *.go               # Interface definitions
│   └── mocks/             # Generated testify mocks
├── mysql/                 # MySQL implementations
│   ├── schema/            # GORM model definitions
│   ├── migrate/           # Database migrations
│   │   ├── db/            # Schema migrations
│   │   └── deploy/        # Deployment migrations
│   └── *.go               # Repository implementations
├── etcd/                  # etcd/K8s CRD storage
│   └── schema/v1/         # CRD type definitions (controller-gen source)
└── cache/                 # Caching layer
```

## WHERE TO LOOK

| Task | Location | Notes |
|------|----------|-------|
| Add repository interface | `repo/*.go` | Define interface first |
| Add MySQL table | `mysql/schema/` | GORM struct + migration |
| Add migration | `mysql/migrate/db/` | Numbered migration file |
| Add K8s CRD | `etcd/schema/v1/` | Run `make manifests` after |
| Mock for testing | `repo/mocks/` | Auto-generated |

## CONVENTIONS

### Repository Interface Pattern
```go
// In repo/sharedpool.go
type SharedPoolRepo interface {
    Load(ctx context.Context, id string, forUpdate bool) (*entity.SharedPool, error)
    Save(ctx context.Context, sp *entity.SharedPool) error
    List(ctx context.Context, opts ...ListOption) ([]*entity.SharedPool, error)
}
```

### MySQL Implementation
```go
// In mysql/sharedpool.go
type sharedPoolRepo struct {
    db *gorm.DB
}

func (r *sharedPoolRepo) Load(ctx context.Context, id string, forUpdate bool) (*entity.SharedPool, error) {
    var model schema.SharedPool
    query := r.db.WithContext(ctx)
    if forUpdate {
        query = query.Clauses(clause.Locking{Strength: "UPDATE"})
    }
    if err := query.Where("id = ?", id).First(&model).Error; err != nil {
        return nil, err
    }
    return model.ToEntity(), nil
}
```

### Migration Naming
- Format: `YYYYMMDDHHMMSS_description.go`
- Example: `20260115100000_add_nodegroup_table.go`

## ANTI-PATTERNS

- **NEVER** access `mysql/` or `etcd/` directly from domain layer
- **NEVER** use raw SQL; use GORM query builder
- **NEVER** skip `forUpdate` parameter when loading for modification
