---
name: database-model-designer
description: Use when designing or modifying internal/store model structs, database schema, or data-layer types. Enforces the rule that store model structs must never carry JSON tags — they represent persistence projections, not wire formats.
---

# Database Model Designer

## Core rule

`internal/store/model` structs are **data-layer persistence projections**. They must never carry `json` struct tags or be used directly as API response/request types. Wire format types belong in `internal/api/dto`.

## Model struct conventions

- Define model structs in `internal/store/model` or `internal/store/{domain}/model.go`.
- Model structs represent database row shapes — field names map to column names via struct field naming conventions or `db` tags.
- **Never add `json` tags to model structs.** If a model needs to be serialized to JSON, convert it to a DTO in the API/mapper layer.
- Model structs may have `db` tags for sqlx/coredb scanning if needed.
- Model structs may have `yaml` tags only for internal configuration purposes (not for API serialization).

## Schema changes

- The canonical TiDB schema lives in `internal/tidb/bootstrap.sql` and `internal/tidb/migrations/*.sql`.
- Any model change that affects the schema must have a corresponding migration file.
- Do not update one path (model or migration) without checking whether the other must change.
- Read `specs/storage/storage.md` and the relevant current implementation notes before schema changes.

## Model → DTO conversion

When an API handler needs to return data from a model:

1. The API layer calls a service or store method that returns a model struct.
2. A **mapper/presenter** function converts the model to a DTO defined in `internal/api/dto`.
3. The handler returns the DTO, never the model directly.

```go
// In internal/api/{domain}/presenters.go
func messageFromModel(m model.Message) dto.Message {
    return dto.Message{
        ID:        m.ID,
        Content:   m.Content,
        CreatedAt: m.CreatedAt,
    }
}
```

## What belongs in model vs DTO

| Aspect | Model (internal/store/model) | DTO (internal/api/dto) |
|--------|------------------------------|------------------------|
| JSON tags | **Never** | Always |
| db tags | Allowed | Never |
| Represents | Database row | API request/response |
| Ownership | Store layer | API layer |
| Validation | DB constraints | API input validation |

## Anti-patterns to avoid

- Do not embed a model struct as an anonymous field in a DTO to "reuse" JSON tags.
- Do not add `json:"-"` to model fields to "hide" them — just don't put json tags on models at all.
- Do not return `*model.X` from API handler methods.
- Do not use model types as function parameters in API handler code.
