# GORM Gen Library Summary (PostgreSQL Extended Version)
This document summarizes the capabilities of the GORM Gen code generation tool, specifically focusing on its extended version tailored for PostgreSQL. It covers standard Gen features and the substantial PostgreSQL-specific enhancements for types and field expressions.
## 1. DAO Interface Generation
- **Concept**: Generates type-safe Data Access Object (DAO) interfaces and query code.
- **Process**:
- **Configuration**: Use `gen.Config` to set output paths, package names, and modes.
- **PostgreSQL Enforcement**: The generator explicitly requires a PostgreSQL database connection via `g.UseDB(db)` (checks for "postgres" dialector).
- **Model Application**: Automatically maps database tables to Go structs using `g.GenerateAllTable()` or specific tables with `g.GenerateModel()`.
- **Output**: Generates DAO interfaces with CRUD methods, query structs, and model structs. Defaults to "Same Package" generation (models and queries in the same directory) for easier usage.
- **Usage**: Interact via a global `Q` variable or initialized query instances.
- Uses `JSONSet` expression for `JSONB_SET` operations.
- Example: `UpdateColumn("attr", types.JSONSet("attr").Set("{age}", 20))` updates a specific path inside a JSONB column without overwriting the whole document.
- **Modifiers**: `Select`, `Omit`.
## 5. Deleting Records
- **Safety**: Requires `Where` clause for bulk deletes.
- **Soft Delete**: Automatically handled if `gorm.DeletedAt` is present.
- **Associations**: Can delete specific associated records.
- **Eager Loading**: `Preload()` with conditions and nested paths.
- **Operations**: `Append`, `Replace`, `Delete`, `Clear` on associations.
## 8. PostgreSQL Specialized Extensions (Unique to this version)
This version of Gen is heavily customized for PostgreSQL, providing rich type support and SQL expressions that standard GORM Gen does not offer out-of-the-box.
### 8.1. Extended Type System (`go.ipao.vip/gen/types`)
Automatically maps PostgreSQL column types to specialized Go types:
- **Generics**: `types.JSONType[T]` for strong typing of JSON column content.
### 8.2. Extended Field Expressions (`go.ipao.vip/gen/field`)
Provides type-safe builders for PostgreSQL operators:
- **JSONB Querying**:
```go
// Query: attributes -> 'role' ? 'admin'
db.Where(u.Attributes.HasKey("role"))
// Query: attributes ->> 'age' > 18
db.Where(u.Attributes.KeyGt("age", 18))
```
- **Array Operations**:
```go
// Query: tags @> '{urgent}'
db.Where(u.Tags.Contains("urgent"))
```
- **Range Overlaps**:
```go
// Query: duration && '[2023-01-01, 2023-01-02)'
db.Where(u.Duration.Overlaps(searchRange))
```
### 8.3. Configuration & Generation
- **YAML Config**: Supports loading configuration from a `.transform.yaml` file (handling field type overrides, ignores, and relationships).
- **Auto Mapping**: `defaultDataTypeMap` in the generator automatically selects the correct extended type (e.g., `int4range` -> `types.Int4Range`) without manual config.
- **Field Wrappers**: Automatically wraps generated fields with their specific expression builders (e.g., a `jsonb` column generates a `field.JSONB` struct instead of a generic `field.Field`, enabling the `.HasKey()` method).
- DO regenerate code after changes (routes/docs/models).
- MUST: in `app/services`, prefer the generated GORM-Gen DAO (`database/models/*`) for DB access ; treat raw `*gorm.DB` usage as a last resort.
- MUST: after adding/removing/renaming any files under `app/services/`, run `atomctl gen service --path ./app/services` to regenerate `app/services/services.gen.go` ; DO NOT edit `services.gen.go` manually.
- MUST: a single service's methods MUST live in a single file ; do NOT split one service across multiple files (e.g. `type user struct{}` in `user.go` but methods in `user_admin.go`), because `atomctl gen service` uses filenames to infer services and will generate incorrect `services.gen.go`.
- DO add `// @provider` above every controller/service `struct` declaration.
- DO keep HTTP middlewares in `app/middlewares/` only.
- DO keep all `const` declarations in `pkg/consts/` only (do not declare constants elsewhere).
- DO NOT manually edit generated files:
- `backend/app/http/**/routes.gen.go`
- `backend/app/http/**/provider.gen.go`
- `backend/docs/docs.go`
- `app/http/**/routes.gen.go`
- `app/http/**/provider.gen.go`
- `docs/docs.go`
- DO NOT manually write provider declarations (only `atomctl gen provider`).
- DO NOT manually write route declarations (only `atomctl gen route`).
- DO keep Swagger annotations consistent with actual Fiber route paths (including `:param`).
- MUST: route path parameter placeholders MUST be `camelCase` (e.g. `:tenantCode`), never `snake_case` (e.g. `:tenant_code`).
- MUST: when importing another HTTP module's `dto` package, the import alias MUST be `<module>_dto` (e.g. `tenant_dto`), not `<module>dto` (e.g. `tenantdto`).
- MUST: when creating/generating Go `struct` definitions (DTOs/requests/responses/etc.), add detailed per-field comments describing meaning, usage scenario, and validation/usage rules (do not rely on “self-explanatory” names).
- MUST: business code comments MUST be written in Chinese (中文注释), to keep review/maintenance consistent across the team.
- MUST: in `app/services`, add Chinese comments at key steps to explain business intent and invariants (e.g., 事务边界、幂等语义、余额冻结/扣减/回滚、权限/前置条件校验点), avoid “what the code does” boilerplate.
---
@@ -21,11 +35,12 @@ This file condenses `backend/docs/dev/http_api.md` + `backend/docs/dev/model.md`
### 1.1 Where code lives
- Controllers: `backend/app/http/<module>/*.go`
- Example module: `backend/app/http/super/tenant.go`, `backend/app/http/super/user.go`
- Use REST client examples: `backend/test/[module]/[controller].http` (extend it for new endpoints)
- Use REST client examples: `tests/[module]/[controller].http` (extend it for new endpoints)
### 1.7 Testing
- Prefer existing test style under `backend/tests/e2e`.
- Prefer existing test style under `tests/e2e`.
- Run: `make test`
### 1.8 Module-level route group (Path + Middlewares)
If you need to define a module HTTP middleware (applies to the module route group):
1) Run `atomctl gen route` first.
2) Edit `app/http/<module>/routes.manual.go`:
- Update `Path()` to return the current module route group prefix (must match the prefix used in `routes.gen.go`, e.g. `/super/v1`, `/t/:tenantCode/v1`).
- Update `Middlewares()` return value: return a list like `[]any{r.middlewares.MiddlewareFunc1, r.middlewares.MiddlewareFunc2, ...}` (no `(...)`), where each item is `r.middlewares.<MiddlewareFunc>` referencing middleware definitions in `app/middlewares`.
---
## 2) Add / update a DB model
Models live in:
- `backend/database/models/*` (generated model code + optional manual extensions)
- `database/models/*` (generated model code + optional manual extensions)
### 2.1 Migration → model generation workflow
@@ -118,6 +142,7 @@ Models live in:
- No explicit `BEGIN/COMMIT` needed (framework handles).
- Table name should be plural (e.g. `tenants`).
- MUST: when writing migration content, every field/column MUST include a brief Chinese remark, and also include commented details for that field’s usage scenario and rules/constraints (e.g., valid range/format, default behavior, special cases).
3) Apply migration:
@@ -125,16 +150,106 @@ Models live in:
4) Map complex field types (JSON/ARRAY/UUID/…) via transform file:
- Define enums in Go under `backend/pkg/consts/<table>.go`, example:
- Define enums in Go under `pkg/consts/<table>.go`, example:
```go
// swagger:enum UserStatus
@@ -142,15 +257,50 @@ Models live in:
type UserStatus string
```
- For every enum `type` defined under `pkg/consts/`, you MUST also define:
- `Description() string`: return the Chinese label for the specific enum value (used by API/FE display).
- `XxxItems() []requests.KV`: return the KV list for FE dropdowns (typically `Key=enum string`, `Value=Description()`). Example: `func TenantStatusItems() []requests.KV` and call it via `consts.TenantStatusItems()`.
- Prefer `string(t)` as `Key`, and use a stable default label for unknown values (e.g. `未知` / `未知状态`).
- MUST: `Description()` and `XxxItems()` MUST be placed immediately below the enum `type` definition (same file, directly under `type Xxx string`), to keep the enum self-contained and easy to review.
- Generate enum code: `atomctl gen enum`
### 2.3 Supported field types (`gen/types/`)
`backend/database/.transform.yaml` typically imports `go.ipao.vip/gen` so you can use `types.*` in `field_type`.
`database/.transform.yaml` typically imports `go.ipao.vip/gen` so you can use `types.*` in `field_type`.
- `atomctl gen model` / `atomctl gen enum` / `atomctl gen service`
- `make init` (full refresh)
---
## 5) Service Layer Unit Testing Guidelines (Generic)
This section is framework-agnostic and applies to any Go service layer (regardless of DI container, ORM, or web framework).
### 5.1 Decide what you are testing
- **Pure unit tests**: no DB/network/filesystem ; dependencies are mocked/faked; tests are fast and deterministic.
- **DB-backed tests (recommended whenever the feature touches the database)**: exercise a real database to validate SQL, constraints, transactions, and ORM behavior.
- Always state which tier the test belongs to and keep the scope consistent.
### 5.2 Design the service for testability
- Inject dependencies via constructor or fields ; depend on **interfaces**, not concrete DB clients.
- Keep domain logic **pure** where possible: parse/validate/compute should be testable without IO.
- Make time/UUID/randomness deterministic by injecting `Clock`/`IDGenerator` when needed.
- If the feature requires database access, **do not mock the database** ; test with an **actual database** (ideally same engine/version as production) to ensure data accuracy. Use mocks/fakes only for non-DB external dependencies when appropriate (e.g., HTTP, SMS, third-party APIs).
### 5.3 Test structure and conventions
- Prefer `*_test.go` with table-driven tests and subtests: `t.Run("case", func(t *testing.T) { ... })`.
- Prefer testing the public API from an external package (`package xxx_test`) unless you must access unexported helpers.
- Avoid “focused” tests in committed code (e.g. `FocusConvey`, `FIt`, `fit`, `it.only`, or equivalent), because they silently skip other tests.
- MUST: in service layer tests, **one test method should focus on one service method** only (e.g. `Test_Freeze` covers `Ledger.Freeze`, `Test_Unfreeze` covers `Ledger.Unfreeze`) ; do not bundle multiple service methods into a single `Test_*` method.
- MUST: within that single `Test_<Method>` function, cover the method’s key behavior contracts and boundary conditions via subcases (`Convey` blocks or `t.Run`) so the method’s behavior can be reviewed in one place (do NOT claim to cover “all edge cases”, but cover the important ones).
- MUST (minimum set): for each service method test, cover at least: happy path ; invalid params / precondition failures; insufficient resources / permission denied (if applicable); idempotency/duplicate call behavior (if applicable); and at least one typical persistence/transaction failure branch (if it is hard to simulate reliably, move that branch coverage to a DB-backed integration/e2e test).
### 5.4 Isolation rules
- Each test must be independent and order-agnostic.
- For integration tests:
- Use transaction rollback per test when possible ; otherwise use truncate + deterministic fixtures.
- Never depend on developer-local state ; prefer ephemeral DB (container) or a dedicated test database/schema.
### 5.5 Assertions and error checks
- Always assert both **result** and **error** (and error types via `errors.Is` / `errors.As` when wrapping is used).
- Keep assertions minimal but complete: verify behavior, not implementation details.
- Use the standard library (`testing`) or a single assertion library consistently across the repo.
### 5.6 Minimal test file template (DI-bootstrapped, DB-backed)
This template matches a common pattern where tests boot a DI container and run against a real database. Replace the bootstrap (`testx.Default/Serve`, `Provide`) and cleanup (`database.Truncate`) with your project's equivalents.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.