Update studio-backend/guidelines-repository.md

This commit is contained in:
2025-09-14 06:51:33 +00:00
parent 29d5f4a7ca
commit 54d2311ebb

View File

@ -1,276 +1,52 @@
# Repository Pattern Guidelines for New Developers
# Guidelines on Repository
## Overview
This guide explains how to properly define repositories in our codebase using the MetaORM framework. The repository pattern provides a clean separation between data access logic and business logic.
## Repository Structure
### 1. Repository Interface
Define a repository interface that extends the base MetaORM repository:
## Basic structure of repository for entity with workspace
```go
type EntityNameRepository interface {
metaorm.Repository[EntityName]
// Custom query methods with descriptive names
FindByEmail(email string) (*EntityName, error)
FindByUserIdAndWorkspaceId(userId uint, workspaceId uint) (*EntityName, error)
FilteredPagedFindMany(
page metaorm.PaginationQuery,
sort metaorm.SortingQuery,
keywordFilter metaorm.Query,
filterParam uint,
) ([]EntityName, *metaorm.Pagination, error)
import "git.metadiv.io/metadiv-studio/metaorm"
func NewWorkspaceMemberLimitRepository(db metaorm.Database, workspaceId uint) WorkspaceMemberLimitRepository {
return &WorkspaceMemberLimitRepositoryImpl{
WorkspaceRepositoryImpl: metaorm.NewWorkspaceRepositoryImpl[WorkspaceMemberLimit](db, workspaceId),
}
}
type WorkspaceMemberLimitRepository interface {
metaorm.WorkspaceRepository[WorkspaceMemberLimit]
}
type WorkspaceMemberLimitRepositoryImpl struct {
metaorm.WorkspaceRepositoryImpl[WorkspaceMemberLimit]
}
```
### 2. Repository Implementation
- Must having NewXXXXRepository method.
- Must having XXXXRepository interface.
- Must having XXXXRepositoryImpl struct.
- The XXXX is entity object.
Implement the repository with proper query building:
## Basic structure of repository for entity without workspace
```go
type EntityNameRepositoryImpl struct {
metaorm.RepositoryImpl[EntityName]
}
```
import "git.metadiv.io/metadiv-studio/metaorm"
### 3. Constructor Function
Always provide a constructor function:
```go
func NewEntityNameRepository(db metaorm.Database) EntityNameRepository {
return &EntityNameRepositoryImpl{
RepositoryImpl: metaorm.NewRepositoryImpl[EntityName](db),
}
}
```
## Query Building Patterns
### 1. Simple Field Queries
```go
func (r *RepositoryImpl) FindByEmail(email string) (*Entity, error) {
qb := metaorm.NewQueryBuilder()
return r.Query(qb.Field("email").Eq(email)).FindOne()
}
func (r *RepositoryImpl) FindById(id uint) (*Entity, error) {
qb := metaorm.NewQueryBuilder()
return r.Query(qb.Field("id").Eq(id)).FindOne()
}
```
### 2. Multiple Field Queries
```go
func (r *RepositoryImpl) FindByUserIdAndWorkspaceId(userId uint, workspaceId uint) (*Entity, error) {
qb := metaorm.NewQueryBuilder()
return r.
Query(qb.And(
qb.Field("user_id").Eq(userId),
qb.Field("workspace_id").Eq(workspaceId),
)).
Preload("Workspace").
Preload("User").
FindOne()
}
```
### 3. Advanced Filtered Queries (Updated Pattern)
Use generic interfaces for better flexibility and cleaner code:
```go
func (r *RepositoryImpl) FilteredPagedFindMany(
page metaorm.PaginationQuery,
sort metaorm.SortingQuery,
keywordFilter metaorm.Query,
workspaceId uint,
) ([]Entity, *metaorm.Pagination, error) {
qb := metaorm.NewQueryBuilder()
queries := make([]metaorm.Query, 0)
// Base filter
queries = append(queries, qb.Field("workspace_id").Eq(workspaceId))
// Add optional keyword filter
if keywordFilter != nil {
queries = append(queries, keywordFilter)
}
return r.
Query(qb.And(queries...)).
Joins("User").
Preload("Workspace").
Pagination(page).
Sorting(sort).
PagedFindMany()
}
```
**Key improvements in this pattern:**
- Use `metaorm.PaginationQuery` and `metaorm.SortingQuery` interfaces instead of concrete types
- Accept `metaorm.Query` for keyword filters instead of string + manual query building
- Let the caller build the keyword filter query for better separation of concerns
- More flexible and reusable approach
### 4. Special Query Methods
```go
// Unscoped queries (include soft-deleted records)
func (r *RepositoryImpl) UnscopedFindByUserEmail(email string, workspaceId uint) (*Entity, error) {
qb := metaorm.NewQueryBuilder()
return r.
Query(qb.And(
qb.Field("User.email").Eq(email),
qb.Field("workspace_members.workspace_id").Eq(workspaceId),
)).
Joins("User").
Preload("Workspace").
Unscoped().
FindOne()
}
// Find with exclusions
func (r *RepositoryImpl) FindAnOtherOwner(workspaceMember *Entity) (*Entity, error) {
qb := metaorm.NewQueryBuilder()
return r.
Query(qb.And(
qb.Field("role").Eq(RoleOwner),
qb.Field("workspace_id").Eq(workspaceMember.WorkspaceId.Get()),
qb.Field("id").Neq(workspaceMember.ID),
)).
FindOne()
}
// Bulk operations
func (r *RepositoryImpl) DeleteAllMembersOfWorkspace(workspaceId uint) error {
qb := metaorm.NewQueryBuilder()
return r.QueriedDelete(qb.Field("workspace_id").Eq(workspaceId))
}
```
## Query Building Best Practices
### 1. Query Builder Usage
```go
// Always start with NewQueryBuilder
qb := metaorm.NewQueryBuilder()
// Simple conditions
qb.Field("field_name").Eq(value)
qb.Field("field_name").Neq(value)
qb.Field("field_name").Similar(keyword) // For LIKE queries
// Complex conditions
qb.And(
qb.Field("field1").Eq(value1),
qb.Field("field2").Eq(value2),
)
qb.Or(
qb.Field("field1").Eq(value1),
qb.Field("field2").Eq(value2),
)
```
### 2. Relationship Handling
```go
// For joins and preloading
return r.
Query(qb.Field("id").Eq(id)).
Joins("RelatedEntity"). // Join for filtering
Preload("RelatedEntity"). // Preload for eager loading
FindOne()
```
### 3. Pagination and Sorting
```go
return r.
Query(qb.And(queries...)).
Joins("User").
Preload("Workspace").
Pagination(page).
Sorting(sort).
PagedFindMany()
```
## Method Naming Conventions
- `FindBy[Field]` - Find single record by field
- `FindBy[Field1]And[Field2]` - Find by multiple fields
- `FindManyBy[Field]` - Find multiple records
- `FilteredPagedFindMany` - Find with pagination and filtering
- `UnscopedFindBy[Field]` - Find including soft-deleted records
- `DeleteBy[Condition]` - Delete records by condition
- `FindAn[Description]` - Find with specific business logic
## Key Implementation Rules
1. **Use generic interfaces** - Prefer `metaorm.PaginationQuery`, `metaorm.SortingQuery`, and `metaorm.Query` over concrete types
2. **Accept query objects** - Let callers build complex filters and pass them as `metaorm.Query`
3. **Always use QueryBuilder** - Never write raw SQL
4. **Use proper field names** - Match database column names exactly
5. **Handle relationships** - Use Joins for filtering, Preload for data
6. **Include comprehensive comments** - Document complex business logic
7. **Follow naming conventions** - Consistent method naming
8. **Return proper types** - Use entity types, not DTOs in repositories
9. **Handle errors properly** - Let MetaORM errors bubble up
## Example: Complete Repository
```go
func NewUserRepository(db metaorm.Database) UserRepository {
return &UserRepositoryImpl{
RepositoryImpl: metaorm.NewRepositoryImpl[User](db),
}
return &UserRepositoryImpl{
RepositoryImpl: metaorm.NewRepositoryImpl[User](db),
}
}
type UserRepository interface {
metaorm.Repository[User]
FindByEmail(email string) (*User, error)
FindByUsername(username string) (*User, error)
FilteredPagedFindMany(
page metaorm.PaginationQuery,
sort metaorm.SortingQuery,
keywordFilter metaorm.Query,
active bool,
) ([]User, *metaorm.Pagination, error)
metaorm.Repository[User]
}
type UserRepositoryImpl struct {
metaorm.RepositoryImpl[User]
metaorm.RepositoryImpl[User]
}
func (r *UserRepositoryImpl) FindByEmail(email string) (*User, error) {
qb := metaorm.NewQueryBuilder()
return r.Query(qb.Field("email").Eq(email)).FindOne()
}
```
func (r *UserRepositoryImpl) FilteredPagedFindMany(
page metaorm.PaginationQuery,
sort metaorm.SortingQuery,
keywordFilter metaorm.Query,
active bool,
) ([]User, *metaorm.Pagination, error) {
qb := metaorm.NewQueryBuilder()
queries := make([]metaorm.Query, 0)
queries = append(queries, qb.Field("active").Eq(active))
if keywordFilter != nil {
queries = append(queries, keywordFilter)
}
return r.
Query(qb.And(queries...)).
Pagination(page).
Sorting(sort).
PagedFindMany()
}
- Must having NewXXXXRepository method.
- Must having XXXXRepository interface.
- Must having XXXXRepositoryImpl struct.
- The XXXX is entity object.