DAO Layer
The DAO (Data Access Object) layer lives under biz/dal/ and isolates persistence from business logic. It groups data access objects by backend type, registers them through a central manager, and keeps the data shapes in dedicated models/ and do/ packages. ftkit verify enforces the conventions below.
Layout
The data access layer sits under biz/dal/:
biz/dal/
├── dao/ # data access objects, grouped by backend type
│ ├── managers.go # DAO registry: constants, init(), RegisterDAO
│ ├── redis_dao/ # cache DAOs (Redis)
│ ├── mongo_dao/ # MongoDB DAOs
│ ├── mysql_dao/ # MySQL DAOs
│ ├── postgres_dao/ # PostgreSQL DAOs
│ ├── cql_dao/ # Cassandra/CQL DAOs
│ └── elastic_dao/ # Elasticsearch DAOs
├── models/ # persistence models (database row/document shapes)
└── do/ # data objects used by the cache layerftkit verify (verifyDAOLayer) checks that biz/dal/dao/ exists, that dao/managers.go is present, and recognises the DAO type sub-directories listed above (redis_dao, mongo_dao, mysql_dao, postgres_dao, cql_dao, elastic_dao). It also looks for biz/dal/models/.
Like controllers, DAO files describe access logic, not data. verifyNoDataStructs rejects any struct in a DAO file whose name does not end in DAO / Dao or Cache; all other shapes belong in biz/dal/do or biz/dal/models.
Managers
dao/managers.go is the registry that names every backend and registers the DAO factories. ftkit verify looks for three things in it:
- Constants — identifiers ending in
_CACHE,_DB, or_STOthat name each cache, database, and storage backend. init()— the function where registration happens at package load.RegisterDAO— the registration calls that bind each DAO factory to its backend.
// biz/dal/dao/managers.go
package dao
const (
SERVICE_CACHE = "service.cache"
SERVICE_DB = "service.db"
SERVICE_STO = "service.sto"
)
func init() {
RegisterDAO(SERVICE_DB, func() any { return mongo_dao.NewOrdersDAO() })
RegisterDAO(SERVICE_CACHE, func() any { return redis_dao.NewOrdersCache() })
}Import rules
DAO packages may import only the data packages appropriate to their tier. This is the intended standard (the corresponding verifier check, verifyDAOImportRules, is currently disabled in code via an if false guard, but it documents the rule the layout is built around):
- Database DAOs —
mongo_dao,mysql_dao,postgres_dao,cql_dao,elastic_dao— importbiz/dal/modelsonly, neverbiz/dal/do. Database DAOs deal in persistence models. - Cache DAOs —
redis_dao— may import bothbiz/dal/modelsandbiz/dal/do, since the cache layer works with the data objects (do) as well as models.
Following this rule keeps the dependency direction clean: persistence models stay free of cache-only concerns, and the cache tier is the only place that bridges the two.
Ownership
Each DAO type is owned by exactly one controller, and the functions that interact with a given DAO live in a single file. ftkit verify (verifyDAOOwnership) collects the exported DAO/Cache structs from each *_dao/ sub-directory and scans the controllers to enforce both rules:
- One owner per DAO — if the same DAO type is referenced as a field across more than one controller domain, the verifier errors. Each DAO belongs to a single controller.
- One file per DAO interaction — within a controller package, the functions that call a given DAO must all sit in one file (the controller struct definition in
controller.gois excluded). Spreading DAO calls across several files is an error.
This keeps data access for each backend localised and traceable to a single controller and a single file.